Merge branch 'master' into gizmos
This commit is contained in:
+12
-31
@@ -29,8 +29,6 @@
|
||||
#include "codec/oiio/oiiodecoder.h"
|
||||
#include "codec/waveinput.h"
|
||||
#include "codec/waveoutput.h"
|
||||
#include "render/backend/indexmanager.h"
|
||||
#include "task/index/index.h"
|
||||
#include "task/taskmanager.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
@@ -47,7 +45,7 @@ Decoder::Decoder(Stream *fs) :
|
||||
{
|
||||
}
|
||||
|
||||
StreamPtr Decoder::stream()
|
||||
StreamPtr Decoder::stream() const
|
||||
{
|
||||
return stream_;
|
||||
}
|
||||
@@ -59,7 +57,7 @@ void Decoder::set_stream(StreamPtr fs)
|
||||
stream_ = fs;
|
||||
}
|
||||
|
||||
FramePtr Decoder::RetrieveVideo(const rational &/*timecode*/, const int &/*divider*/)
|
||||
FramePtr Decoder::RetrieveVideo(const rational &/*timecode*/, const int &/*divider*/, bool /*use_proxies*/)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
@@ -134,16 +132,6 @@ bool Decoder::ProbeMedia(Footage *f, const QAtomicInt* cancelled)
|
||||
|
||||
// FIXME: Cache the results so we don't have to probe if this media is added a second time
|
||||
|
||||
// Start an index task
|
||||
foreach (StreamPtr stream, f->streams()) {
|
||||
if (stream->type() == Stream::kAudio) {
|
||||
QMetaObject::invokeMethod(IndexManager::instance(),
|
||||
"StartIndexingStream",
|
||||
Qt::QueuedConnection,
|
||||
OLIVE_NS_ARG(StreamPtr, stream));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -173,7 +161,7 @@ DecoderPtr Decoder::CreateFromID(const QString &id)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Decoder::Conform(const AudioRenderingParams ¶ms, const QAtomicInt* cancelled)
|
||||
/*void Decoder::Conform(const AudioRenderingParams ¶ms, const QAtomicInt* cancelled)
|
||||
{
|
||||
if (stream()->type() != Stream::kAudio) {
|
||||
// Nothing to be done
|
||||
@@ -265,7 +253,7 @@ void Decoder::Conform(const AudioRenderingParams ¶ms, const QAtomicInt* canc
|
||||
} else {
|
||||
qWarning() << "Failed to conform file:" << stream()->footage()->filename();
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
void Decoder::ConformInternal(SwrContext* resampler, WaveOutput* output, const char* in_data, int in_sample_count)
|
||||
{
|
||||
@@ -295,19 +283,6 @@ void Decoder::ConformInternal(SwrContext* resampler, WaveOutput* output, const c
|
||||
QString Decoder::GetConformedFilename(const AudioRenderingParams ¶ms)
|
||||
{
|
||||
QString index_fn = GetIndexFilename();
|
||||
WaveInput input(GetIndexFilename());
|
||||
|
||||
// FIXME: No handling if input failed to open/is corrupt
|
||||
if (input.open()) {
|
||||
// If the parameters are equal, nothing to be done
|
||||
AudioRenderingParams index_params = input.params();
|
||||
input.close();
|
||||
|
||||
if (index_params == params) {
|
||||
// Source file matches perfectly, no conform required
|
||||
return index_fn;
|
||||
}
|
||||
}
|
||||
|
||||
index_fn.append('.');
|
||||
index_fn.append(QString::number(params.sample_rate()));
|
||||
@@ -319,8 +294,14 @@ QString Decoder::GetConformedFilename(const AudioRenderingParams ¶ms)
|
||||
return index_fn;
|
||||
}
|
||||
|
||||
void Decoder::Index(const QAtomicInt *)
|
||||
bool Decoder::ProxyVideo(const QAtomicInt *, int )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Decoder::ConformAudio(const QAtomicInt *, const AudioRenderingParams& )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Decoder::HasConformedVersion(const AudioRenderingParams ¶ms)
|
||||
@@ -349,7 +330,7 @@ bool Decoder::HasConformedVersion(const AudioRenderingParams ¶ms)
|
||||
return index_already_matches;
|
||||
}
|
||||
|
||||
void Decoder::SignalIndexProgress(const int64_t &ts)
|
||||
void Decoder::SignalProcessingProgress(const int64_t &ts)
|
||||
{
|
||||
if (stream()->duration() != AV_NOPTS_VALUE && stream()->duration() != 0) {
|
||||
emit IndexProgress(qRound(100.0 * static_cast<double>(ts) / static_cast<double>(stream()->duration())));
|
||||
|
||||
+23
-28
@@ -74,7 +74,7 @@ public:
|
||||
|
||||
virtual QString id() = 0;
|
||||
|
||||
StreamPtr stream();
|
||||
StreamPtr stream() const;
|
||||
void set_stream(StreamPtr fs);
|
||||
|
||||
/**
|
||||
@@ -117,11 +117,6 @@ public:
|
||||
*/
|
||||
virtual bool Open() = 0;
|
||||
|
||||
/**
|
||||
* @brief Determine whether the Decoder is able to retrieve data
|
||||
*/
|
||||
virtual RetrieveState GetRetrieveState(const rational& time) = 0;
|
||||
|
||||
/**
|
||||
* @brief Retrieve video frame
|
||||
*
|
||||
@@ -143,7 +138,7 @@ public:
|
||||
* A FramePtr of valid data at this timecode or nullptr if there was nothing to retrieve at the provided timecode or
|
||||
* the media could not be opened.
|
||||
*/
|
||||
virtual FramePtr RetrieveVideo(const rational& timecode, const int& divider);
|
||||
virtual FramePtr RetrieveVideo(const rational& timecode, const int& divider, bool use_proxies);
|
||||
|
||||
/**
|
||||
* @brief Retrieve video frame
|
||||
@@ -216,49 +211,49 @@ public:
|
||||
static DecoderPtr CreateFromID(const QString& id);
|
||||
|
||||
/**
|
||||
* @brief Conform an audio stream to match certain parameters (audio only)
|
||||
*
|
||||
* Resamples and converts the currently open audio to match the params. If the audio doesn't need conforming (e.g.
|
||||
* audio params already match or a conformed match already exists), this function will return immediately. Otherwise
|
||||
* it will block the calling thread until the conform is complete. This function should therefore only be called
|
||||
* from a background render thread.
|
||||
*
|
||||
* All audio decoders must override this. It's not pure since video decoders don't need to use this, but default
|
||||
* behavior will abort since it should never be called.
|
||||
* @brief VIDEO ONLY: Produce a compressed EXR proxy with the specified divider
|
||||
*/
|
||||
void Conform(const AudioRenderingParams& params, const QAtomicInt* cancelled);
|
||||
virtual bool ProxyVideo(const QAtomicInt* cancelled, int divider);
|
||||
|
||||
/**
|
||||
* @brief Create an index for this media
|
||||
* @brief AUDIO ONLY: Produces a complete PCM extraction of the audio stream
|
||||
*
|
||||
* Indexes are used to improve speed and reliability of imported media. Calling Retrieve() will automatically check
|
||||
* for an index and create one if it doesn't exist.
|
||||
* Internally, our render engine only deals with PCM since it provides the least headaches and
|
||||
* modern computers have the processing power to do it.
|
||||
*
|
||||
* Indexing is slow so it's recommended to do it in a background thread. Index() must be called while the Decoder is
|
||||
* open, and does not automatically call Open() and Close() the Decoder. The caller must call thse manually.
|
||||
* Resamples and converts the currently open audio to match the params. If the audio doesn't need
|
||||
* conforming (e.g. audio params already match or a conformed match already exists), this function
|
||||
* will return immediately. Otherwise it will block the calling thread until the conform is
|
||||
* complete. This function should therefore only be called from a background render thread.
|
||||
*
|
||||
* All audio decoders must override this. It's not pure since video decoders don't need to use
|
||||
* this, but default behavior will abort since it should never be called.
|
||||
*/
|
||||
virtual void Index(const QAtomicInt* cancelled);
|
||||
virtual bool ConformAudio(const QAtomicInt* cancelled, const AudioRenderingParams ¶ms);
|
||||
|
||||
/**
|
||||
* @brief AUDIO ONLY: Returns whether a cached transcode of this audio matching the specified params already exists
|
||||
* @brief AUDIO ONLY: Returns whether a transcode of this audio matching the specified params
|
||||
* already exists
|
||||
*/
|
||||
bool HasConformedVersion(const AudioRenderingParams& params);
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief While indexing, this signal will provide progress as a percentage (0-100 inclusive) if available
|
||||
* @brief While indexing, this signal will provide progress as a percentage (0-100 inclusive) if
|
||||
* available
|
||||
*/
|
||||
void IndexProgress(int);
|
||||
|
||||
protected:
|
||||
void SignalIndexProgress(const int64_t& ts);
|
||||
void SignalProcessingProgress(const int64_t& ts);
|
||||
|
||||
/**
|
||||
* @brief Returns the filename for the index
|
||||
*
|
||||
* Retrieves the absolute filename of the index file for this stream. Decoder must be open for this to work correctly.
|
||||
* Retrieves the absolute filename of the index file for this stream. Decoder must be open for
|
||||
* this to work correctly.
|
||||
*/
|
||||
virtual QString GetIndexFilename() = 0;
|
||||
virtual QString GetIndexFilename() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the destination filename of an audio stream conformed to a set of parameters
|
||||
|
||||
+338
-161
@@ -27,12 +27,14 @@ extern "C" {
|
||||
#include <libavutil/pixdesc.h>
|
||||
}
|
||||
|
||||
#include <OpenImageIO/imagebuf.h>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QString>
|
||||
#include <QtMath>
|
||||
#include <QThread>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
#include "codec/waveinput.h"
|
||||
#include "common/define.h"
|
||||
@@ -40,6 +42,7 @@ extern "C" {
|
||||
#include "common/functiontimer.h"
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "ffmpegcommon.h"
|
||||
#include "render/backend/videorenderframecache.h"
|
||||
#include "render/diskmanager.h"
|
||||
#include "render/pixelformat.h"
|
||||
|
||||
@@ -108,23 +111,9 @@ bool FFmpegDecoder::Open()
|
||||
|
||||
// Determine which Olive native pixel format we retrieved
|
||||
// Note that FFmpeg doesn't support float formats
|
||||
switch (ideal_pix_fmt_) {
|
||||
case AV_PIX_FMT_RGB24:
|
||||
native_pix_fmt_ = PixelFormat::PIX_FMT_RGB8;
|
||||
break;
|
||||
case AV_PIX_FMT_RGBA:
|
||||
native_pix_fmt_ = PixelFormat::PIX_FMT_RGBA8;
|
||||
break;
|
||||
case AV_PIX_FMT_RGB48:
|
||||
native_pix_fmt_ = PixelFormat::PIX_FMT_RGB16U;
|
||||
break;
|
||||
case AV_PIX_FMT_RGBA64:
|
||||
native_pix_fmt_ = PixelFormat::PIX_FMT_RGBA16U;
|
||||
break;
|
||||
default:
|
||||
// We should never get here, but just in case...
|
||||
qFatal("Invalid output format");
|
||||
}
|
||||
native_pix_fmt_ = GetNativePixelFormat(ideal_pix_fmt_);
|
||||
|
||||
Q_ASSERT(native_pix_fmt_ != PixelFormat::PIX_FMT_INVALID);
|
||||
|
||||
aspect_ratio_ = our_instance->sample_aspect_ratio();
|
||||
}
|
||||
@@ -146,30 +135,7 @@ bool FFmpegDecoder::Open()
|
||||
return true;
|
||||
}
|
||||
|
||||
Decoder::RetrieveState FFmpegDecoder::GetRetrieveState(const rational& time)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
if (!open_) {
|
||||
return kFailedToOpen;
|
||||
}
|
||||
|
||||
if (stream()->type() == Stream::kVideo) {
|
||||
|
||||
// Do nothing
|
||||
|
||||
} else if (stream()->type() == Stream::kAudio) {
|
||||
AudioStreamPtr audio_stream = std::static_pointer_cast<AudioStream>(stream());
|
||||
|
||||
if (time > audio_stream->index_length() && !audio_stream->index_done()) {
|
||||
return kIndexUnavailable;
|
||||
}
|
||||
}
|
||||
|
||||
return kReady;
|
||||
}
|
||||
|
||||
FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int ÷r)
|
||||
FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int ÷r, bool use_proxies)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
@@ -184,6 +150,53 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int &divid
|
||||
|
||||
int64_t target_ts = Timecode::time_to_timestamp(timecode, time_base_) + start_time_;
|
||||
|
||||
VideoStreamPtr vs = std::static_pointer_cast<VideoStream>(stream());
|
||||
|
||||
if (use_proxies && vs->using_proxy()) {
|
||||
QString proxy_fn = GetProxyFilename(vs->using_proxy());
|
||||
|
||||
int64_t index_ts = vs->get_closest_timestamp_in_frame_index(target_ts);
|
||||
|
||||
if (target_ts > -1) {
|
||||
// Use this timestamp instead - even if we fall through to decoding manually, it'll be more
|
||||
// accurate than the one we calculated earlier
|
||||
target_ts = index_ts;
|
||||
|
||||
QString frame_filename = GetProxyFrameFilename(target_ts, vs->using_proxy());
|
||||
|
||||
if (QFileInfo::exists(frame_filename)) {
|
||||
auto in = OIIO::ImageInput::open(frame_filename.toStdString());
|
||||
|
||||
if (in) {
|
||||
FramePtr copy = Frame::Create();
|
||||
copy->set_video_params(VideoRenderingParams(vs->width(),
|
||||
vs->height(),
|
||||
native_pix_fmt_,
|
||||
vs->using_proxy()));
|
||||
copy->set_timestamp(Timecode::timestamp_to_time(target_ts, time_base_));
|
||||
copy->set_sample_aspect_ratio(aspect_ratio_);
|
||||
copy->allocate();
|
||||
|
||||
// We're running one "decoder" per thread already, no need to spawn more than that
|
||||
in->threads(1);
|
||||
|
||||
in->read_image(PixelFormat::GetOIIOTypeDesc(native_pix_fmt_),
|
||||
copy->data(),
|
||||
OIIO::AutoStride,
|
||||
copy->linesize_bytes());
|
||||
|
||||
in->close();
|
||||
|
||||
#if OIIO_VERSION < 10903
|
||||
OIIO::ImageInput::destroy(in);
|
||||
#endif
|
||||
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FFmpegDecoderInstance* working_instance = nullptr;
|
||||
FFmpegFramePool::ElementPtr return_frame = nullptr;
|
||||
|
||||
@@ -300,13 +313,12 @@ FramePtr FFmpegDecoder::RetrieveVideo(const rational &timecode, const int &divid
|
||||
InitScaler(divider);
|
||||
}
|
||||
|
||||
VideoStream* vs = static_cast<VideoStream*>(stream().get());
|
||||
|
||||
// Create frame to return
|
||||
FramePtr copy = Frame::Create();
|
||||
copy->set_video_params(VideoRenderingParams(GetScaledDimension(vs->width(), divider),
|
||||
GetScaledDimension(vs->height(), divider),
|
||||
native_pix_fmt_));
|
||||
copy->set_video_params(VideoRenderingParams(vs->width(),
|
||||
vs->height(),
|
||||
native_pix_fmt_,
|
||||
divider));
|
||||
copy->set_timestamp(Timecode::timestamp_to_time(target_ts, time_base_));
|
||||
copy->set_sample_aspect_ratio(aspect_ratio_);
|
||||
copy->allocate();
|
||||
@@ -623,96 +635,223 @@ void FFmpegDecoder::Error(const QString &s)
|
||||
ClearResources();
|
||||
}
|
||||
|
||||
void FFmpegDecoder::Index(const QAtomicInt* cancelled)
|
||||
QMutex scaler_lock;
|
||||
void SaveCacheFrame(FFmpegDecoder* decoder,
|
||||
SwsContext* scaler,
|
||||
AVFrame* frame,
|
||||
VideoRenderingParams params,
|
||||
QString dst_fn)
|
||||
{
|
||||
QMutexLocker locker(stream()->index_process_lock());
|
||||
QByteArray converted_buffer(PixelFormat::GetBufferSize(params.format(),
|
||||
params.width(),
|
||||
params.height()),
|
||||
Qt::Uninitialized);
|
||||
|
||||
if (stream()->type() == Stream::kAudio) {
|
||||
uint8_t* converted_data = reinterpret_cast<uint8_t*>(converted_buffer.data());
|
||||
int converted_linesize = PixelFormat::GetBufferSize(params.format(),
|
||||
params.width(),
|
||||
1);
|
||||
|
||||
if (QFileInfo::exists(GetIndexFilename())) {
|
||||
WaveInput input(GetIndexFilename());
|
||||
if (input.open()) {
|
||||
std::static_pointer_cast<AudioStream>(stream())->set_index_done(true);
|
||||
std::static_pointer_cast<AudioStream>(stream())->set_index_length(input.params().bytes_to_time(input.data_length()));
|
||||
scaler_lock.lock();
|
||||
sws_scale(scaler,
|
||||
frame->data,
|
||||
frame->linesize,
|
||||
0,
|
||||
frame->height,
|
||||
&converted_data,
|
||||
&converted_linesize);
|
||||
scaler_lock.unlock();
|
||||
|
||||
input.close();
|
||||
}
|
||||
} else {
|
||||
UnconditionalAudioIndex(cancelled);
|
||||
if (!VideoRenderFrameCache::SaveCacheFrame(dst_fn, converted_buffer.data(), params)) {
|
||||
qCritical() <<" Failed to save cache frame" << dst_fn;
|
||||
}
|
||||
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
bool FFmpegDecoder::ProxyVideo(const QAtomicInt *cancelled, int divider)
|
||||
{
|
||||
VideoStreamPtr video_stream = std::static_pointer_cast<VideoStream>(stream());
|
||||
|
||||
QString proxy_filename = GetProxyFilename(divider);
|
||||
|
||||
if (QFileInfo::exists(proxy_filename)) {
|
||||
|
||||
// A proxy of this type already exists so we can do nothing
|
||||
QFile index_file(proxy_filename);
|
||||
if (index_file.open(QFile::ReadOnly)) {
|
||||
QVector<int64_t> index(index_file.size() / sizeof(int64_t));
|
||||
|
||||
index_file.read(reinterpret_cast<char*>(index.data()),
|
||||
index_file.size());
|
||||
|
||||
index_file.close();
|
||||
|
||||
video_stream->set_proxy(divider, index);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Iterate each frame and transcode it to EXR
|
||||
FFmpegDecoderInstance instance(stream()->footage()->filename().toUtf8(), stream()->index());
|
||||
|
||||
int ret;
|
||||
|
||||
AVPixelFormat src_fmt = static_cast<AVPixelFormat>(instance.stream()->codecpar->format);
|
||||
AVPixelFormat ideal_fmt = FFmpegCommon::GetCompatiblePixelFormat(src_fmt);
|
||||
PixelFormat::Format native_fmt = GetNativePixelFormat(ideal_fmt);
|
||||
|
||||
int divided_width = GetScaledDimension(instance.stream()->codecpar->width, divider);
|
||||
int divided_height = GetScaledDimension(instance.stream()->codecpar->height, divider);
|
||||
|
||||
SwsContext* scaler = sws_getContext(instance.stream()->codecpar->width,
|
||||
instance.stream()->codecpar->height,
|
||||
src_fmt,
|
||||
divided_width,
|
||||
divided_height,
|
||||
ideal_fmt,
|
||||
SWS_FAST_BILINEAR,
|
||||
nullptr,
|
||||
nullptr,
|
||||
0);
|
||||
|
||||
AVPacket* pkt = av_packet_alloc();
|
||||
QVector<int64_t> frame_index;
|
||||
QVector< QFuture<void> > futures;
|
||||
int finished_futures = 0;
|
||||
|
||||
VideoRenderingParams converted_params(divided_width,
|
||||
divided_height,
|
||||
native_fmt);
|
||||
|
||||
bool succeeded = false;
|
||||
|
||||
while (true) {
|
||||
if (cancelled && *cancelled) {
|
||||
break;
|
||||
}
|
||||
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
|
||||
ret = instance.GetFrame(pkt, frame);
|
||||
|
||||
// Handle errors
|
||||
if (ret < 0) {
|
||||
if (ret == AVERROR_EOF) {
|
||||
succeeded = true;
|
||||
} else {
|
||||
char err_str[50];
|
||||
av_strerror(ret, err_str, 50);
|
||||
qWarning() << "Failed to proxy:" << ret << err_str;
|
||||
}
|
||||
|
||||
av_frame_free(&frame);
|
||||
break;
|
||||
}
|
||||
|
||||
frame_index.append(frame->pts);
|
||||
|
||||
QFuture<void> future = QtConcurrent::run(SaveCacheFrame,
|
||||
this,
|
||||
scaler,
|
||||
frame,
|
||||
converted_params,
|
||||
GetProxyFrameFilename(frame->pts, divider));
|
||||
futures.append(future);
|
||||
|
||||
while (finished_futures < futures.size()) {
|
||||
if (!futures.at(finished_futures).isFinished()) {
|
||||
SignalProcessingProgress(frame_index.at(finished_futures));
|
||||
break;
|
||||
}
|
||||
|
||||
finished_futures++;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for all conversions to finish
|
||||
for ( ; finished_futures<futures.size(); finished_futures++) {
|
||||
futures[finished_futures].waitForFinished();
|
||||
SignalProcessingProgress(frame_index.at(finished_futures));
|
||||
}
|
||||
|
||||
// If succeeded, update the video stream's proxy state
|
||||
if (succeeded) {
|
||||
QFile index_output(proxy_filename);
|
||||
|
||||
if (index_output.open(QFile::WriteOnly)) {
|
||||
index_output.write(reinterpret_cast<const char*>(frame_index.constData()),
|
||||
frame_index.size() * sizeof(int64_t));
|
||||
|
||||
index_output.close();
|
||||
}
|
||||
|
||||
video_stream->set_proxy(divider, frame_index);
|
||||
}
|
||||
|
||||
sws_freeContext(scaler);
|
||||
|
||||
av_packet_free(&pkt);
|
||||
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
QString FFmpegDecoder::GetIndexFilename()
|
||||
{
|
||||
return FileFunctions::GetMediaIndexFilename(FileFunctions::GetUniqueFileIdentifier(stream()->footage()->filename()))
|
||||
.append(QString::number(stream()->index()));
|
||||
}
|
||||
|
||||
int FFmpegDecoder::GetScaledDimension(int dim, int divider)
|
||||
{
|
||||
return dim / divider;
|
||||
}
|
||||
|
||||
void FFmpegDecoder::UnconditionalAudioIndex(const QAtomicInt* cancelled)
|
||||
bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioRenderingParams &p)
|
||||
{
|
||||
// Iterate through each audio frame and extract the PCM data
|
||||
|
||||
QByteArray fn_bytes = stream()->footage()->filename().toUtf8();
|
||||
|
||||
FFmpegDecoderInstance index_instance(fn_bytes.constData(), stream()->index());
|
||||
|
||||
uint64_t channel_layout = index_instance.stream()->codecpar->channel_layout;
|
||||
if (!channel_layout) {
|
||||
if (!index_instance.stream()->codecpar->channels) {
|
||||
// No channel data - we can't do anything with this
|
||||
return;
|
||||
}
|
||||
|
||||
channel_layout = static_cast<uint64_t>(av_get_default_channel_layout(index_instance.stream()->codecpar->channels));
|
||||
}
|
||||
|
||||
AudioStreamPtr audio_stream = std::static_pointer_cast<AudioStream>(stream());
|
||||
|
||||
// This should be unnecessary, but just in case...
|
||||
audio_stream->clear_index();
|
||||
// Check if we already have a conform of this type
|
||||
QString conformed_fn = GetConformedFilename(p);
|
||||
|
||||
SwrContext* resampler = nullptr;
|
||||
AVSampleFormat src_sample_fmt = static_cast<AVSampleFormat>(index_instance.stream()->codecpar->format);
|
||||
AVSampleFormat dst_sample_fmt;
|
||||
if (QFileInfo::exists(conformed_fn)) {
|
||||
|
||||
// We don't use planar types internally, so if this is a planar format convert it now
|
||||
if (av_sample_fmt_is_planar(src_sample_fmt)) {
|
||||
dst_sample_fmt = av_get_packed_sample_fmt(src_sample_fmt);
|
||||
// If we have one, and we can open it correctly, we can use it as-is
|
||||
WaveInput input(conformed_fn);
|
||||
if (input.open()) {
|
||||
audio_stream->append_conformed_version(p);
|
||||
|
||||
resampler = swr_alloc_set_opts(nullptr,
|
||||
static_cast<int64_t>(index_instance.stream()->codecpar->channel_layout),
|
||||
dst_sample_fmt,
|
||||
index_instance.stream()->codecpar->sample_rate,
|
||||
static_cast<int64_t>(index_instance.stream()->codecpar->channel_layout),
|
||||
src_sample_fmt,
|
||||
index_instance.stream()->codecpar->sample_rate,
|
||||
0,
|
||||
nullptr);
|
||||
input.close();
|
||||
|
||||
swr_init(resampler);
|
||||
} else {
|
||||
dst_sample_fmt = src_sample_fmt;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
AudioRenderingParams wave_params(index_instance.stream()->codecpar->sample_rate,
|
||||
channel_layout,
|
||||
FFmpegCommon::GetNativeSampleFormat(dst_sample_fmt));
|
||||
WaveOutput wave_out(GetIndexFilename(), wave_params);
|
||||
// Conform doesn't exist, we'll have to produce one
|
||||
FFmpegDecoderInstance index_instance(stream()->footage()->filename().toUtf8(),
|
||||
stream()->index());
|
||||
|
||||
// Handle NULL channel layout
|
||||
uint64_t channel_layout = ValidateChannelLayout(index_instance.stream());
|
||||
if (!channel_layout) {
|
||||
qCritical() << "Failed to determine channel layout of audio file, could not conform";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create resampling context
|
||||
SwrContext* resampler = swr_alloc_set_opts(nullptr,
|
||||
p.channel_layout(),
|
||||
FFmpegCommon::GetFFmpegSampleFormat(p.format()),
|
||||
p.sample_rate(),
|
||||
static_cast<int64_t>(index_instance.stream()->codecpar->channel_layout),
|
||||
static_cast<AVSampleFormat>(index_instance.stream()->codecpar->format),
|
||||
index_instance.stream()->codecpar->sample_rate,
|
||||
0,
|
||||
nullptr);
|
||||
|
||||
swr_init(resampler);
|
||||
|
||||
WaveOutput wave_out(conformed_fn, p);
|
||||
|
||||
AVPacket* pkt = av_packet_alloc();
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
int ret;
|
||||
|
||||
if (wave_out.open()) {
|
||||
bool success = false;
|
||||
bool success = false;
|
||||
|
||||
if (wave_out.open()) {
|
||||
while (true) {
|
||||
// Check if we have a `cancelled` ptr and its value
|
||||
if (cancelled && *cancelled) {
|
||||
@@ -728,75 +867,105 @@ void FFmpegDecoder::UnconditionalAudioIndex(const QAtomicInt* cancelled)
|
||||
} else {
|
||||
char err_str[50];
|
||||
av_strerror(ret, err_str, 50);
|
||||
qWarning() << "Failed to index:" << ret << err_str;
|
||||
qWarning() << "Failed to conform:" << ret << err_str;
|
||||
}
|
||||
break;
|
||||
|
||||
} else {
|
||||
char* data;
|
||||
int nb_samples;
|
||||
|
||||
if (resampler) {
|
||||
|
||||
nb_samples = swr_get_out_samples(resampler, frame->nb_samples);
|
||||
|
||||
data = new char[wave_params.samples_to_bytes(nb_samples)];
|
||||
|
||||
// We must need to resample this (mainly just convert from planar to packed if necessary)
|
||||
nb_samples = swr_convert(resampler,
|
||||
reinterpret_cast<uint8_t**>(&data),
|
||||
nb_samples,
|
||||
const_cast<const uint8_t**>(frame->data),
|
||||
frame->nb_samples);
|
||||
|
||||
if (nb_samples < 0) {
|
||||
char err_str[50];
|
||||
av_strerror(nb_samples, err_str, 50);
|
||||
qWarning() << "libswresample failed with error:" << nb_samples << err_str;
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// No resampling required, we can write directly from the frame buffer
|
||||
data = reinterpret_cast<char*>(frame->data[0]);
|
||||
nb_samples = frame->nb_samples;
|
||||
|
||||
}
|
||||
|
||||
// Write packed WAV data to the disk cache
|
||||
wave_out.write(data, wave_params.samples_to_bytes(nb_samples));
|
||||
|
||||
audio_stream->set_index_length(wave_params.bytes_to_time(wave_out.data_length()));
|
||||
|
||||
// If we allocated an output for the resampler, delete it here
|
||||
if (data != reinterpret_cast<char*>(frame->data[0])) {
|
||||
delete [] data;
|
||||
}
|
||||
|
||||
SignalIndexProgress(frame->pts);
|
||||
}
|
||||
|
||||
// Allocate buffers
|
||||
int nb_samples = swr_get_out_samples(resampler, frame->nb_samples);
|
||||
char* data = new char[p.samples_to_bytes(nb_samples)];
|
||||
|
||||
// Resample audio to our destination parameters
|
||||
nb_samples = swr_convert(resampler,
|
||||
reinterpret_cast<uint8_t**>(&data),
|
||||
nb_samples,
|
||||
const_cast<const uint8_t**>(frame->data),
|
||||
frame->nb_samples);
|
||||
|
||||
if (nb_samples < 0) {
|
||||
char err_str[50];
|
||||
av_strerror(nb_samples, err_str, 50);
|
||||
qWarning() << "libswresample failed with error:" << nb_samples << err_str;
|
||||
break;
|
||||
}
|
||||
|
||||
// Write packed WAV data to the disk cache
|
||||
wave_out.write(data, p.samples_to_bytes(nb_samples));
|
||||
|
||||
// If we allocated an output for the resampler, delete it here
|
||||
if (data != reinterpret_cast<char*>(frame->data[0])) {
|
||||
delete [] data;
|
||||
}
|
||||
|
||||
SignalProcessingProgress(frame->pts);
|
||||
}
|
||||
|
||||
wave_out.close();
|
||||
|
||||
if (success) {
|
||||
audio_stream->set_index_done(true);
|
||||
|
||||
// If our conform succeeded, add it
|
||||
audio_stream->append_conformed_version(p);
|
||||
|
||||
} else {
|
||||
|
||||
// Audio index didn't complete, delete it
|
||||
QFile(GetIndexFilename()).remove();
|
||||
audio_stream->clear_index();
|
||||
QFile(conformed_fn).remove();
|
||||
|
||||
}
|
||||
} else {
|
||||
qWarning() << "Failed to open WAVE output for indexing";
|
||||
}
|
||||
|
||||
if (resampler != nullptr) {
|
||||
swr_free(&resampler);
|
||||
}
|
||||
swr_free(&resampler);
|
||||
|
||||
av_frame_free(&frame);
|
||||
av_packet_free(&pkt);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
QString FFmpegDecoder::GetIndexFilename() const
|
||||
{
|
||||
return FileFunctions::GetMediaIndexFilename(FileFunctions::GetUniqueFileIdentifier(stream()->footage()->filename()))
|
||||
.append(QString::number(stream()->index()));
|
||||
}
|
||||
|
||||
QString FFmpegDecoder::GetProxyFilename(int divider) const
|
||||
{
|
||||
return GetIndexFilename().append('d').append(QString::number(divider));
|
||||
}
|
||||
|
||||
int FFmpegDecoder::GetScaledDimension(int dim, int divider)
|
||||
{
|
||||
return dim / divider;
|
||||
}
|
||||
|
||||
PixelFormat::Format FFmpegDecoder::GetNativePixelFormat(AVPixelFormat pix_fmt)
|
||||
{
|
||||
switch (pix_fmt) {
|
||||
case AV_PIX_FMT_RGB24:
|
||||
return PixelFormat::PIX_FMT_RGB8;
|
||||
case AV_PIX_FMT_RGBA:
|
||||
return PixelFormat::PIX_FMT_RGBA8;
|
||||
case AV_PIX_FMT_RGB48:
|
||||
return PixelFormat::PIX_FMT_RGB16U;
|
||||
case AV_PIX_FMT_RGBA64:
|
||||
return PixelFormat::PIX_FMT_RGBA16U;
|
||||
default:
|
||||
return PixelFormat::PIX_FMT_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t FFmpegDecoder::ValidateChannelLayout(AVStream* stream)
|
||||
{
|
||||
if (stream->codecpar->channel_layout) {
|
||||
return stream->codecpar->channel_layout;
|
||||
}
|
||||
|
||||
return av_get_default_channel_layout(stream->codecpar->channels);
|
||||
}
|
||||
|
||||
int FFmpegDecoderInstance::GetFrame(AVPacket *pkt, AVFrame *frame)
|
||||
@@ -1113,6 +1282,14 @@ void FFmpegDecoder::FreeScaler()
|
||||
}
|
||||
}
|
||||
|
||||
QString FFmpegDecoder::GetProxyFrameFilename(const int64_t ×tamp, const int& divider) const
|
||||
{
|
||||
QString dst_fn = GetProxyFilename(divider);
|
||||
dst_fn.append(QString::number(timestamp));
|
||||
dst_fn.append(VideoRenderFrameCache::GetFormatExtension(native_pix_fmt_));
|
||||
return dst_fn;
|
||||
}
|
||||
|
||||
int64_t FFmpegDecoderInstance::RangeStart() const
|
||||
{
|
||||
if (cached_frames_.isEmpty()) {
|
||||
|
||||
@@ -134,8 +134,7 @@ public:
|
||||
virtual bool Probe(Footage *f, const QAtomicInt *cancelled) override;
|
||||
|
||||
virtual bool Open() override;
|
||||
virtual RetrieveState GetRetrieveState(const rational &time) override;
|
||||
virtual FramePtr RetrieveVideo(const rational &timecode, const int& divider) override;
|
||||
virtual FramePtr RetrieveVideo(const rational &timecode, const int& divider, bool use_proxies) override;
|
||||
virtual SampleBufferPtr RetrieveAudio(const rational &timecode, const rational &length, const AudioRenderingParams& params) override;
|
||||
virtual void Close() override;
|
||||
|
||||
@@ -144,7 +143,8 @@ public:
|
||||
virtual bool SupportsVideo() override;
|
||||
virtual bool SupportsAudio() override;
|
||||
|
||||
virtual void Index(const QAtomicInt *cancelled) override;
|
||||
virtual bool ProxyVideo(const QAtomicInt* cancelled, int divider) override;
|
||||
virtual bool ConformAudio(const QAtomicInt* cancelled, const AudioRenderingParams& p) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
@@ -166,17 +166,23 @@ private:
|
||||
*/
|
||||
void FFmpegError(int error_code);
|
||||
|
||||
virtual QString GetIndexFilename() override;
|
||||
virtual QString GetIndexFilename() const override;
|
||||
|
||||
void UnconditionalAudioIndex(const QAtomicInt* cancelled);
|
||||
QString GetProxyFilename(int divider) const;
|
||||
|
||||
void ClearResources();
|
||||
|
||||
void InitScaler(int divider);
|
||||
void FreeScaler();
|
||||
|
||||
QString GetProxyFrameFilename(const int64_t& timestamp, const int ÷r) const;
|
||||
|
||||
static int GetScaledDimension(int dim, int divider);
|
||||
|
||||
static PixelFormat::Format GetNativePixelFormat(AVPixelFormat pix_fmt);
|
||||
|
||||
static uint64_t ValidateChannelLayout(AVStream *stream);
|
||||
|
||||
SwsContext* scale_ctx_;
|
||||
int scale_divider_;
|
||||
AVPixelFormat src_pix_fmt_;
|
||||
|
||||
+3
-3
@@ -47,7 +47,7 @@ void Frame::set_video_params(const VideoRenderingParams ¶ms)
|
||||
params_ = params;
|
||||
|
||||
// Align linesize to 16
|
||||
linesize_ = qCeil(static_cast<double>(params.width()) / 16.0) * 16;
|
||||
linesize_ = qCeil(static_cast<double>(width()) / 16.0) * 16;
|
||||
}
|
||||
|
||||
int Frame::linesize_pixels() const
|
||||
@@ -62,12 +62,12 @@ int Frame::linesize_bytes() const
|
||||
|
||||
const int &Frame::width() const
|
||||
{
|
||||
return params_.width();
|
||||
return params_.effective_width();
|
||||
}
|
||||
|
||||
const int &Frame::height() const
|
||||
{
|
||||
return params_.height();
|
||||
return params_.effective_height();
|
||||
}
|
||||
|
||||
const PixelFormat::Format &Frame::format() const
|
||||
|
||||
@@ -154,18 +154,7 @@ bool OIIODecoder::Open()
|
||||
return true;
|
||||
}
|
||||
|
||||
Decoder::RetrieveState OIIODecoder::GetRetrieveState(const rational &time)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
if (!open_) {
|
||||
return kFailedToOpen;
|
||||
}
|
||||
|
||||
return kReady;
|
||||
}
|
||||
|
||||
FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider)
|
||||
FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider, bool /*use_proxies*/)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
@@ -226,11 +215,40 @@ bool OIIODecoder::SupportsVideo()
|
||||
return true;
|
||||
}
|
||||
|
||||
QString OIIODecoder::GetIndexFilename()
|
||||
QString OIIODecoder::GetIndexFilename() const
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
void OIIODecoder::FrameToBuffer(FramePtr frame, OIIO::ImageBuf *buf)
|
||||
{
|
||||
#if OIIO_VERSION < 20112
|
||||
//
|
||||
// Workaround for OIIO bug that ignores destination stride in versions OLDER than 2.1.12
|
||||
//
|
||||
// See more: https://github.com/OpenImageIO/oiio/pull/2487
|
||||
//
|
||||
int width_in_bytes = frame->width() * PixelFormat::BytesPerPixel(frame->format());
|
||||
|
||||
for (int i=0;i<buf->spec().height;i++) {
|
||||
memcpy(
|
||||
#if OIIO_VERSION < 10903
|
||||
reinterpret_cast<char*>(buf->localpixels()) + i * width_in_bytes,
|
||||
#else
|
||||
reinterpret_cast<char*>(buf->localpixels()) + i * buf->scanline_stride(),
|
||||
#endif
|
||||
frame->data() + i * frame->linesize_bytes(),
|
||||
width_in_bytes);
|
||||
}
|
||||
#else
|
||||
buf->set_pixels(OIIO::ROI(),
|
||||
buf->spec().format,
|
||||
frame->data(),
|
||||
OIIO::AutoStride,
|
||||
frame->linesize_bytes());
|
||||
#endif
|
||||
}
|
||||
|
||||
void OIIODecoder::BufferToFrame(OIIO::ImageBuf *buf, FramePtr frame)
|
||||
{
|
||||
#if OIIO_VERSION < 20112
|
||||
@@ -239,9 +257,9 @@ void OIIODecoder::BufferToFrame(OIIO::ImageBuf *buf, FramePtr frame)
|
||||
//
|
||||
// See more: https://github.com/OpenImageIO/oiio/pull/2487
|
||||
//
|
||||
for (int i=0;i<buf->spec().height;i++) {
|
||||
int width_in_bytes = frame->width() * PixelFormat::BytesPerPixel(frame->format());
|
||||
int width_in_bytes = frame->width() * PixelFormat::BytesPerPixel(frame->format());
|
||||
|
||||
for (int i=0;i<buf->spec().height;i++) {
|
||||
memcpy(frame->data() + i * frame->linesize_bytes(),
|
||||
#if OIIO_VERSION < 10903
|
||||
reinterpret_cast<const char*>(buf->localpixels()) + i * width_in_bytes,
|
||||
|
||||
@@ -40,13 +40,14 @@ public:
|
||||
virtual bool Probe(Footage *f, const QAtomicInt* cancelled) override;
|
||||
|
||||
virtual bool Open() override;
|
||||
virtual RetrieveState GetRetrieveState(const rational &time) override;
|
||||
virtual FramePtr RetrieveVideo(const rational &timecode, const int& divider) override;
|
||||
virtual FramePtr RetrieveVideo(const rational &timecode, const int& divider, bool use_proxies) override;
|
||||
virtual void Close() override;
|
||||
|
||||
virtual bool SupportsVideo() override;
|
||||
|
||||
virtual QString GetIndexFilename() override;
|
||||
virtual QString GetIndexFilename() const override;
|
||||
|
||||
static void FrameToBuffer(FramePtr frame, OIIO::ImageBuf* buf);
|
||||
|
||||
static void BufferToFrame(OIIO::ImageBuf* buf, FramePtr frame);
|
||||
|
||||
|
||||
+17
-11
@@ -48,7 +48,6 @@
|
||||
#include "project/projectimportmanager.h"
|
||||
#include "project/projectloadmanager.h"
|
||||
#include "project/projectsavemanager.h"
|
||||
#include "render/backend/indexmanager.h"
|
||||
#include "render/backend/opengl/opengltexturecache.h"
|
||||
#include "render/colormanager.h"
|
||||
#include "render/diskmanager.h"
|
||||
@@ -119,15 +118,9 @@ bool Core::Start()
|
||||
// Set up node factory/library
|
||||
NodeFactory::Initialize();
|
||||
|
||||
// Set up the index manager for renderers
|
||||
IndexManager::CreateInstance();
|
||||
|
||||
// Set up color manager's default config
|
||||
ColorManager::SetUpDefaultConfig();
|
||||
|
||||
// Initialize disk service
|
||||
DiskManager::CreateInstance();
|
||||
|
||||
// Initialize task manager
|
||||
TaskManager::CreateInstance();
|
||||
|
||||
@@ -226,8 +219,6 @@ void Core::Stop()
|
||||
|
||||
NodeFactory::Destroy();
|
||||
|
||||
IndexManager::DestroyInstance();
|
||||
|
||||
delete main_window_;
|
||||
}
|
||||
|
||||
@@ -370,8 +361,17 @@ void Core::DialogPreferencesShow()
|
||||
|
||||
void Core::DialogProjectPropertiesShow()
|
||||
{
|
||||
ProjectPropertiesDialog ppd(GetActiveProject().get(), main_window_);
|
||||
ppd.exec();
|
||||
ProjectPtr proj = GetActiveProject();
|
||||
|
||||
if (proj) {
|
||||
ProjectPropertiesDialog ppd(proj.get(), main_window_);
|
||||
ppd.exec();
|
||||
} else {
|
||||
QMessageBox::critical(main_window_,
|
||||
tr("No Active Project"),
|
||||
tr("No project is currently open to set the properties for"),
|
||||
QMessageBox::Ok);
|
||||
}
|
||||
}
|
||||
|
||||
void Core::DialogExportShow()
|
||||
@@ -556,6 +556,9 @@ void Core::StartGUI(bool full_screen)
|
||||
// Initialize audio service
|
||||
AudioManager::CreateInstance();
|
||||
|
||||
// Initialize disk service
|
||||
DiskManager::CreateInstance();
|
||||
|
||||
// Initialize pixel service
|
||||
PixelFormat::CreateInstance();
|
||||
|
||||
@@ -783,6 +786,7 @@ QList<uint64_t> Core::SupportedChannelLayouts()
|
||||
|
||||
channel_layouts.append(AV_CH_LAYOUT_MONO);
|
||||
channel_layouts.append(AV_CH_LAYOUT_STEREO);
|
||||
channel_layouts.append(AV_CH_LAYOUT_2_1);
|
||||
channel_layouts.append(AV_CH_LAYOUT_5POINT1);
|
||||
channel_layouts.append(AV_CH_LAYOUT_7POINT1);
|
||||
|
||||
@@ -806,6 +810,8 @@ QString Core::ChannelLayoutToString(const uint64_t &layout)
|
||||
return tr("Mono");
|
||||
case AV_CH_LAYOUT_STEREO:
|
||||
return tr("Stereo");
|
||||
case AV_CH_LAYOUT_2_1:
|
||||
return tr("2.1");
|
||||
case AV_CH_LAYOUT_5POINT1:
|
||||
return tr("5.1");
|
||||
case AV_CH_LAYOUT_7POINT1:
|
||||
|
||||
@@ -66,7 +66,11 @@ FootagePropertiesDialog::FootagePropertiesDialog(QWidget *parent, Footage *foota
|
||||
stacked_widget_ = new QStackedWidget();
|
||||
layout->addWidget(stacked_widget_, row, 0, 1, 2);
|
||||
|
||||
foreach (StreamPtr stream, footage_->streams()) {
|
||||
int first_usable_stream = -1;
|
||||
|
||||
for (int i=0;i<footage_->streams().size();i++) {
|
||||
StreamPtr stream = footage_->stream(i);
|
||||
|
||||
QListWidgetItem* item = new QListWidgetItem(stream->description(), track_list);
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(stream->enabled() ? Qt::Checked : Qt::Unchecked);
|
||||
@@ -83,18 +87,31 @@ FootagePropertiesDialog::FootagePropertiesDialog(QWidget *parent, Footage *foota
|
||||
default:
|
||||
stacked_widget_->addWidget(new StreamProperties());
|
||||
}
|
||||
|
||||
if (first_usable_stream == -1
|
||||
&& (stream->type() == Stream::kVideo
|
||||
|| stream->type() == Stream::kAudio
|
||||
|| stream->type() == Stream::kImage)) {
|
||||
first_usable_stream = i;
|
||||
}
|
||||
}
|
||||
|
||||
row++;
|
||||
|
||||
connect(track_list, SIGNAL(currentRowChanged(int)), stacked_widget_, SLOT(setCurrentIndex(int)));
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
buttons->setCenterButtons(true);
|
||||
layout->addWidget(buttons, row, 0, 1, 2);
|
||||
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
connect(track_list, &QListWidget::currentRowChanged, stacked_widget_, &QStackedWidget::setCurrentIndex);
|
||||
|
||||
// Auto-select first item that actually has properties
|
||||
if (first_usable_stream >= 0) {
|
||||
track_list->item(first_usable_stream)->setSelected(true);
|
||||
}
|
||||
track_list->setFocus();
|
||||
}
|
||||
|
||||
void FootagePropertiesDialog::accept() {
|
||||
|
||||
@@ -62,16 +62,19 @@ PreferencesDialog::PreferencesDialog(QWidget *parent, QMenuBar* main_menu_bar) :
|
||||
splitter->addWidget(list_widget_);
|
||||
splitter->addWidget(preference_pane_stack_);
|
||||
|
||||
QDialogButtonBox* buttonBox = new QDialogButtonBox(this);
|
||||
buttonBox->setOrientation(Qt::Horizontal);
|
||||
buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
|
||||
QDialogButtonBox* button_box = new QDialogButtonBox(this);
|
||||
button_box->setOrientation(Qt::Horizontal);
|
||||
button_box->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
|
||||
|
||||
layout->addWidget(buttonBox);
|
||||
layout->addWidget(button_box);
|
||||
|
||||
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
|
||||
connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
|
||||
connect(button_box, &QDialogButtonBox::accepted, this, &PreferencesDialog::accept);
|
||||
connect(button_box, &QDialogButtonBox::rejected, this, &PreferencesDialog::reject);
|
||||
|
||||
connect(list_widget_, SIGNAL(currentRowChanged(int)), preference_pane_stack_, SLOT(setCurrentIndex(int)));
|
||||
connect(list_widget_,
|
||||
&QListWidget::currentRowChanged,
|
||||
preference_pane_stack_,
|
||||
&QStackedWidget::setCurrentIndex);
|
||||
}
|
||||
|
||||
void PreferencesDialog::accept()
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileDialog>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
@@ -45,67 +44,119 @@ ProjectPropertiesDialog::ProjectPropertiesDialog(Project* p, QWidget *parent) :
|
||||
|
||||
setWindowTitle(tr("Project Properties for '%1'").arg(working_project_->name()));
|
||||
|
||||
QGroupBox* color_group = new QGroupBox();
|
||||
color_group->setTitle(tr("Color Management"));
|
||||
{
|
||||
// Color management group
|
||||
QGroupBox* color_group = new QGroupBox();
|
||||
color_group->setTitle(tr("Color Management"));
|
||||
|
||||
QGridLayout* color_layout = new QGridLayout(color_group);
|
||||
QGridLayout* color_layout = new QGridLayout(color_group);
|
||||
|
||||
int row = 0;
|
||||
int row = 0;
|
||||
|
||||
color_layout->addWidget(new QLabel(tr("OpenColorIO Configuration:")), row, 0);
|
||||
color_layout->addWidget(new QLabel(tr("OpenColorIO Configuration:")), row, 0);
|
||||
|
||||
ocio_filename_ = new QLineEdit();
|
||||
ocio_filename_->setPlaceholderText(tr("(default)"));
|
||||
color_layout->addWidget(ocio_filename_, row, 1);
|
||||
ocio_filename_ = new QLineEdit();
|
||||
ocio_filename_->setPlaceholderText(tr("(default)"));
|
||||
color_layout->addWidget(ocio_filename_, row, 1);
|
||||
|
||||
row++;
|
||||
row++;
|
||||
|
||||
color_layout->addWidget(new QLabel(tr("Default Input Color Space:")), row, 0);
|
||||
color_layout->addWidget(new QLabel(tr("Default Input Color Space:")), row, 0);
|
||||
|
||||
default_input_colorspace_ = new QComboBox();
|
||||
color_layout->addWidget(default_input_colorspace_, row, 1, 1, 2);
|
||||
default_input_colorspace_ = new QComboBox();
|
||||
color_layout->addWidget(default_input_colorspace_, row, 1, 1, 2);
|
||||
|
||||
row++;
|
||||
row++;
|
||||
|
||||
QPushButton* browse_btn = new QPushButton(tr("Browse"));
|
||||
color_layout->addWidget(browse_btn, 0, 2);
|
||||
connect(browse_btn, SIGNAL(clicked(bool)), this, SLOT(BrowseForOCIOConfig()));
|
||||
QPushButton* browse_btn = new QPushButton(tr("Browse"));
|
||||
color_layout->addWidget(browse_btn, 0, 2);
|
||||
connect(browse_btn, &QPushButton::clicked, this, &ProjectPropertiesDialog::BrowseForOCIOConfig);
|
||||
|
||||
layout->addWidget(color_group);
|
||||
layout->addWidget(color_group);
|
||||
|
||||
QDialogButtonBox* dialog_btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal);
|
||||
layout->addWidget(dialog_btns);
|
||||
connect(dialog_btns, SIGNAL(accepted()), this, SLOT(accept()));
|
||||
connect(dialog_btns, SIGNAL(rejected()), this, SLOT(reject()));
|
||||
ocio_filename_->setText(working_project_->color_manager()->GetConfigFilename());
|
||||
|
||||
if (working_project_ == nullptr) {
|
||||
QMessageBox::critical(this,
|
||||
tr("No Active Project"),
|
||||
tr("No project is currently open to set the properties for"),
|
||||
QMessageBox::Ok);
|
||||
reject();
|
||||
return;
|
||||
connect(ocio_filename_, &QLineEdit::textChanged, this, &ProjectPropertiesDialog::OCIOFilenameUpdated);
|
||||
OCIOFilenameUpdated();
|
||||
}
|
||||
|
||||
ocio_filename_->setText(working_project_->color_manager()->GetConfigFilename());
|
||||
{
|
||||
// Paths group
|
||||
QGroupBox* paths_group = new QGroupBox();
|
||||
paths_group->setTitle(tr("Paths"));
|
||||
|
||||
connect(ocio_filename_, &QLineEdit::textChanged, this, &ProjectPropertiesDialog::FilenameUpdated);
|
||||
FilenameUpdated();
|
||||
QGridLayout* paths_layout = new QGridLayout(paths_group);
|
||||
|
||||
cache_path_ = new PathWidget(working_project_->cache_path(), this);
|
||||
proxy_path_ = new PathWidget(working_project_->proxy_path(), this);
|
||||
|
||||
int row = 0;
|
||||
|
||||
paths_layout->addWidget(new QLabel(tr("Cache Path:")), row, 0);
|
||||
paths_layout->addWidget(cache_path_->path_edit(), row, 1);
|
||||
paths_layout->addWidget(cache_path_->browse_btn(), row, 2);
|
||||
paths_layout->addWidget(cache_path_->default_box(), row, 3);
|
||||
|
||||
row++;
|
||||
|
||||
paths_layout->addWidget(new QLabel(tr("Proxy Path:")), row, 0);
|
||||
paths_layout->addWidget(proxy_path_->path_edit(), row, 1);
|
||||
paths_layout->addWidget(proxy_path_->browse_btn(), row, 2);
|
||||
paths_layout->addWidget(proxy_path_->default_box(), row, 3);
|
||||
|
||||
layout->addWidget(paths_group);
|
||||
}
|
||||
|
||||
QDialogButtonBox* dialog_btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
|
||||
Qt::Horizontal);
|
||||
layout->addWidget(dialog_btns);
|
||||
connect(dialog_btns, &QDialogButtonBox::accepted, this, &ProjectPropertiesDialog::accept);
|
||||
connect(dialog_btns, &QDialogButtonBox::rejected, this, &ProjectPropertiesDialog::reject);
|
||||
}
|
||||
|
||||
void ProjectPropertiesDialog::accept()
|
||||
{
|
||||
if (ocio_config_is_valid_) {
|
||||
// This should ripple changes throughout the program that the color config has changed, therefore must be done last
|
||||
working_project_->color_manager()->SetConfigAndDefaultInput(ocio_filename_->text(), default_input_colorspace_->currentText());
|
||||
|
||||
QDialog::accept();
|
||||
} else {
|
||||
QMessageBox::critical(this,
|
||||
tr("OpenColorIO Config Error"),
|
||||
tr("Failed to set OpenColorIO configuration: %1").arg(ocio_config_error_),
|
||||
QMessageBox::Ok);
|
||||
if (!ocio_config_is_valid_) {
|
||||
QMessageBox mb(this);
|
||||
mb.setWindowModality(Qt::WindowModal);
|
||||
mb.setIcon(QMessageBox::Critical);
|
||||
mb.setWindowTitle(tr("OpenColorIO Config Error"));
|
||||
mb.setText(tr("Failed to set OpenColorIO configuration: %1").arg(ocio_config_error_));
|
||||
mb.addButton(QMessageBox::Ok);
|
||||
mb.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cache_path_->PathIsValid(true)) {
|
||||
QMessageBox mb(this);
|
||||
mb.setWindowModality(Qt::WindowModal);
|
||||
mb.setIcon(QMessageBox::Critical);
|
||||
mb.setWindowTitle(tr("Invalid path"));
|
||||
mb.setText(tr("The cache path is invalid. Please check it and try again."));
|
||||
mb.addButton(QMessageBox::Ok);
|
||||
mb.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!proxy_path_->PathIsValid(true)) {
|
||||
QMessageBox mb(this);
|
||||
mb.setWindowModality(Qt::WindowModal);
|
||||
mb.setIcon(QMessageBox::Critical);
|
||||
mb.setWindowTitle(tr("Invalid path"));
|
||||
mb.setText(tr("The proxy path is invalid. Please check it and try again."));
|
||||
mb.addButton(QMessageBox::Ok);
|
||||
mb.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
working_project_->set_cache_path(cache_path_->path_edit()->text());
|
||||
working_project_->set_proxy_path(proxy_path_->path_edit()->text());
|
||||
|
||||
// This should ripple changes throughout the program that the color config has changed, therefore must be done last
|
||||
working_project_->color_manager()->SetConfigAndDefaultInput(ocio_filename_->text(),
|
||||
default_input_colorspace_->currentText());
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void ProjectPropertiesDialog::BrowseForOCIOConfig()
|
||||
@@ -116,7 +167,7 @@ void ProjectPropertiesDialog::BrowseForOCIOConfig()
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectPropertiesDialog::FilenameUpdated()
|
||||
void ProjectPropertiesDialog::OCIOFilenameUpdated()
|
||||
{
|
||||
default_input_colorspace_->clear();
|
||||
|
||||
@@ -150,4 +201,54 @@ void ProjectPropertiesDialog::FilenameUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
PathWidget::PathWidget(const QString &path, QWidget *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
path_edit_ = new QLineEdit();
|
||||
path_edit_->setText(path);
|
||||
connect(path_edit_, &QLineEdit::textChanged, this, &PathWidget::LineEditChanged);
|
||||
|
||||
default_box_ = new QCheckBox(tr("Default"));
|
||||
|
||||
browse_btn_ = new QPushButton(tr("Browse"));
|
||||
|
||||
connect(default_box_, &QCheckBox::toggled, this, &PathWidget::DefaultToggled);
|
||||
|
||||
default_box_->setChecked(path.isEmpty());
|
||||
|
||||
connect(browse_btn_, &QPushButton::clicked, this, &PathWidget::BrowseClicked);
|
||||
}
|
||||
|
||||
bool PathWidget::PathIsValid(bool try_to_create) const
|
||||
{
|
||||
return default_box_->isChecked()
|
||||
|| QDir(path_edit_->text()).exists()
|
||||
|| (try_to_create && QDir(path_edit_->text()).mkpath(QStringLiteral(".")));
|
||||
}
|
||||
|
||||
void PathWidget::DefaultToggled(bool e)
|
||||
{
|
||||
path_edit_->setEnabled(!e);
|
||||
}
|
||||
|
||||
void PathWidget::BrowseClicked()
|
||||
{
|
||||
QString dir = QFileDialog::getExistingDirectory(static_cast<QWidget*>(parent()),
|
||||
tr("Browse for path"),
|
||||
path_edit_->text());
|
||||
|
||||
if (!dir.isEmpty()) {
|
||||
path_edit_->setText(dir);
|
||||
}
|
||||
}
|
||||
|
||||
void PathWidget::LineEditChanged()
|
||||
{
|
||||
if (PathIsValid(false)) {
|
||||
path_edit_->setStyleSheet(QString());
|
||||
} else {
|
||||
path_edit_->setStyleSheet(QStringLiteral("QLineEdit {color: red;}"));
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -1,59 +1,102 @@
|
||||
/***
|
||||
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
***/
|
||||
|
||||
#ifndef PROJECTPROPERTIESDIALOG_H
|
||||
#define PROJECTPROPERTIESDIALOG_H
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
#include <QGridLayout>
|
||||
#include <QLineEdit>
|
||||
|
||||
#include "project/project.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class PathWidget : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PathWidget(const QString& path,
|
||||
QWidget* parent = nullptr);
|
||||
|
||||
bool PathIsValid(bool try_to_create) const;
|
||||
|
||||
QLineEdit* path_edit() const {
|
||||
return path_edit_;
|
||||
}
|
||||
|
||||
QCheckBox* default_box() const {
|
||||
return default_box_;
|
||||
}
|
||||
|
||||
QPushButton* browse_btn() const {
|
||||
return browse_btn_;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void DefaultToggled(bool e);
|
||||
|
||||
void BrowseClicked();
|
||||
|
||||
void LineEditChanged();
|
||||
|
||||
private:
|
||||
QLineEdit* path_edit_;
|
||||
|
||||
QCheckBox* default_box_;
|
||||
|
||||
QPushButton* browse_btn_;
|
||||
|
||||
};
|
||||
|
||||
class ProjectPropertiesDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ProjectPropertiesDialog(Project *p, QWidget* parent);
|
||||
|
||||
|
||||
public slots:
|
||||
virtual void accept() override;
|
||||
|
||||
|
||||
private:
|
||||
Project* working_project_;
|
||||
|
||||
|
||||
QLineEdit* ocio_filename_;
|
||||
|
||||
|
||||
QComboBox* default_input_colorspace_;
|
||||
|
||||
|
||||
bool ocio_config_is_valid_;
|
||||
|
||||
|
||||
QString ocio_config_error_;
|
||||
|
||||
PathWidget* cache_path_;
|
||||
|
||||
PathWidget* proxy_path_;
|
||||
|
||||
private slots:
|
||||
void BrowseForOCIOConfig();
|
||||
|
||||
void FilenameUpdated();
|
||||
|
||||
|
||||
void OCIOFilenameUpdated();
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -22,8 +22,7 @@
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
AudioStream::AudioStream() :
|
||||
index_done_(false)
|
||||
AudioStream::AudioStream()
|
||||
{
|
||||
set_type(kAudio);
|
||||
}
|
||||
@@ -65,68 +64,32 @@ void AudioStream::set_sample_rate(const int &sample_rate)
|
||||
sample_rate_ = sample_rate;
|
||||
}
|
||||
|
||||
const rational &AudioStream::index_length()
|
||||
bool AudioStream::try_start_conforming(const AudioRenderingParams ¶ms)
|
||||
{
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
QMutexLocker locker(proxy_access_lock());
|
||||
|
||||
return index_length_;
|
||||
}
|
||||
|
||||
void AudioStream::set_index_length(const rational &index_length)
|
||||
{
|
||||
{
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
|
||||
index_length_ = index_length;
|
||||
}
|
||||
|
||||
emit IndexChanged();
|
||||
}
|
||||
|
||||
const bool &AudioStream::index_done()
|
||||
{
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
|
||||
return index_done_;
|
||||
}
|
||||
|
||||
void AudioStream::set_index_done(const bool& index_done)
|
||||
{
|
||||
{
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
|
||||
index_done_ = index_done;
|
||||
}
|
||||
|
||||
emit IndexChanged();
|
||||
}
|
||||
|
||||
void AudioStream::clear_index()
|
||||
{
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
|
||||
index_done_ = false;
|
||||
index_length_ = 0;
|
||||
}
|
||||
|
||||
bool AudioStream::has_conformed_version(const AudioRenderingParams ¶ms)
|
||||
{
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
|
||||
foreach (const AudioRenderingParams& p, conformed_) {
|
||||
if (p == params) {
|
||||
return true;
|
||||
}
|
||||
if (!currently_conforming_.contains(params)
|
||||
&& !conformed_.contains(params)) {
|
||||
currently_conforming_.append(params);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioStream::has_conformed_version(const AudioRenderingParams ¶ms)
|
||||
{
|
||||
QMutexLocker locker(proxy_access_lock());
|
||||
|
||||
return conformed_.contains(params);
|
||||
}
|
||||
|
||||
void AudioStream::append_conformed_version(const AudioRenderingParams ¶ms)
|
||||
{
|
||||
{
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
QMutexLocker locker(proxy_access_lock());
|
||||
|
||||
currently_conforming_.removeOne(params);
|
||||
conformed_.append(params);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,30 +49,21 @@ public:
|
||||
const int& sample_rate() const;
|
||||
void set_sample_rate(const int& sample_rate);
|
||||
|
||||
const rational& index_length();
|
||||
void set_index_length(const rational& index_length);
|
||||
|
||||
const bool& index_done();
|
||||
void set_index_done(const bool &index_done);
|
||||
|
||||
void clear_index();
|
||||
|
||||
bool try_start_conforming(const AudioRenderingParams& params);
|
||||
bool has_conformed_version(const AudioRenderingParams& params);
|
||||
void append_conformed_version(const AudioRenderingParams& params);
|
||||
|
||||
signals:
|
||||
void ConformAppended(const AudioRenderingParams& params);
|
||||
void ConformAppended(OLIVE_NAMESPACE::AudioRenderingParams params);
|
||||
|
||||
private:
|
||||
int channels_;
|
||||
uint64_t layout_;
|
||||
int sample_rate_;
|
||||
|
||||
QMutex index_access_lock_;
|
||||
rational index_length_;
|
||||
bool index_done_;
|
||||
QList<AudioRenderingParams> conformed_;
|
||||
|
||||
QVector<AudioRenderingParams> conformed_;
|
||||
QList<AudioRenderingParams> currently_conforming_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -304,11 +304,11 @@ void Footage::ClearStreams()
|
||||
streams_.clear();
|
||||
}
|
||||
|
||||
bool Footage::HasStreamsOfType(const Stream::Type type)
|
||||
bool Footage::HasStreamsOfType(const Stream::Type &type) const
|
||||
{
|
||||
// Return true if any streams are video streams
|
||||
for (int i=0;i<streams_.size();i++) {
|
||||
if (streams_.at(i)->type() == type) {
|
||||
foreach (StreamPtr stream, streams_) {
|
||||
if (stream->type() == type) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -316,6 +316,17 @@ bool Footage::HasStreamsOfType(const Stream::Type type)
|
||||
return false;
|
||||
}
|
||||
|
||||
StreamPtr Footage::get_first_stream_of_type(const Stream::Type &type) const
|
||||
{
|
||||
foreach (StreamPtr stream, streams_) {
|
||||
if (stream->type() == type) {
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Footage::UpdateTooltip()
|
||||
{
|
||||
switch (status_) {
|
||||
|
||||
@@ -206,12 +206,6 @@ public:
|
||||
|
||||
quint64 get_enabled_stream_flags() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Internal function to delete all Stream children and empty the array
|
||||
*/
|
||||
void ClearStreams();
|
||||
|
||||
/**
|
||||
* @brief Check if this footage has streams of a certain type
|
||||
*
|
||||
@@ -219,7 +213,15 @@ private:
|
||||
*
|
||||
* The stream type to check for
|
||||
*/
|
||||
bool HasStreamsOfType(const Stream::Type type);
|
||||
bool HasStreamsOfType(const Stream::Type& type) const;
|
||||
|
||||
StreamPtr get_first_stream_of_type(const Stream::Type& type) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Internal function to delete all Stream children and empty the array
|
||||
*/
|
||||
void ClearStreams();
|
||||
|
||||
/**
|
||||
* @brief Update the icon based on the Footage status
|
||||
|
||||
@@ -139,14 +139,9 @@ QIcon Stream::IconFromType(const Stream::Type &type)
|
||||
return QIcon();
|
||||
}
|
||||
|
||||
/*StreamID Stream::ToID() const
|
||||
QMutex *Stream::proxy_access_lock()
|
||||
{
|
||||
return StreamID(footage_->filename(), index_);
|
||||
}*/
|
||||
|
||||
QMutex* Stream::index_process_lock()
|
||||
{
|
||||
return &index_process_lock_;
|
||||
return &proxy_access_lock_;
|
||||
}
|
||||
|
||||
void Stream::FootageSetEvent(Footage*)
|
||||
@@ -162,10 +157,4 @@ void Stream::SaveCustomParameters(QXmlStreamWriter*) const
|
||||
{
|
||||
}
|
||||
|
||||
/*StreamID::StreamID(const QString &filename, const int &stream_index) :
|
||||
filename_(filename),
|
||||
stream_index_(stream_index)
|
||||
{
|
||||
}*/
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -33,17 +33,6 @@ OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class Footage;
|
||||
|
||||
/*class StreamID {
|
||||
public:
|
||||
StreamID(const QString& filename, const int& stream_index);
|
||||
|
||||
private:
|
||||
QString filename_;
|
||||
|
||||
int stream_index_;
|
||||
|
||||
};*/
|
||||
|
||||
/**
|
||||
* @brief A base class for keeping metadata about a media stream.
|
||||
*
|
||||
@@ -103,9 +92,7 @@ public:
|
||||
|
||||
static QIcon IconFromType(const Type& type);
|
||||
|
||||
//StreamID ToID() const;
|
||||
|
||||
QMutex* index_process_lock();
|
||||
QMutex* proxy_access_lock();
|
||||
|
||||
protected:
|
||||
virtual void FootageSetEvent(Footage*);
|
||||
@@ -115,8 +102,6 @@ protected:
|
||||
virtual void SaveCustomParameters(QXmlStreamWriter* writer) const;
|
||||
|
||||
signals:
|
||||
void IndexChanged();
|
||||
|
||||
void ParametersChanged();
|
||||
|
||||
private:
|
||||
@@ -132,7 +117,7 @@ private:
|
||||
|
||||
bool enabled_;
|
||||
|
||||
QMutex index_process_lock_;
|
||||
QMutex proxy_access_lock_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
const int64_t VideoStream::kEndTimestamp = AV_NOPTS_VALUE;
|
||||
|
||||
VideoStream::VideoStream() :
|
||||
start_time_(0),
|
||||
is_image_sequence_(false)
|
||||
is_image_sequence_(false),
|
||||
is_generating_proxy_(false),
|
||||
using_proxy_(0)
|
||||
{
|
||||
set_type(kVideo);
|
||||
}
|
||||
@@ -73,6 +73,42 @@ void VideoStream::set_image_sequence(bool e)
|
||||
is_image_sequence_ = e;
|
||||
}
|
||||
|
||||
bool VideoStream::is_generating_proxy()
|
||||
{
|
||||
QMutexLocker locker(proxy_access_lock());
|
||||
|
||||
return is_generating_proxy_;
|
||||
}
|
||||
|
||||
bool VideoStream::try_start_proxy()
|
||||
{
|
||||
QMutexLocker locker(proxy_access_lock());
|
||||
|
||||
if (is_generating_proxy_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
is_generating_proxy_ = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int VideoStream::using_proxy()
|
||||
{
|
||||
QMutexLocker locker(proxy_access_lock());
|
||||
|
||||
return using_proxy_;
|
||||
}
|
||||
|
||||
void VideoStream::set_proxy(const int ÷r, const QVector<int64_t> &index)
|
||||
{
|
||||
QMutexLocker locker(proxy_access_lock());
|
||||
|
||||
using_proxy_ = divider;
|
||||
frame_index_ = index;
|
||||
is_generating_proxy_ = false;
|
||||
}
|
||||
|
||||
int64_t VideoStream::get_closest_timestamp_in_frame_index(const rational &time)
|
||||
{
|
||||
// Get rough approximation of what the timestamp would be in this timebase
|
||||
@@ -84,45 +120,31 @@ int64_t VideoStream::get_closest_timestamp_in_frame_index(const rational &time)
|
||||
|
||||
int64_t VideoStream::get_closest_timestamp_in_frame_index(int64_t timestamp)
|
||||
{
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
QMutexLocker locker(proxy_access_lock());
|
||||
|
||||
if (frame_index_.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
if (!frame_index_.isEmpty()) {
|
||||
if (timestamp <= frame_index_.first()) {
|
||||
return frame_index_.first();
|
||||
} else if (timestamp >= frame_index_.last()) {
|
||||
return frame_index_.last();
|
||||
} else {
|
||||
// Use index to find closest frame in file
|
||||
for (int i=1;i<frame_index_.size();i++) {
|
||||
int64_t this_ts = frame_index_.at(i);
|
||||
|
||||
// Adjust target by stream's start time
|
||||
timestamp += start_time_;
|
||||
|
||||
if (timestamp <= 0) {
|
||||
return frame_index_.first();
|
||||
}
|
||||
|
||||
int index_size = frame_index_.size();
|
||||
|
||||
if (frame_index_.last() == kEndTimestamp) {
|
||||
index_size--;
|
||||
}
|
||||
|
||||
// Use index to find closest frame in file
|
||||
for (int i=0;i<index_size;i++) {
|
||||
int64_t this_ts = frame_index_.at(i);
|
||||
|
||||
if (this_ts == timestamp) {
|
||||
return timestamp;
|
||||
} else if (this_ts > timestamp) {
|
||||
return frame_index_.at(i - 1);
|
||||
if (this_ts == timestamp) {
|
||||
return timestamp;
|
||||
} else if (this_ts > timestamp) {
|
||||
return frame_index_.at(i - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (frame_index_.last() == kEndTimestamp) {
|
||||
// Index is done
|
||||
return frame_index_.last();
|
||||
} else {
|
||||
// Index is not done yet
|
||||
return -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
void VideoStream::clear_frame_index()
|
||||
{
|
||||
{
|
||||
@@ -204,5 +226,6 @@ bool VideoStream::save_frame_index(const QString &s)
|
||||
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -31,8 +31,6 @@ class VideoStream : public ImageStream
|
||||
public:
|
||||
VideoStream();
|
||||
|
||||
static const int64_t kEndTimestamp;
|
||||
|
||||
virtual QString description() const override;
|
||||
|
||||
/**
|
||||
@@ -51,6 +49,7 @@ public:
|
||||
|
||||
int64_t get_closest_timestamp_in_frame_index(const rational& time);
|
||||
int64_t get_closest_timestamp_in_frame_index(int64_t timestamp);
|
||||
/*
|
||||
void clear_frame_index();
|
||||
void append_frame_index(const int64_t& ts);
|
||||
bool is_frame_index_ready();
|
||||
@@ -58,6 +57,12 @@ public:
|
||||
|
||||
bool load_frame_index(const QString& s);
|
||||
bool save_frame_index(const QString& s);
|
||||
*/
|
||||
|
||||
bool is_generating_proxy();
|
||||
bool try_start_proxy();
|
||||
int using_proxy();
|
||||
void set_proxy(const int& divider, const QVector<int64_t>& index);
|
||||
|
||||
private:
|
||||
rational frame_rate_;
|
||||
@@ -66,10 +71,12 @@ private:
|
||||
|
||||
int64_t start_time_;
|
||||
|
||||
QMutex index_access_lock_;
|
||||
|
||||
bool is_image_sequence_;
|
||||
|
||||
bool is_generating_proxy_;
|
||||
|
||||
int using_proxy_;
|
||||
|
||||
};
|
||||
|
||||
using VideoStreamPtr = std::shared_ptr<VideoStream>;
|
||||
|
||||
@@ -70,6 +70,22 @@ public:
|
||||
|
||||
bool is_new() const;
|
||||
|
||||
const QString& cache_path() const {
|
||||
return cache_path_;
|
||||
}
|
||||
|
||||
void set_cache_path(const QString& cache_path) {
|
||||
cache_path_ = cache_path;
|
||||
}
|
||||
|
||||
const QString& proxy_path() const {
|
||||
return proxy_path_;
|
||||
}
|
||||
|
||||
void set_proxy_path(const QString& proxy_path) {
|
||||
proxy_path_ = proxy_path;
|
||||
}
|
||||
|
||||
signals:
|
||||
void NameChanged();
|
||||
|
||||
@@ -86,6 +102,10 @@ private:
|
||||
|
||||
bool autorecovery_saved_;
|
||||
|
||||
QString cache_path_;
|
||||
|
||||
QString proxy_path_;
|
||||
|
||||
};
|
||||
|
||||
using ProjectPtr = std::shared_ptr<Project>;
|
||||
|
||||
@@ -34,8 +34,6 @@ set(OLIVE_SOURCES
|
||||
render/backend/audiorenderbackend.cpp
|
||||
render/backend/audiorenderworker.h
|
||||
render/backend/audiorenderworker.cpp
|
||||
render/backend/indexmanager.h
|
||||
render/backend/indexmanager.cpp
|
||||
|
||||
render/backend/videorenderbackend.h
|
||||
render/backend/videorenderbackend.cpp
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
|
||||
#include "audiorenderworker.h"
|
||||
#include "common/filefunctions.h"
|
||||
#include "render/backend/indexmanager.h"
|
||||
#include "task/conform/conform.h"
|
||||
#include "task/taskmanager.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
@@ -33,7 +34,6 @@ AudioRenderBackend::AudioRenderBackend(QObject *parent) :
|
||||
RenderBackend(parent),
|
||||
ic_from_conform_(false)
|
||||
{
|
||||
connect(IndexManager::instance(), &IndexManager::StreamConformAppended, this, &AudioRenderBackend::ConformUpdated);
|
||||
connect(this, &AudioRenderBackend::QueueComplete, this, &AudioRenderBackend::FilterQueueCompleteSignal);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,8 @@ void AudioRenderBackend::DisconnectViewer(ViewerOutput *node)
|
||||
{
|
||||
disconnect(node, &ViewerOutput::AudioChangedBetween, this, &AudioRenderBackend::InvalidateCache);
|
||||
disconnect(node, &ViewerOutput::LengthChanged, this, &AudioRenderBackend::TruncateCache);
|
||||
|
||||
conform_wait_info_.clear();
|
||||
}
|
||||
|
||||
bool AudioRenderBackend::GenerateCacheIDInternal(QCryptographicHash &hash)
|
||||
@@ -154,6 +156,30 @@ void AudioRenderBackend::InvalidateCacheInternal(const rational &start_range, co
|
||||
RenderBackend::InvalidateCacheInternal(start_range, end_range);
|
||||
}
|
||||
|
||||
void AudioRenderBackend::ListenForConformSignal(AudioStreamPtr s)
|
||||
{
|
||||
foreach (const ConformWaitInfo& info, conform_wait_info_) {
|
||||
if (info.stream == s) {
|
||||
// We've probably already connected to this one
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
connect(s.get(), &AudioStream::ConformAppended, this, &AudioRenderBackend::ConformUpdated);
|
||||
}
|
||||
|
||||
void AudioRenderBackend::StopListeningForConformSignal(AudioStream* s)
|
||||
{
|
||||
foreach (const ConformWaitInfo& info, conform_wait_info_) {
|
||||
if (info.stream.get() == s) {
|
||||
// There are still conforms we're waiting for, don't disconnect
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
disconnect(s, &AudioStream::ConformAppended, this, &AudioRenderBackend::ConformUpdated);
|
||||
}
|
||||
|
||||
void AudioRenderBackend::ConformUnavailable(StreamPtr stream, TimeRange range, rational stream_time, AudioRenderingParams params)
|
||||
{
|
||||
ConformWaitInfo info = {stream, params, range, stream_time};
|
||||
@@ -164,26 +190,38 @@ void AudioRenderBackend::ConformUnavailable(StreamPtr stream, TimeRange range, r
|
||||
|
||||
AudioStreamPtr audio_stream = std::static_pointer_cast<AudioStream>(stream);
|
||||
|
||||
if (IndexManager::instance()->IsConforming(audio_stream, params)) {
|
||||
if (audio_stream->try_start_conforming(params)) {
|
||||
|
||||
// Start indexing process
|
||||
ListenForConformSignal(audio_stream);
|
||||
|
||||
conform_wait_info_.append(info);
|
||||
|
||||
ConformTask* conform_task = new ConformTask(audio_stream, params);
|
||||
|
||||
TaskManager::instance()->AddTask(conform_task);
|
||||
|
||||
} else if (audio_stream->has_conformed_version(params)) {
|
||||
|
||||
// Index JUST finished, requeue this time
|
||||
// Conform JUST finished, requeue this time
|
||||
ic_from_conform_ = true;
|
||||
InvalidateCache(range, nullptr);
|
||||
ic_from_conform_ = false;
|
||||
|
||||
} else {
|
||||
|
||||
// Start indexing process
|
||||
// A conform task is already running, so we'll just wait for it
|
||||
ListenForConformSignal(audio_stream);
|
||||
|
||||
conform_wait_info_.append(info);
|
||||
IndexManager::instance()->StartConformingStream(audio_stream, params);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void AudioRenderBackend::ConformUpdated(Stream *stream, AudioRenderingParams params)
|
||||
void AudioRenderBackend::ConformUpdated(AudioRenderingParams params)
|
||||
{
|
||||
AudioStream *stream = static_cast<AudioStream*>(sender());
|
||||
|
||||
for (int i=0;i<conform_wait_info_.size();i++) {
|
||||
const ConformWaitInfo& info = conform_wait_info_.at(i);
|
||||
|
||||
@@ -204,6 +242,8 @@ void AudioRenderBackend::ConformUpdated(Stream *stream, AudioRenderingParams par
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
StopListeningForConformSignal(stream);
|
||||
}
|
||||
|
||||
void AudioRenderBackend::TruncateCache(const rational &r)
|
||||
|
||||
@@ -79,6 +79,10 @@ private:
|
||||
bool operator==(const ConformWaitInfo& rhs) const;
|
||||
};
|
||||
|
||||
void ListenForConformSignal(AudioStreamPtr s);
|
||||
|
||||
void StopListeningForConformSignal(AudioStream *s);
|
||||
|
||||
QList<ConformWaitInfo> conform_wait_info_;
|
||||
|
||||
AudioRenderingParams params_;
|
||||
@@ -88,7 +92,7 @@ private:
|
||||
private slots:
|
||||
void ConformUnavailable(StreamPtr stream, TimeRange range, rational stream_time, AudioRenderingParams params);
|
||||
|
||||
void ConformUpdated(Stream *stream, AudioRenderingParams params);
|
||||
void ConformUpdated(OLIVE_NAMESPACE::AudioRenderingParams params);
|
||||
|
||||
void TruncateCache(const rational& r);
|
||||
|
||||
|
||||
@@ -112,7 +112,14 @@ NodeValueTable AudioRenderWorker::RenderBlock(const TrackOutput *track, const Ti
|
||||
AudioRenderingParams waveform_params(SampleSummer::kSumSampleRate, audio_params_.channel_layout(), SampleFormat::SAMPLE_FMT_S32);
|
||||
int chunk_size = (audio_params().sample_rate() / waveform_params.sample_rate());
|
||||
|
||||
qint64 start_offset = waveform_params.time_to_bytes(range_for_block.in() - b->in());
|
||||
{
|
||||
// Write metadata header
|
||||
SampleSummer::Info info;
|
||||
info.channels = audio_params_.channel_count();
|
||||
wave_file.write(reinterpret_cast<char*>(&info), sizeof(SampleSummer::Info));
|
||||
}
|
||||
|
||||
qint64 start_offset = sizeof(SampleSummer::Info) + waveform_params.time_to_bytes(range_for_block.in() - b->in());
|
||||
qint64 length_offset = waveform_params.time_to_bytes(range_for_block.length());
|
||||
qint64 end_offset = start_offset + length_offset;
|
||||
|
||||
@@ -133,17 +140,6 @@ NodeValueTable AudioRenderWorker::RenderBlock(const TrackOutput *track, const Ti
|
||||
|
||||
wave_file.close();
|
||||
|
||||
// Write metadata about this waveform file
|
||||
QFile wave_metadata(wave_fn.append(QStringLiteral(".meta")));
|
||||
if (wave_metadata.open(QFile::WriteOnly)) {
|
||||
SampleSummer::Info info;
|
||||
info.channels = audio_params_.channel_count();
|
||||
|
||||
wave_metadata.write(reinterpret_cast<char*>(&info), sizeof(SampleSummer::Info));
|
||||
|
||||
wave_metadata.close();
|
||||
}
|
||||
|
||||
if (src_block->type() == Block::kClip) {
|
||||
emit static_cast<ClipBlock*>(src_block)->PreviewUpdated();
|
||||
}
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "indexmanager.h"
|
||||
|
||||
#include "task/taskmanager.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
IndexManager* IndexManager::instance_ = nullptr;
|
||||
|
||||
void IndexManager::CreateInstance()
|
||||
{
|
||||
instance_ = new IndexManager();
|
||||
}
|
||||
|
||||
IndexManager *IndexManager::instance()
|
||||
{
|
||||
return instance_;
|
||||
}
|
||||
|
||||
void IndexManager::DestroyInstance()
|
||||
{
|
||||
delete instance_;
|
||||
instance_ = nullptr;
|
||||
}
|
||||
|
||||
void IndexManager::StartIndexingStream(StreamPtr stream)
|
||||
{
|
||||
if (IsIndexing(stream)) {
|
||||
return;
|
||||
}
|
||||
|
||||
IndexTask* index_task = new IndexTask(stream);
|
||||
indexing_.append({stream, index_task});
|
||||
|
||||
connect(stream.get(), &Stream::IndexChanged, this, &IndexManager::StreamIndexUpdatedEvent, Qt::QueuedConnection);
|
||||
connect(index_task, &IndexTask::Succeeded, this, &IndexManager::IndexTaskFinished, Qt::QueuedConnection);
|
||||
|
||||
TaskManager::instance()->AddTask(index_task);
|
||||
}
|
||||
|
||||
void IndexManager::StartConformingStream(AudioStreamPtr stream, AudioRenderingParams params)
|
||||
{
|
||||
if (IsConforming(stream, params)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConformTask* conform_task = new ConformTask(stream, params);
|
||||
conforming_.append({stream, params, conform_task});
|
||||
|
||||
connect(stream.get(), &AudioStream::ConformAppended, this, &IndexManager::StreamConformAppendedEvent, Qt::QueuedConnection);
|
||||
connect(conform_task, &ConformTask::Succeeded, this, &IndexManager::IndexTaskFinished, Qt::QueuedConnection);
|
||||
|
||||
TaskManager::instance()->AddTask(conform_task);
|
||||
}
|
||||
|
||||
bool IndexManager::IsIndexing(StreamPtr stream) const
|
||||
{
|
||||
foreach (const IndexPair& stp, indexing_) {
|
||||
if (stp.stream == stream) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IndexManager::IsConforming(AudioStreamPtr stream, const AudioRenderingParams ¶ms) const
|
||||
{
|
||||
foreach (const ConformPair& cfp, conforming_) {
|
||||
if (cfp.stream == stream && cfp.params == params) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void IndexManager::IndexTaskFinished()
|
||||
{
|
||||
for (int i=0;i<indexing_.size();i++) {
|
||||
const IndexPair& stp = indexing_.at(i);
|
||||
|
||||
if (stp.task == sender()) {
|
||||
indexing_.removeAt(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IndexManager::StreamIndexUpdatedEvent()
|
||||
{
|
||||
emit StreamIndexUpdated(static_cast<Stream*>(sender()));
|
||||
}
|
||||
|
||||
void IndexManager::StreamConformAppendedEvent(const AudioRenderingParams ¶ms)
|
||||
{
|
||||
emit StreamConformAppended(static_cast<Stream*>(sender()), params);
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
@@ -1,82 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef INDEXMANAGER_H
|
||||
#define INDEXMANAGER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "project/item/footage/stream.h"
|
||||
#include "task/conform/conform.h"
|
||||
#include "task/index/index.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class IndexManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
IndexManager() = default;
|
||||
|
||||
static void CreateInstance();
|
||||
static IndexManager* instance();
|
||||
static void DestroyInstance();
|
||||
|
||||
bool IsIndexing(StreamPtr stream) const;
|
||||
bool IsConforming(AudioStreamPtr stream, const AudioRenderingParams& params) const;
|
||||
|
||||
public slots:
|
||||
void StartIndexingStream(OLIVE_NAMESPACE::StreamPtr stream);
|
||||
void StartConformingStream(OLIVE_NAMESPACE::AudioStreamPtr stream, OLIVE_NAMESPACE::AudioRenderingParams params);
|
||||
|
||||
signals:
|
||||
void StreamIndexUpdated(Stream* stream);
|
||||
void StreamConformAppended(Stream* stream, OLIVE_NAMESPACE::AudioRenderingParams params);
|
||||
|
||||
private:
|
||||
static IndexManager* instance_;
|
||||
|
||||
struct IndexPair {
|
||||
StreamPtr stream;
|
||||
IndexTask* task;
|
||||
};
|
||||
|
||||
struct ConformPair {
|
||||
StreamPtr stream;
|
||||
AudioRenderingParams params;
|
||||
ConformTask* task;
|
||||
};
|
||||
|
||||
QList<IndexPair> indexing_;
|
||||
|
||||
QList<ConformPair> conforming_;
|
||||
|
||||
private slots:
|
||||
void IndexTaskFinished();
|
||||
|
||||
void StreamIndexUpdatedEvent();
|
||||
|
||||
void StreamConformAppendedEvent(const AudioRenderingParams& params);
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // INDEXMANAGER_H
|
||||
@@ -130,19 +130,19 @@ void OpenGLProxy::FrameToValue(FramePtr frame, StreamPtr stream, NodeValueTable*
|
||||
}
|
||||
}
|
||||
|
||||
VideoRenderingParams footage_params(frame->width(), frame->height(), frame->format());
|
||||
|
||||
footage_tex_ref = texture_cache_.Get(ctx_, footage_params, frame);
|
||||
footage_tex_ref = texture_cache_.Get(ctx_, frame);
|
||||
|
||||
if (ocio_method == ColorManager::kOCIOFast) {
|
||||
if (!color_processor->IsEnabled()) {
|
||||
color_processor->Enable(ctx_, video_stream->premultiplied_alpha());
|
||||
}
|
||||
|
||||
VideoRenderingParams frame_params = frame->video_params();
|
||||
|
||||
// Check frame aspect ratio
|
||||
if (frame->sample_aspect_ratio() != 1 && frame->sample_aspect_ratio() != 0) {
|
||||
int new_width = frame->width();
|
||||
int new_height = frame->height();
|
||||
int new_width = frame_params.width();
|
||||
int new_height = frame_params.height();
|
||||
|
||||
// Scale the frame in a way that does not reduce the resolution
|
||||
if (frame->sample_aspect_ratio() > 1) {
|
||||
@@ -153,14 +153,16 @@ void OpenGLProxy::FrameToValue(FramePtr frame, StreamPtr stream, NodeValueTable*
|
||||
new_height = qRound(static_cast<double>(new_height) / frame->sample_aspect_ratio().toDouble());
|
||||
}
|
||||
|
||||
footage_params = VideoRenderingParams(new_width,
|
||||
new_height,
|
||||
footage_params.format());
|
||||
frame_params = VideoRenderingParams(new_width,
|
||||
new_height,
|
||||
frame_params.format(),
|
||||
frame_params.divider());
|
||||
}
|
||||
|
||||
VideoRenderingParams dest_params(footage_params.width(),
|
||||
footage_params.height(),
|
||||
video_params_.format());
|
||||
VideoRenderingParams dest_params(frame_params.width(),
|
||||
frame_params.height(),
|
||||
video_params_.format(),
|
||||
frame_params.divider());
|
||||
|
||||
// Create destination texture
|
||||
OpenGLTextureCache::ReferencePtr associated_tex_ref = texture_cache_.Get(ctx_, dest_params);
|
||||
@@ -329,8 +331,8 @@ void OpenGLProxy::RunNodeAccelerated(const Node *node, const TimeRange &range, N
|
||||
int res_param_location = shader->uniformLocation(QStringLiteral("%1_resolution").arg(input->id()));
|
||||
if (res_param_location > -1) {
|
||||
shader->setUniformValue(res_param_location,
|
||||
static_cast<GLfloat>(texture->texture()->width() * video_params_.divider()),
|
||||
static_cast<GLfloat>(texture->texture()->height() * video_params_.divider()));
|
||||
static_cast<GLfloat>(texture->texture()->width() * texture->texture()->divider()),
|
||||
static_cast<GLfloat>(texture->texture()->height() * texture->texture()->divider()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ OpenGLShaderPtr OpenGLShader::CreateDefault(const QString &function_name, const
|
||||
}
|
||||
|
||||
// copied from source code to OCIODisplay
|
||||
const int OCIO_LUT3D_EDGE_SIZE = 32;
|
||||
const int OCIO_LUT3D_EDGE_SIZE = 64;
|
||||
|
||||
// copied from source code to OCIODisplay, expanded from 3*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE
|
||||
const int OCIO_NUM_3D_ENTRIES = 3*OCIO_LUT3D_EDGE_SIZE*OCIO_LUT3D_EDGE_SIZE*OCIO_LUT3D_EDGE_SIZE;
|
||||
|
||||
@@ -31,10 +31,7 @@ OLIVE_NAMESPACE_ENTER
|
||||
|
||||
OpenGLTexture::OpenGLTexture() :
|
||||
created_ctx_(nullptr),
|
||||
texture_(0),
|
||||
width_(0),
|
||||
height_(0),
|
||||
format_(PixelFormat::PIX_FMT_INVALID)
|
||||
texture_(0)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -48,7 +45,7 @@ bool OpenGLTexture::IsCreated() const
|
||||
return (texture_);
|
||||
}
|
||||
|
||||
void OpenGLTexture::Create(QOpenGLContext *ctx, int width, int height, const PixelFormat::Format &format, const void* data, int linesize)
|
||||
void OpenGLTexture::Create(QOpenGLContext *ctx, const VideoRenderingParams ¶ms, const void* data, int linesize)
|
||||
{
|
||||
if (!ctx) {
|
||||
qWarning() << "OpenGLTexture::Create was passed an invalid context";
|
||||
@@ -58,9 +55,7 @@ void OpenGLTexture::Create(QOpenGLContext *ctx, int width, int height, const Pix
|
||||
Destroy();
|
||||
|
||||
created_ctx_ = ctx;
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
format_ = format;
|
||||
params_ = params;
|
||||
|
||||
connect(created_ctx_, SIGNAL(aboutToBeDestroyed()), this, SLOT(Destroy()), Qt::DirectConnection);
|
||||
|
||||
@@ -68,9 +63,9 @@ void OpenGLTexture::Create(QOpenGLContext *ctx, int width, int height, const Pix
|
||||
CreateInternal(created_ctx_, &texture_, data, linesize);
|
||||
}
|
||||
|
||||
void OpenGLTexture::Create(QOpenGLContext *ctx, int width, int height, const PixelFormat::Format &format)
|
||||
void OpenGLTexture::Create(QOpenGLContext *ctx, const VideoRenderingParams ¶ms)
|
||||
{
|
||||
Create(ctx, width, height, format, nullptr, 0);
|
||||
Create(ctx, params, nullptr, 0);
|
||||
}
|
||||
|
||||
void OpenGLTexture::Create(QOpenGLContext *ctx, FramePtr frame)
|
||||
@@ -80,7 +75,7 @@ void OpenGLTexture::Create(QOpenGLContext *ctx, FramePtr frame)
|
||||
|
||||
void OpenGLTexture::Create(QOpenGLContext *ctx, Frame *frame)
|
||||
{
|
||||
Create(ctx, frame->width(), frame->height(), frame->format(), frame->data(), frame->linesize_pixels());
|
||||
Create(ctx, frame->video_params(), frame->data(), frame->linesize_pixels());
|
||||
}
|
||||
|
||||
void OpenGLTexture::Destroy()
|
||||
@@ -107,17 +102,17 @@ void OpenGLTexture::Release()
|
||||
|
||||
const int &OpenGLTexture::width() const
|
||||
{
|
||||
return width_;
|
||||
return params_.effective_width();
|
||||
}
|
||||
|
||||
const int &OpenGLTexture::height() const
|
||||
{
|
||||
return height_;
|
||||
return params_.effective_height();
|
||||
}
|
||||
|
||||
const PixelFormat::Format &OpenGLTexture::format() const
|
||||
{
|
||||
return format_;
|
||||
return params_.format();
|
||||
}
|
||||
|
||||
const GLuint &OpenGLTexture::texture() const
|
||||
@@ -125,6 +120,11 @@ const GLuint &OpenGLTexture::texture() const
|
||||
return texture_;
|
||||
}
|
||||
|
||||
const int &OpenGLTexture::divider() const
|
||||
{
|
||||
return params_.divider();
|
||||
}
|
||||
|
||||
void OpenGLTexture::Upload(FramePtr frame)
|
||||
{
|
||||
Upload(frame.get());
|
||||
@@ -150,10 +150,10 @@ void OpenGLTexture::Upload(const void *data, int linesize)
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
width_,
|
||||
height_,
|
||||
OpenGLRenderFunctions::GetPixelFormat(format_),
|
||||
OpenGLRenderFunctions::GetPixelType(format_),
|
||||
width(),
|
||||
height(),
|
||||
OpenGLRenderFunctions::GetPixelFormat(format()),
|
||||
OpenGLRenderFunctions::GetPixelType(format()),
|
||||
data);
|
||||
|
||||
created_ctx_->functions()->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
@@ -183,12 +183,12 @@ void OpenGLTexture::CreateInternal(QOpenGLContext* create_ctx, GLuint* tex, cons
|
||||
// Allocate storage for texture
|
||||
f->glTexImage2D(GL_TEXTURE_2D,
|
||||
0,
|
||||
OpenGLRenderFunctions::GetInternalFormat(format_),
|
||||
width_,
|
||||
height_,
|
||||
OpenGLRenderFunctions::GetInternalFormat(format()),
|
||||
width(),
|
||||
height(),
|
||||
0,
|
||||
OpenGLRenderFunctions::GetPixelFormat(format_),
|
||||
OpenGLRenderFunctions::GetPixelType(format_),
|
||||
OpenGLRenderFunctions::GetPixelFormat(format()),
|
||||
OpenGLRenderFunctions::GetPixelType(format()),
|
||||
data);
|
||||
|
||||
// Return linesize to default
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/***
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
@@ -41,8 +41,8 @@ public:
|
||||
|
||||
DISABLE_COPY_MOVE(OpenGLTexture)
|
||||
|
||||
void Create(QOpenGLContext* ctx, int width, int height, const PixelFormat::Format &format, const void *data, int linesize);
|
||||
void Create(QOpenGLContext* ctx, int width, int height, const PixelFormat::Format &format);
|
||||
void Create(QOpenGLContext* ctx, const VideoRenderingParams& params, const void *data, int linesize);
|
||||
void Create(QOpenGLContext* ctx, const VideoRenderingParams& params);
|
||||
void Create(QOpenGLContext* ctx, FramePtr frame);
|
||||
void Create(QOpenGLContext* ctx, Frame* frame);
|
||||
|
||||
@@ -60,6 +60,8 @@ public:
|
||||
|
||||
const GLuint& texture() const;
|
||||
|
||||
const int& divider() const;
|
||||
|
||||
void Upload(FramePtr frame);
|
||||
void Upload(Frame* frame);
|
||||
void Upload(const void *data, int linesize);
|
||||
@@ -74,11 +76,7 @@ private:
|
||||
|
||||
GLuint texture_;
|
||||
|
||||
int width_;
|
||||
|
||||
int height_;
|
||||
|
||||
PixelFormat::Format format_;
|
||||
VideoRenderingParams params_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -29,14 +29,14 @@ OpenGLTextureCache::~OpenGLTextureCache()
|
||||
}
|
||||
}
|
||||
|
||||
OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext *ctx, const VideoRenderingParams ¶ms, FramePtr frame)
|
||||
OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext *ctx, FramePtr frame)
|
||||
{
|
||||
return Get(ctx, params, frame.get());
|
||||
return Get(ctx, frame.get());
|
||||
}
|
||||
|
||||
OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext *ctx, const VideoRenderingParams ¶ms, Frame *frame)
|
||||
OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext *ctx, Frame *frame)
|
||||
{
|
||||
return Get(ctx, params, frame->data(), frame->linesize_pixels());
|
||||
return Get(ctx, frame->video_params(), frame->data(), frame->linesize_pixels());
|
||||
}
|
||||
|
||||
OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext* ctx, const VideoRenderingParams ¶ms, const void *data, int linesize)
|
||||
@@ -61,7 +61,7 @@ OpenGLTextureCache::ReferencePtr OpenGLTextureCache::Get(QOpenGLContext* ctx, co
|
||||
// If we didn't find a texture, we'll need to create one
|
||||
if (!texture) {
|
||||
texture = std::make_shared<OpenGLTexture>();
|
||||
texture->Create(ctx, params.effective_width(), params.effective_height(), params.format());
|
||||
texture->Create(ctx, params);
|
||||
}
|
||||
|
||||
ReferencePtr ref = std::make_shared<Reference>(this, texture);
|
||||
|
||||
@@ -57,8 +57,8 @@ public:
|
||||
|
||||
DISABLE_COPY_MOVE(OpenGLTextureCache)
|
||||
|
||||
ReferencePtr Get(QOpenGLContext *ctx, const VideoRenderingParams& params, FramePtr frame);
|
||||
ReferencePtr Get(QOpenGLContext *ctx, const VideoRenderingParams& params, Frame* frame);
|
||||
ReferencePtr Get(QOpenGLContext *ctx, FramePtr frame);
|
||||
ReferencePtr Get(QOpenGLContext *ctx, Frame* frame);
|
||||
ReferencePtr Get(QOpenGLContext *ctx, const VideoRenderingParams& params, const void *data, int linesize);
|
||||
ReferencePtr Get(QOpenGLContext *ctx, const VideoRenderingParams& params);
|
||||
|
||||
|
||||
@@ -38,7 +38,9 @@ OpenGLWorker::OpenGLWorker(VideoRenderFrameCache *frame_cache, QObject *parent)
|
||||
|
||||
void OpenGLWorker::FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range, NodeValueTable *table)
|
||||
{
|
||||
FramePtr frame = decoder->RetrieveVideo(range.in(), video_params().divider());
|
||||
FramePtr frame = decoder->RetrieveVideo(range.in(),
|
||||
video_params().divider(),
|
||||
video_params().mode() == RenderMode::kOffline);
|
||||
|
||||
if (frame) {
|
||||
emit RequestFrameToValue(frame, stream, table);
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include <QThread>
|
||||
|
||||
#include "core.h"
|
||||
#include "render/backend/indexmanager.h"
|
||||
#include "window/mainwindow/mainwindow.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
@@ -37,8 +36,6 @@ RenderBackend::RenderBackend(QObject *parent) :
|
||||
{
|
||||
// FIXME: Don't create in CLI mode
|
||||
cancel_dialog_ = new RenderCancelDialog(Core::instance()->main_window());
|
||||
|
||||
connect(IndexManager::instance(), &IndexManager::StreamIndexUpdated, this, &RenderBackend::IndexUpdated);
|
||||
}
|
||||
|
||||
bool RenderBackend::Init()
|
||||
@@ -473,7 +470,6 @@ void RenderBackend::InitWorkers()
|
||||
|
||||
// Connect cancel dialog to it
|
||||
connect(processor, &RenderWorker::CompletedCache, cancel_dialog_, &RenderCancelDialog::WorkerDone, Qt::QueuedConnection);
|
||||
connect(processor, &RenderWorker::FootageUnavailable, this, &RenderBackend::FootageUnavailable, Qt::QueuedConnection);
|
||||
|
||||
// Finally, we can move it to its own thread
|
||||
processor->moveToThread(thread);
|
||||
@@ -486,83 +482,6 @@ void RenderBackend::InitWorkers()
|
||||
processor_busy_state_.fill(false);
|
||||
}
|
||||
|
||||
void RenderBackend::FootageUnavailable(StreamPtr stream, Decoder::RetrieveState state, const TimeRange &range, const rational &stream_time)
|
||||
{
|
||||
if (state == Decoder::kFailedToOpen){
|
||||
|
||||
qWarning() << "For range" << range.in() << "-" << range.out() << stream->footage()->filename() << "stream" << stream->index() << "failed to open";
|
||||
|
||||
} else if (state == Decoder::kIndexUnavailable) {
|
||||
|
||||
FootageWaitInfo info = {stream, range, stream_time};
|
||||
|
||||
if (footage_wait_info_.contains(info)) {
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Waiting for" << stream.get() << "time" << stream_time.toDouble() << "for frame" << range.in();
|
||||
|
||||
if (IndexManager::instance()->IsIndexing(stream)) {
|
||||
|
||||
footage_wait_info_.append(info);
|
||||
|
||||
} else if ((stream->type() == Stream::kVideo && std::static_pointer_cast<VideoStream>(stream)->is_frame_index_ready())
|
||||
|| (stream->type() == Stream::kAudio && std::static_pointer_cast<AudioStream>(stream)->index_done())) {
|
||||
|
||||
// Index JUST finished, requeue this time
|
||||
InvalidateCache(range, nullptr);
|
||||
|
||||
} else {
|
||||
|
||||
// Start indexing process
|
||||
footage_wait_info_.append(info);
|
||||
IndexManager::instance()->StartIndexingStream(stream);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void RenderBackend::IndexUpdated(Stream* stream)
|
||||
{
|
||||
for (int i=0;i<footage_wait_info_.size();i++) {
|
||||
const FootageWaitInfo& info = footage_wait_info_.at(i);
|
||||
|
||||
if (info.stream.get() == stream) {
|
||||
bool footage_ready = false;
|
||||
|
||||
// See if this stream now has an index for the requested time
|
||||
if (stream->type() == Stream::kVideo) {
|
||||
|
||||
VideoStream* video_stream = static_cast<VideoStream*>(stream);
|
||||
|
||||
if (video_stream->get_closest_timestamp_in_frame_index(info.stream_time) >= 0) {
|
||||
// This index now has this frame, we can re-render it
|
||||
qDebug() << "Re-ICing video" << info.affected_range.in().toDouble() << "to" << info.affected_range.out().toDouble();
|
||||
footage_ready = true;
|
||||
}
|
||||
|
||||
} else if (stream->type() == Stream::kAudio) {
|
||||
|
||||
AudioStream* audio_stream = static_cast<AudioStream*>(stream);
|
||||
|
||||
if (audio_stream->index_length() >= info.stream_time) {
|
||||
// The index now has this audio, we can re-render it
|
||||
qDebug() << "Re-ICing audio" << info.affected_range.in().toDouble() << "to" << info.affected_range.out().toDouble();
|
||||
footage_ready = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (footage_ready) {
|
||||
InvalidateCache(info.affected_range, nullptr);
|
||||
footage_wait_info_.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderBackend::FootageWaitInfo::operator==(const RenderBackend::FootageWaitInfo &rhs) const
|
||||
{
|
||||
return rhs.stream == stream
|
||||
|
||||
@@ -167,11 +167,6 @@ private:
|
||||
|
||||
QList<FootageWaitInfo> footage_wait_info_;
|
||||
|
||||
private slots:
|
||||
void FootageUnavailable(StreamPtr stream, Decoder::RetrieveState state, const TimeRange& path, const rational& stream_time);
|
||||
|
||||
void IndexUpdated(Stream *stream);
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -108,11 +108,6 @@ bool RenderWorker::IsStarted()
|
||||
return started_;
|
||||
}
|
||||
|
||||
void RenderWorker::ReportUnavailableFootage(StreamPtr stream, Decoder::RetrieveState state, const rational& stream_time)
|
||||
{
|
||||
emit FootageUnavailable(stream, state, path_.range(), stream_time);
|
||||
}
|
||||
|
||||
void RenderWorker::InputProcessingEvent(NodeInput* input, const TimeRange& input_time, NodeValueTable *table)
|
||||
{
|
||||
// Exception for Footage types where we actually retrieve some Footage data from a decoder
|
||||
@@ -124,13 +119,8 @@ void RenderWorker::InputProcessingEvent(NodeInput* input, const TimeRange& input
|
||||
|
||||
if (decoder) {
|
||||
|
||||
Decoder::RetrieveState state = decoder->GetRetrieveState(input_time.out());
|
||||
FrameToValue(decoder, stream, input_time, table);
|
||||
|
||||
if (state == Decoder::kReady) {
|
||||
FrameToValue(decoder, stream, input_time, table);
|
||||
} else {
|
||||
ReportUnavailableFootage(stream, state, input_time.out());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +48,6 @@ public slots:
|
||||
signals:
|
||||
void CompletedCache(OLIVE_NAMESPACE::NodeDependency dep, OLIVE_NAMESPACE::NodeValueTable data, qint64 job_time);
|
||||
|
||||
void FootageUnavailable(OLIVE_NAMESPACE::StreamPtr stream, OLIVE_NAMESPACE::Decoder::RetrieveState state, const OLIVE_NAMESPACE::TimeRange& range, const OLIVE_NAMESPACE::rational& stream_time);
|
||||
|
||||
protected:
|
||||
virtual bool InitInternal() = 0;
|
||||
|
||||
@@ -61,8 +59,6 @@ protected:
|
||||
|
||||
virtual void FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range, NodeValueTable* table) = 0;
|
||||
|
||||
virtual void ReportUnavailableFootage(StreamPtr stream, Decoder::RetrieveState state, const rational& stream_time);
|
||||
|
||||
virtual void InputProcessingEvent(NodeInput *input, const TimeRange &input_time, NodeValueTable* table) override;
|
||||
|
||||
virtual void ProcessNodeEvent(const Node *node, const TimeRange &range, NodeValueDatabase &input_params, NodeValueTable &output_params) override;
|
||||
|
||||
@@ -263,17 +263,12 @@ TimeRange VideoRenderBackend::PopNextFrameFromQueue()
|
||||
return TimeRange(frame_range.in(), frame_range.in());
|
||||
}
|
||||
|
||||
void VideoRenderBackend::ThreadCompletedDownload(NodeDependency dep, qint64 job_time, QByteArray hash, bool texture_existed)
|
||||
void VideoRenderBackend::ThreadCompletedDownload(NodeDependency dep, qint64 job_time, QByteArray hash)
|
||||
{
|
||||
SetWorkerBusyState(static_cast<RenderWorker*>(sender()), false);
|
||||
|
||||
SetFrameHash(dep, hash, job_time);
|
||||
|
||||
// Register frame with the disk manager
|
||||
if (texture_existed && operating_mode_ & VideoRenderWorker::kDownloadOnly) {
|
||||
DiskManager::instance()->CreatedFile(frame_cache()->CachePathName(hash, params_.format()), hash);
|
||||
}
|
||||
|
||||
QList<rational> hashes_with_time = frame_cache()->FramesWithHash(hash);
|
||||
|
||||
foreach (const rational& t, hashes_with_time) {
|
||||
|
||||
@@ -136,7 +136,7 @@ private:
|
||||
bool pop_toggle_;
|
||||
|
||||
private slots:
|
||||
void ThreadCompletedDownload(NodeDependency dep, qint64 job_time, QByteArray hash, bool texture_existed);
|
||||
void ThreadCompletedDownload(NodeDependency dep, qint64 job_time, QByteArray hash);
|
||||
void ThreadSkippedFrame(NodeDependency dep, qint64 job_time, QByteArray hash);
|
||||
void ThreadHashAlreadyExists(NodeDependency dep, qint64 job_time, QByteArray hash);
|
||||
void ThreadGeneratedFrame();
|
||||
|
||||
@@ -20,10 +20,16 @@
|
||||
|
||||
#include "videorenderframecache.h"
|
||||
|
||||
#include <OpenEXR/ImfFloatAttribute.h>
|
||||
#include <OpenEXR/ImfInputFile.h>
|
||||
#include <OpenEXR/ImfOutputFile.h>
|
||||
#include <OpenEXR/ImfChannelList.h>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "codec/frame.h"
|
||||
#include "common/filefunctions.h"
|
||||
#include "render/diskmanager.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
@@ -139,25 +145,132 @@ const QMap<rational, QByteArray> &VideoRenderFrameCache::time_hash_map() const
|
||||
return time_hash_map_;
|
||||
}
|
||||
|
||||
QString VideoRenderFrameCache::GetFormatExtension(const PixelFormat::Format &f)
|
||||
{
|
||||
if (PixelFormat::FormatIsFloat(f)) {
|
||||
// EXR is only fast with float buffers so we only use it for those
|
||||
return QStringLiteral(".exr");
|
||||
} else {
|
||||
// FIXME: Will probably need different codec here. JPEG is the fastest and smallest by far (much
|
||||
// more so than TIFF or PNG) and we don't mind lossy for the offline cache, but JPEG
|
||||
// doesn't support >8-bit or alpha channels. JPEG2000 does, but my OIIO wasn't compiled
|
||||
// with it and I imagine it's not common in general. Still, this works well for now as a
|
||||
// prototype.
|
||||
return QStringLiteral(".jpg");
|
||||
}
|
||||
}
|
||||
|
||||
void VideoRenderFrameCache::SaveCacheFrame(const QByteArray& hash,
|
||||
char* data,
|
||||
const VideoRenderingParams& vparam) const
|
||||
{
|
||||
QString fn = CachePathName(hash, vparam.format());
|
||||
|
||||
if (SaveCacheFrame(fn, data, vparam)) {
|
||||
// Register frame with the disk manager
|
||||
DiskManager::instance()->CreatedFile(fn, hash);
|
||||
}
|
||||
}
|
||||
|
||||
QString VideoRenderFrameCache::CachePathName(const QByteArray& hash, const PixelFormat::Format& pix_fmt) const
|
||||
{
|
||||
QString ext;
|
||||
|
||||
if (pix_fmt == PixelFormat::PIX_FMT_RGB8
|
||||
|| pix_fmt == PixelFormat::PIX_FMT_RGBA8
|
||||
|| pix_fmt == PixelFormat::PIX_FMT_RGB16U
|
||||
|| pix_fmt == PixelFormat::PIX_FMT_RGBA16U) {
|
||||
ext = QStringLiteral("jpg");
|
||||
} else {
|
||||
ext = QStringLiteral("exr");
|
||||
}
|
||||
QString ext = GetFormatExtension(pix_fmt);
|
||||
|
||||
QDir cache_dir(QDir(FileFunctions::GetMediaCacheLocation()).filePath(QString(hash.left(1).toHex())));
|
||||
cache_dir.mkpath(".");
|
||||
|
||||
QString filename = QStringLiteral("%1.%2").arg(QString(hash.mid(1).toHex()), ext);
|
||||
QString filename = QStringLiteral("%1%2").arg(QString(hash.mid(1).toHex()), ext);
|
||||
|
||||
return cache_dir.filePath(filename);
|
||||
}
|
||||
|
||||
bool VideoRenderFrameCache::SaveCacheFrame(const QString &filename, char *data, const VideoRenderingParams &vparam)
|
||||
{
|
||||
switch (vparam.format()) {
|
||||
case PixelFormat::PIX_FMT_RGB8:
|
||||
case PixelFormat::PIX_FMT_RGBA8:
|
||||
case PixelFormat::PIX_FMT_RGB16U:
|
||||
case PixelFormat::PIX_FMT_RGBA16U:
|
||||
{
|
||||
// Integer types are stored in JPEG which we run through OIIO
|
||||
|
||||
std::string fn_std = filename.toStdString();
|
||||
|
||||
auto out = OIIO::ImageOutput::create(fn_std);
|
||||
|
||||
if (out) {
|
||||
// Attempt to keep this write to one thread
|
||||
out->threads(1);
|
||||
|
||||
out->open(fn_std, OIIO::ImageSpec(vparam.width(),
|
||||
vparam.height(),
|
||||
PixelFormat::ChannelCount(vparam.format()),
|
||||
PixelFormat::GetOIIOTypeDesc(vparam.format())));
|
||||
|
||||
out->write_image(PixelFormat::GetOIIOTypeDesc(vparam.format()), data);
|
||||
|
||||
out->close();
|
||||
|
||||
#if OIIO_VERSION < 10903
|
||||
OIIO::ImageOutput::destroy(out);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
} else {
|
||||
qCritical() << "Failed to write JPEG file:" << OIIO::geterror().c_str();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case PixelFormat::PIX_FMT_RGB16F:
|
||||
case PixelFormat::PIX_FMT_RGBA16F:
|
||||
case PixelFormat::PIX_FMT_RGB32F:
|
||||
case PixelFormat::PIX_FMT_RGBA32F:
|
||||
{
|
||||
// Floating point types are stored in EXR
|
||||
Imf::PixelType pix_type;
|
||||
|
||||
if (vparam.format() == PixelFormat::PIX_FMT_RGB16F
|
||||
|| vparam.format() == PixelFormat::PIX_FMT_RGBA16F) {
|
||||
pix_type = Imf::HALF;
|
||||
} else {
|
||||
pix_type = Imf::FLOAT;
|
||||
}
|
||||
|
||||
Imf::Header header(vparam.effective_width(),
|
||||
vparam.effective_height());
|
||||
header.channels().insert("R", Imf::Channel(pix_type));
|
||||
header.channels().insert("G", Imf::Channel(pix_type));
|
||||
header.channels().insert("B", Imf::Channel(pix_type));
|
||||
header.channels().insert("A", Imf::Channel(pix_type));
|
||||
|
||||
header.compression() = Imf::DWAA_COMPRESSION;
|
||||
header.insert("dwaCompressionLevel", Imf::FloatAttribute(200.0f));
|
||||
|
||||
Imf::OutputFile out(filename.toUtf8(), header, 0);
|
||||
|
||||
int bpc = PixelFormat::BytesPerChannel(vparam.format());
|
||||
|
||||
size_t xs = kRGBAChannels * bpc;
|
||||
size_t ys = vparam.effective_width() * kRGBAChannels * bpc;
|
||||
|
||||
Imf::FrameBuffer framebuffer;
|
||||
framebuffer.insert("R", Imf::Slice(pix_type, data, xs, ys));
|
||||
framebuffer.insert("G", Imf::Slice(pix_type, data + bpc, xs, ys));
|
||||
framebuffer.insert("B", Imf::Slice(pix_type, data + 2*bpc, xs, ys));
|
||||
framebuffer.insert("A", Imf::Slice(pix_type, data + 3*bpc, xs, ys));
|
||||
out.setFrameBuffer(framebuffer);
|
||||
|
||||
out.writePixels(vparam.effective_height());
|
||||
|
||||
return true;
|
||||
}
|
||||
case PixelFormat::PIX_FMT_INVALID:
|
||||
case PixelFormat::PIX_FMT_COUNT:
|
||||
qCritical() << "Unable to cache invalid pixel format" << vparam.format();
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "common/rational.h"
|
||||
#include "render/pixelformat.h"
|
||||
#include "render/videoparams.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
@@ -50,11 +51,6 @@ public:
|
||||
*/
|
||||
bool TryCache(const QByteArray& hash);
|
||||
|
||||
/**
|
||||
* @brief Return the path of the cached image at this time
|
||||
*/
|
||||
QString CachePathName(const QByteArray &hash, const PixelFormat::Format& pix_fmt) const;
|
||||
|
||||
void SetCacheID(const QString& id);
|
||||
|
||||
QByteArray TimeToHash(const rational& time) const;
|
||||
@@ -77,6 +73,16 @@ public:
|
||||
|
||||
const QMap<rational, QByteArray>& time_hash_map() const;
|
||||
|
||||
static QString GetFormatExtension(const PixelFormat::Format& f);
|
||||
|
||||
/**
|
||||
* @brief Return the path of the cached image at this time
|
||||
*/
|
||||
QString CachePathName(const QByteArray &hash, const PixelFormat::Format& pix_fmt) const;
|
||||
|
||||
static bool SaveCacheFrame(const QString& filename, char *data, const VideoRenderingParams &vparam);
|
||||
void SaveCacheFrame(const QByteArray& hash, char *data, const VideoRenderingParams &vparam) const;
|
||||
|
||||
private:
|
||||
QMap<rational, QByteArray> time_hash_map_;
|
||||
|
||||
|
||||
@@ -20,11 +20,6 @@
|
||||
|
||||
#include "videorenderworker.h"
|
||||
|
||||
#include <OpenEXR/ImfFloatAttribute.h>
|
||||
#include <OpenEXR/ImfInputFile.h>
|
||||
#include <OpenEXR/ImfOutputFile.h>
|
||||
#include <OpenEXR/ImfChannelList.h>
|
||||
|
||||
#include "common/define.h"
|
||||
#include "common/functiontimer.h"
|
||||
#include "node/block/transition/transition.h"
|
||||
@@ -84,7 +79,7 @@ NodeValueTable VideoRenderWorker::RenderInternal(const NodeDependency& path, con
|
||||
if (!(operating_mode_ & kRenderOnly)) {
|
||||
|
||||
// Emit only the hash
|
||||
emit CompletedDownload(path, job_time, hash, false);
|
||||
emit CompletedDownload(path, job_time, hash);
|
||||
|
||||
} else if ((operating_mode_ & kHashOnly) && frame_cache_->HasHash(hash, video_params_.format())) {
|
||||
|
||||
@@ -101,14 +96,14 @@ NodeValueTable VideoRenderWorker::RenderInternal(const NodeDependency& path, con
|
||||
|
||||
// If we actually have a texture, download it into the disk cache
|
||||
if (!texture.isNull() || (!(operating_mode_ & kDownloadOnly))) {
|
||||
Download(path.in(), texture, frame_cache_->CachePathName(hash, video_params_.format()));
|
||||
Download(hash, path.in(), texture);
|
||||
}
|
||||
|
||||
frame_cache_->RemoveHashFromCurrentlyCaching(hash);
|
||||
|
||||
// Signal that this job is complete
|
||||
if (operating_mode_ & kDownloadOnly) {
|
||||
emit CompletedDownload(path, job_time, hash, !texture.isNull());
|
||||
emit CompletedDownload(path, job_time, hash);
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -162,92 +157,17 @@ void VideoRenderWorker::CloseInternal()
|
||||
download_buffer_.clear();
|
||||
}
|
||||
|
||||
void VideoRenderWorker::Download(const rational& time, QVariant texture, QString filename)
|
||||
void VideoRenderWorker::Download(const QByteArray& hash, const rational& time, QVariant texture)
|
||||
{
|
||||
if (operating_mode_ & kDownloadOnly) {
|
||||
|
||||
TextureToBuffer(texture, download_buffer_.data(), 0);
|
||||
|
||||
switch (video_params().format()) {
|
||||
case PixelFormat::PIX_FMT_RGB8:
|
||||
case PixelFormat::PIX_FMT_RGBA8:
|
||||
case PixelFormat::PIX_FMT_RGB16U:
|
||||
case PixelFormat::PIX_FMT_RGBA16U:
|
||||
{
|
||||
// Integer types are stored in JPEG which we run through OIIO
|
||||
|
||||
std::string fn_std = filename.toStdString();
|
||||
|
||||
auto out = OIIO::ImageOutput::create(fn_std);
|
||||
|
||||
if (out) {
|
||||
// Attempt to keep this write to one thread
|
||||
out->threads(1);
|
||||
|
||||
out->open(fn_std, OIIO::ImageSpec(video_params().effective_width(),
|
||||
video_params().effective_height(),
|
||||
PixelFormat::ChannelCount(video_params().format()),
|
||||
PixelFormat::GetOIIOTypeDesc(video_params().format())));
|
||||
|
||||
out->write_image(PixelFormat::GetOIIOTypeDesc(video_params().format()), download_buffer_.data());
|
||||
|
||||
out->close();
|
||||
|
||||
#if OIIO_VERSION < 10903
|
||||
OIIO::ImageOutput::destroy(out);
|
||||
#endif
|
||||
} else {
|
||||
qCritical() << "Failed to write JPEG file:" << OIIO::geterror().c_str();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PixelFormat::PIX_FMT_RGB16F:
|
||||
case PixelFormat::PIX_FMT_RGBA16F:
|
||||
case PixelFormat::PIX_FMT_RGB32F:
|
||||
case PixelFormat::PIX_FMT_RGBA32F:
|
||||
{
|
||||
// Floating point types are stored in EXR
|
||||
Imf::PixelType pix_type;
|
||||
|
||||
if (video_params().format() == PixelFormat::PIX_FMT_RGB16F
|
||||
|| video_params().format() == PixelFormat::PIX_FMT_RGBA16F) {
|
||||
pix_type = Imf::HALF;
|
||||
} else {
|
||||
pix_type = Imf::FLOAT;
|
||||
}
|
||||
|
||||
Imf::Header header(video_params().effective_width(),
|
||||
video_params().effective_height());
|
||||
header.channels().insert("R", Imf::Channel(pix_type));
|
||||
header.channels().insert("G", Imf::Channel(pix_type));
|
||||
header.channels().insert("B", Imf::Channel(pix_type));
|
||||
header.channels().insert("A", Imf::Channel(pix_type));
|
||||
|
||||
header.compression() = Imf::DWAA_COMPRESSION;
|
||||
header.insert("dwaCompressionLevel", Imf::FloatAttribute(200.0f));
|
||||
|
||||
Imf::OutputFile out(filename.toUtf8(), header, 0);
|
||||
|
||||
int bpc = PixelFormat::BytesPerChannel(video_params().format());
|
||||
|
||||
size_t xs = kRGBAChannels * bpc;
|
||||
size_t ys = video_params().effective_width() * kRGBAChannels * bpc;
|
||||
|
||||
Imf::FrameBuffer framebuffer;
|
||||
framebuffer.insert("R", Imf::Slice(pix_type, download_buffer_.data(), xs, ys));
|
||||
framebuffer.insert("G", Imf::Slice(pix_type, download_buffer_.data() + bpc, xs, ys));
|
||||
framebuffer.insert("B", Imf::Slice(pix_type, download_buffer_.data() + 2*bpc, xs, ys));
|
||||
framebuffer.insert("A", Imf::Slice(pix_type, download_buffer_.data() + 3*bpc, xs, ys));
|
||||
out.setFrameBuffer(framebuffer);
|
||||
|
||||
out.writePixels(video_params().effective_height());
|
||||
break;
|
||||
}
|
||||
case PixelFormat::PIX_FMT_INVALID:
|
||||
case PixelFormat::PIX_FMT_COUNT:
|
||||
qCritical() << "Unable to cache invalid pixel format" << video_params().format();
|
||||
break;
|
||||
}
|
||||
frame_cache_->SaveCacheFrame(hash,
|
||||
download_buffer_.data(),
|
||||
VideoRenderingParams(video_params_.effective_width(),
|
||||
video_params_.effective_height(),
|
||||
video_params_.format()));
|
||||
|
||||
} else {
|
||||
|
||||
@@ -295,14 +215,6 @@ NodeValueTable VideoRenderWorker::RenderBlock(const TrackOutput *track, const Ti
|
||||
return table;
|
||||
}
|
||||
|
||||
void VideoRenderWorker::ReportUnavailableFootage(StreamPtr stream, Decoder::RetrieveState state, const rational &stream_time)
|
||||
{
|
||||
emit FootageUnavailable(stream,
|
||||
state,
|
||||
TimeRange(CurrentPath().in(), CurrentPath().in() + video_params().time_base()),
|
||||
stream_time);
|
||||
}
|
||||
|
||||
ColorProcessorCache *VideoRenderWorker::color_cache()
|
||||
{
|
||||
return &color_cache_;
|
||||
|
||||
@@ -73,7 +73,7 @@ public:
|
||||
void SetFrameGenerationParams(int width, int height, const QMatrix4x4 &matrix);
|
||||
|
||||
signals:
|
||||
void CompletedDownload(NodeDependency path, qint64 job_time, QByteArray hash, bool texture_existed);
|
||||
void CompletedDownload(NodeDependency path, qint64 job_time, QByteArray hash);
|
||||
|
||||
void HashAlreadyBeingCached(NodeDependency path, qint64 job_time, QByteArray hash);
|
||||
|
||||
@@ -100,12 +100,10 @@ protected:
|
||||
|
||||
virtual NodeValueTable RenderBlock(const TrackOutput *track, const TimeRange& range) override;
|
||||
|
||||
virtual void ReportUnavailableFootage(StreamPtr stream, Decoder::RetrieveState state, const rational& stream_time) override;
|
||||
|
||||
ColorProcessorCache* color_cache();
|
||||
|
||||
private:
|
||||
void Download(const rational &time, QVariant texture, QString filename);
|
||||
void Download(const QByteArray &hash, const rational &time, QVariant texture);
|
||||
|
||||
void ResizeDownloadBuffer();
|
||||
|
||||
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
ourbasename=$(basename "$0")
|
||||
|
||||
rm ocioconf.qrc
|
||||
echo "<RCC>" >> ocioconf.qrc
|
||||
echo " <qresource prefix=\"/ocioconf\">" >> ocioconf.qrc
|
||||
|
||||
for f in $(find * -type f)
|
||||
do
|
||||
if [ "$f" != "CMakeLists.txt" ] && [ "$f" != "ocioconf.qrc" ] && [ "$f" != "$ourbasename" ]
|
||||
then
|
||||
echo " <file>$f</file>" >> ocioconf.qrc
|
||||
fi
|
||||
done
|
||||
|
||||
echo " </qresource>" >> ocioconf.qrc
|
||||
echo "</RCC>" >> ocioconf.qrc
|
||||
@@ -230,11 +230,7 @@ FramePtr PixelFormat::ConvertPixelFormat(FramePtr frame, const PixelFormat::Form
|
||||
|
||||
// Set the pixels (this is necessary as opposed to an OIIO buffer wrapper since Frame has
|
||||
// linesizes)
|
||||
src.set_pixels(OIIO::ROI(),
|
||||
GetOIIOTypeDesc(frame->format()),
|
||||
frame->const_data(),
|
||||
OIIO::AutoStride,
|
||||
frame->linesize_bytes());
|
||||
OIIODecoder::FrameToBuffer(frame, &src);
|
||||
|
||||
// Create a destination OIIO buffer with our destination format
|
||||
OIIO::ImageBuf dst(OIIO::ImageSpec(converted->width(),
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(conform)
|
||||
add_subdirectory(index)
|
||||
add_subdirectory(proxy)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
|
||||
@@ -34,7 +34,7 @@ ConformTask::ConformTask(AudioStreamPtr stream, const AudioRenderingParams& para
|
||||
void ConformTask::Action()
|
||||
{
|
||||
if (stream_->footage()->decoder().isEmpty()) {
|
||||
emit Failed(QStringLiteral("Stream has no decoder"));
|
||||
emit Failed(tr("Failed to find decoder to conform audio stream"));
|
||||
} else {
|
||||
DecoderPtr decoder = Decoder::CreateFromID(stream_->footage()->decoder());
|
||||
|
||||
@@ -42,9 +42,11 @@ void ConformTask::Action()
|
||||
|
||||
connect(decoder.get(), &Decoder::IndexProgress, this, &ConformTask::ProgressChanged);
|
||||
|
||||
decoder->Conform(params_, &IsCancelled());
|
||||
|
||||
emit Succeeded();
|
||||
if (decoder->ConformAudio(&IsCancelled(), params_)) {
|
||||
emit Succeeded();
|
||||
} else {
|
||||
emit Failed(QStringLiteral("Failed to conform audio"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
task/index/index.h
|
||||
task/index/index.cpp
|
||||
task/proxy/proxy.h
|
||||
task/proxy/proxy.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -18,33 +18,42 @@
|
||||
|
||||
***/
|
||||
|
||||
#include "index.h"
|
||||
#include "proxy.h"
|
||||
|
||||
#include "codec/decoder.h"
|
||||
#include "codec/ffmpeg/ffmpegdecoder.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
IndexTask::IndexTask(StreamPtr stream) :
|
||||
stream_(stream)
|
||||
ProxyTask::ProxyTask(VideoStreamPtr stream, int divider) :
|
||||
stream_(stream),
|
||||
divider_(divider)
|
||||
{
|
||||
SetTitle(tr("Indexing %1:%2").arg(stream_->footage()->filename(), QString::number(stream_->index())));
|
||||
if (divider_ == 1) {
|
||||
SetTitle(tr("Generating full resolution proxy %1:%2").arg(stream_->footage()->filename(),
|
||||
QString::number(stream_->index())));
|
||||
} else {
|
||||
SetTitle(tr("Generating 1/%1 resolution proxy %2:%3").arg(QString::number(divider),
|
||||
stream_->footage()->filename(),
|
||||
QString::number(stream_->index())));
|
||||
}
|
||||
}
|
||||
|
||||
void IndexTask::Action()
|
||||
void ProxyTask::Action()
|
||||
{
|
||||
if (stream_->footage()->decoder().isEmpty()) {
|
||||
emit Failed(QStringLiteral("Stream has no decoder"));
|
||||
emit Failed(tr("Failed to find decoder to conform audio stream"));
|
||||
} else {
|
||||
DecoderPtr decoder = Decoder::CreateFromID(stream_->footage()->decoder());
|
||||
|
||||
decoder->set_stream(stream_);
|
||||
|
||||
connect(decoder.get(), &Decoder::IndexProgress, this, &IndexTask::ProgressChanged);
|
||||
connect(decoder.get(), &Decoder::IndexProgress, this, &ProxyTask::ProgressChanged);
|
||||
|
||||
decoder->Index(&IsCancelled());
|
||||
|
||||
emit Succeeded();
|
||||
if (decoder->ProxyVideo(&IsCancelled(), divider_)) {
|
||||
emit Succeeded();
|
||||
} else {
|
||||
emit Failed(QStringLiteral("Failed to generate proxy"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,27 +18,29 @@
|
||||
|
||||
***/
|
||||
|
||||
#ifndef INDEXTASK_H
|
||||
#define INDEXTASK_H
|
||||
#ifndef PROXYTASK_H
|
||||
#define PROXYTASK_H
|
||||
|
||||
#include "project/item/footage/footage.h"
|
||||
#include "project/item/footage/videostream.h"
|
||||
#include "task/task.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class IndexTask : public Task
|
||||
class ProxyTask : public Task
|
||||
{
|
||||
public:
|
||||
IndexTask(StreamPtr stream);
|
||||
ProxyTask(VideoStreamPtr stream, int divider);
|
||||
|
||||
protected:
|
||||
virtual void Action() override;
|
||||
|
||||
private:
|
||||
StreamPtr stream_;
|
||||
VideoStreamPtr stream_;
|
||||
|
||||
int divider_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // INDEXTASK_H
|
||||
#endif // PROXYTASK_H
|
||||
@@ -45,14 +45,18 @@ QList<StyleDescriptor> StyleManager::ListInternal()
|
||||
return style_list;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
void StyleManager::UseNativeWindowsStyling(QWidget *widget)
|
||||
void StyleManager::UseOSNativeStyling(QWidget *widget)
|
||||
{
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
QStyle* s = QStyleFactory::create(QStringLiteral("windowsvista"));
|
||||
widget->setStyle(s);
|
||||
widget->setPalette(s->standardPalette());
|
||||
}
|
||||
#elif defined(Q_OS_MAC)
|
||||
QStyle* s = QStyleFactory::create(QStringLiteral("macintosh"));
|
||||
widget->setStyle(s);
|
||||
widget->setPalette(s->standardPalette());
|
||||
#endif
|
||||
}
|
||||
|
||||
QPalette StyleManager::ParsePalette(const QString& ini_path)
|
||||
{
|
||||
|
||||
@@ -54,9 +54,7 @@ public:
|
||||
|
||||
static QList<StyleDescriptor> ListInternal();
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
static void UseNativeWindowsStyling(QWidget* widget);
|
||||
#endif
|
||||
static void UseOSNativeStyling(QWidget* widget);
|
||||
|
||||
private:
|
||||
static QPalette ParsePalette(const QString& ini_path);
|
||||
|
||||
@@ -110,9 +110,7 @@ void Menu::SetBooleanAction(QAction *a, bool* boolean)
|
||||
|
||||
void Menu::Init()
|
||||
{
|
||||
#ifdef Q_OS_WINDOWS
|
||||
StyleManager::UseNativeWindowsStyling(this);
|
||||
#endif
|
||||
StyleManager::UseOSNativeStyling(this);
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
#include "core.h"
|
||||
#include "dialog/footageproperties/footageproperties.h"
|
||||
#include "dialog/sequence/sequence.h"
|
||||
#include "task/proxy/proxy.h"
|
||||
#include "task/taskmanager.h"
|
||||
#include "widget/menu/menu.h"
|
||||
#include "widget/menu/menushared.h"
|
||||
#include "window/mainwindow/mainwindow.h"
|
||||
@@ -284,6 +286,44 @@ void ProjectExplorer::ShowContextMenu()
|
||||
connect(reveal_action, &QAction::triggered, this, &ProjectExplorer::RevealSelectedFootage);
|
||||
|
||||
menu.addSeparator();
|
||||
|
||||
Footage* f = static_cast<Footage*>(context_menu_item_);
|
||||
|
||||
if (f->HasStreamsOfType(Stream::kVideo)) {
|
||||
Menu* proxy_menu = new Menu(tr("Proxy"), &menu);
|
||||
menu.addMenu(proxy_menu);
|
||||
|
||||
VideoStreamPtr video_stream = std::static_pointer_cast<VideoStream>(f->get_first_stream_of_type(Stream::kVideo));
|
||||
|
||||
if (video_stream->is_generating_proxy()) {
|
||||
|
||||
// Prevent multiple proxy actions from occurring at once
|
||||
QAction* cant_proxy_action = proxy_menu->addAction(tr("Proxy being generated..."));
|
||||
cant_proxy_action->setEnabled(false);
|
||||
|
||||
} else {
|
||||
|
||||
proxy_menu->addAction(tr("(None)"))->setData(0);
|
||||
proxy_menu->addSeparator();
|
||||
proxy_menu->addAction(tr("Full"))->setData(1);
|
||||
proxy_menu->addAction(tr("1/2"))->setData(2);
|
||||
proxy_menu->addAction(tr("1/4"))->setData(4);
|
||||
proxy_menu->addAction(tr("1/8"))->setData(8);
|
||||
|
||||
foreach (QAction* a, proxy_menu->actions()) {
|
||||
a->setCheckable(true);
|
||||
|
||||
if (a->data() == video_stream->using_proxy()) {
|
||||
a->setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
connect(proxy_menu, &Menu::triggered, this, &ProjectExplorer::ContextMenuStartProxy);
|
||||
|
||||
}
|
||||
|
||||
menu.addSeparator();
|
||||
}
|
||||
}
|
||||
|
||||
QAction* properties_action = menu.addAction(tr("P&roperties"));
|
||||
@@ -347,6 +387,40 @@ void ProjectExplorer::OpenContextMenuItemInNewWindow()
|
||||
Core::instance()->main_window()->FolderOpen(project(), context_menu_item_, true);
|
||||
}
|
||||
|
||||
void ProjectExplorer::ContextMenuStartProxy(QAction *a)
|
||||
{
|
||||
// Find video stream
|
||||
VideoStreamPtr video_stream = nullptr;
|
||||
|
||||
foreach (StreamPtr s, static_cast<Footage*>(context_menu_item_)->streams()) {
|
||||
if (s->type() == Stream::kVideo) {
|
||||
video_stream = std::static_pointer_cast<VideoStream>(s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!video_stream) {
|
||||
return;
|
||||
}
|
||||
|
||||
int chosen_proxy_setting = a->data().toInt();
|
||||
|
||||
if (chosen_proxy_setting != video_stream->using_proxy()) {
|
||||
if (!a->data().toInt()) {
|
||||
|
||||
// 0 means disable the proxy
|
||||
video_stream->set_proxy(0, QVector<int64_t>());
|
||||
|
||||
} else if (video_stream->try_start_proxy()) {
|
||||
|
||||
// Start a background task for proxying
|
||||
ProxyTask* proxy_task = new ProxyTask(video_stream, a->data().toInt());
|
||||
TaskManager::instance()->AddTask(proxy_task);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Project *ProjectExplorer::project() const
|
||||
{
|
||||
return model_.project();
|
||||
|
||||
@@ -171,6 +171,8 @@ private slots:
|
||||
|
||||
void OpenContextMenuItemInNewWindow();
|
||||
|
||||
void ContextMenuStartProxy(QAction* a);
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -196,7 +196,7 @@ void WaveformScope::UploadTextureFromBuffer()
|
||||
managed_tex_.Destroy();
|
||||
|
||||
texture_.Create(context(), buffer_);
|
||||
managed_tex_.Create(context(), buffer_->width(), buffer_->height(), buffer_->format());
|
||||
managed_tex_.Create(context(), buffer_->video_params());
|
||||
} else {
|
||||
texture_.Upload(buffer_);
|
||||
}
|
||||
|
||||
@@ -107,19 +107,15 @@ void TimelineViewBlockItem::paint(QPainter *painter, const QStyleOptionGraphicsI
|
||||
|
||||
// Read metadata
|
||||
SampleSummer::Info info;
|
||||
QFile wave_meta(wave_fn.append(QStringLiteral(".meta")));
|
||||
if (wave_meta.open(QFile::ReadOnly)) {
|
||||
wave_meta.read(reinterpret_cast<char*>(&info), sizeof(SampleSummer::Info));
|
||||
wave_meta.close();
|
||||
}
|
||||
memcpy(&info, w.data(), sizeof(SampleSummer::Info));
|
||||
|
||||
// Prevent divide by zero
|
||||
if (info.channels) {
|
||||
AudioWaveformView::DrawWaveform(painter,
|
||||
rect().toRect(),
|
||||
this->GetScale(),
|
||||
reinterpret_cast<const SampleSummer::Sum*>(w.constData()),
|
||||
w.size() / sizeof(SampleSummer::Sum),
|
||||
reinterpret_cast<const SampleSummer::Sum*>(w.constData() + sizeof(SampleSummer::Info)),
|
||||
(w.size() - sizeof(SampleSummer::Info)) / sizeof(SampleSummer::Sum),
|
||||
info.channels);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ void ViewerDisplayWidget::SetImage(const QString &fn)
|
||||
load_buffer_.set_video_params(VideoRenderingParams(input->spec().width, input->spec().height, image_format));
|
||||
load_buffer_.allocate();
|
||||
|
||||
texture_.Create(context(), input->spec().width, input->spec().height, image_format);
|
||||
texture_.Create(context(), VideoRenderingParams(input->spec().width, input->spec().height, image_format));
|
||||
}
|
||||
|
||||
input->read_image(input->spec().format, load_buffer_.data(), OIIO::AutoStride, load_buffer_.linesize_bytes());
|
||||
@@ -133,7 +133,7 @@ void ViewerDisplayWidget::SetImageFromLoadBuffer(Frame *in_buffer)
|
||||
|| texture_.width() != in_buffer->width()
|
||||
|| texture_.height() != in_buffer->height()
|
||||
|| texture_.format() != in_buffer->format()) {
|
||||
texture_.Create(context(), in_buffer->width(), in_buffer->height(), in_buffer->format(), in_buffer->data(), load_buffer_.linesize_pixels());
|
||||
texture_.Create(context(), in_buffer->video_params(), in_buffer->data(), load_buffer_.linesize_pixels());
|
||||
} else {
|
||||
texture_.Upload(in_buffer);
|
||||
}
|
||||
|
||||
@@ -39,9 +39,7 @@ OLIVE_NAMESPACE_ENTER
|
||||
MainMenu::MainMenu(MainWindow *parent) :
|
||||
QMenuBar(parent)
|
||||
{
|
||||
#ifdef Q_OS_WINDOWS
|
||||
StyleManager::UseNativeWindowsStyling(this);
|
||||
#endif
|
||||
StyleManager::UseOSNativeStyling(this);
|
||||
|
||||
//
|
||||
// FILE MENU
|
||||
|
||||
Reference in New Issue
Block a user