began work on exr proxy task
This commit is contained in:
@@ -319,7 +319,11 @@ QString Decoder::GetConformedFilename(const AudioRenderingParams ¶ms)
|
||||
return index_fn;
|
||||
}
|
||||
|
||||
void Decoder::Index(const QAtomicInt *)
|
||||
void Decoder::ProxyVideo(const QAtomicInt *, int )
|
||||
{
|
||||
}
|
||||
|
||||
void Decoder::ProxyAudio(const QAtomicInt *)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
+23
-18
@@ -216,37 +216,41 @@ public:
|
||||
static DecoderPtr CreateFromID(const QString& id);
|
||||
|
||||
/**
|
||||
* @brief Conform an audio stream to match certain parameters (audio only)
|
||||
* @brief AUDIO ONLY: Conform an audio stream to match certain parameters
|
||||
*
|
||||
* 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.
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
void Conform(const AudioRenderingParams& params, const QAtomicInt* cancelled);
|
||||
|
||||
/**
|
||||
* @brief Create an index for this media
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
* @brief VIDEO ONLY: Produce a compressed EXR proxy with the specified divider
|
||||
*/
|
||||
virtual void Index(const QAtomicInt* cancelled);
|
||||
virtual void ProxyVideo(const QAtomicInt* cancelled, int divider);
|
||||
|
||||
/**
|
||||
* @brief AUDIO ONLY: Returns whether a cached transcode of this audio matching the specified params already exists
|
||||
* @brief AUDIO ONLY: Produces a complete PCM extraction of the audio stream
|
||||
*
|
||||
* Internally, our render engine only deals with PCM since it provides the least headaches and
|
||||
* modern computers have the processing power to do it.
|
||||
*/
|
||||
virtual void ProxyAudio(const QAtomicInt* cancelled);
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
@@ -256,7 +260,8 @@ protected:
|
||||
/**
|
||||
* @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;
|
||||
|
||||
|
||||
+156
-154
@@ -623,24 +623,170 @@ void FFmpegDecoder::Error(const QString &s)
|
||||
ClearResources();
|
||||
}
|
||||
|
||||
void FFmpegDecoder::Index(const QAtomicInt* cancelled)
|
||||
void FFmpegDecoder::ProxyVideo(const QAtomicInt *cancelled, int divider)
|
||||
{
|
||||
// Iterate through each video frame transcode each frame to compressed EXR
|
||||
QMutexLocker locker(stream()->index_process_lock());
|
||||
|
||||
if (stream()->type() == Stream::kAudio) {
|
||||
QByteArray fn_bytes = stream()->footage()->filename().toUtf8();
|
||||
|
||||
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()));
|
||||
FFmpegDecoderInstance index_instance(fn_bytes.constData(), stream()->index());
|
||||
|
||||
input.close();
|
||||
|
||||
}
|
||||
|
||||
void FFmpegDecoder::ProxyAudio(const QAtomicInt *cancelled)
|
||||
{
|
||||
// Iterate through each audio frame and extract the PCM data
|
||||
QMutexLocker locker(stream()->index_process_lock());
|
||||
|
||||
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()));
|
||||
|
||||
input.close();
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
UnconditionalAudioIndex(cancelled);
|
||||
|
||||
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();
|
||||
|
||||
SwrContext* resampler = nullptr;
|
||||
AVSampleFormat src_sample_fmt = static_cast<AVSampleFormat>(index_instance.stream()->codecpar->format);
|
||||
AVSampleFormat dst_sample_fmt;
|
||||
|
||||
// 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);
|
||||
|
||||
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);
|
||||
|
||||
swr_init(resampler);
|
||||
} else {
|
||||
dst_sample_fmt = src_sample_fmt;
|
||||
}
|
||||
|
||||
AudioRenderingParams wave_params(index_instance.stream()->codecpar->sample_rate,
|
||||
channel_layout,
|
||||
FFmpegCommon::GetNativeSampleFormat(dst_sample_fmt));
|
||||
WaveOutput wave_out(GetIndexFilename(), wave_params);
|
||||
|
||||
AVPacket* pkt = av_packet_alloc();
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
int ret;
|
||||
|
||||
if (wave_out.open()) {
|
||||
bool success = false;
|
||||
|
||||
while (true) {
|
||||
// Check if we have a `cancelled` ptr and its value
|
||||
if (cancelled && *cancelled) {
|
||||
break;
|
||||
}
|
||||
|
||||
ret = index_instance.GetFrame(pkt, frame);
|
||||
|
||||
if (ret < 0) {
|
||||
|
||||
if (ret == AVERROR_EOF) {
|
||||
success = true;
|
||||
} else {
|
||||
char err_str[50];
|
||||
av_strerror(ret, err_str, 50);
|
||||
qWarning() << "Failed to index:" << 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);
|
||||
}
|
||||
}
|
||||
|
||||
wave_out.close();
|
||||
|
||||
if (success) {
|
||||
audio_stream->set_index_done(true);
|
||||
} else {
|
||||
// Audio index didn't complete, delete it
|
||||
QFile(GetIndexFilename()).remove();
|
||||
audio_stream->clear_index();
|
||||
}
|
||||
} else {
|
||||
qWarning() << "Failed to open WAVE output for indexing";
|
||||
}
|
||||
|
||||
if (resampler != nullptr) {
|
||||
swr_free(&resampler);
|
||||
}
|
||||
|
||||
av_frame_free(&frame);
|
||||
av_packet_free(&pkt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -655,150 +801,6 @@ int FFmpegDecoder::GetScaledDimension(int dim, int divider)
|
||||
return dim / divider;
|
||||
}
|
||||
|
||||
void FFmpegDecoder::UnconditionalAudioIndex(const QAtomicInt* cancelled)
|
||||
{
|
||||
// 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();
|
||||
|
||||
SwrContext* resampler = nullptr;
|
||||
AVSampleFormat src_sample_fmt = static_cast<AVSampleFormat>(index_instance.stream()->codecpar->format);
|
||||
AVSampleFormat dst_sample_fmt;
|
||||
|
||||
// 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);
|
||||
|
||||
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);
|
||||
|
||||
swr_init(resampler);
|
||||
} else {
|
||||
dst_sample_fmt = src_sample_fmt;
|
||||
}
|
||||
|
||||
AudioRenderingParams wave_params(index_instance.stream()->codecpar->sample_rate,
|
||||
channel_layout,
|
||||
FFmpegCommon::GetNativeSampleFormat(dst_sample_fmt));
|
||||
WaveOutput wave_out(GetIndexFilename(), wave_params);
|
||||
|
||||
AVPacket* pkt = av_packet_alloc();
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
int ret;
|
||||
|
||||
if (wave_out.open()) {
|
||||
bool success = false;
|
||||
|
||||
while (true) {
|
||||
// Check if we have a `cancelled` ptr and its value
|
||||
if (cancelled && *cancelled) {
|
||||
break;
|
||||
}
|
||||
|
||||
ret = index_instance.GetFrame(pkt, frame);
|
||||
|
||||
if (ret < 0) {
|
||||
|
||||
if (ret == AVERROR_EOF) {
|
||||
success = true;
|
||||
} else {
|
||||
char err_str[50];
|
||||
av_strerror(ret, err_str, 50);
|
||||
qWarning() << "Failed to index:" << 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);
|
||||
}
|
||||
}
|
||||
|
||||
wave_out.close();
|
||||
|
||||
if (success) {
|
||||
audio_stream->set_index_done(true);
|
||||
} else {
|
||||
// Audio index didn't complete, delete it
|
||||
QFile(GetIndexFilename()).remove();
|
||||
audio_stream->clear_index();
|
||||
}
|
||||
} else {
|
||||
qWarning() << "Failed to open WAVE output for indexing";
|
||||
}
|
||||
|
||||
if (resampler != nullptr) {
|
||||
swr_free(&resampler);
|
||||
}
|
||||
|
||||
av_frame_free(&frame);
|
||||
av_packet_free(&pkt);
|
||||
}
|
||||
|
||||
int FFmpegDecoderInstance::GetFrame(AVPacket *pkt, AVFrame *frame)
|
||||
{
|
||||
bool eof = false;
|
||||
|
||||
@@ -144,7 +144,8 @@ public:
|
||||
virtual bool SupportsVideo() override;
|
||||
virtual bool SupportsAudio() override;
|
||||
|
||||
virtual void Index(const QAtomicInt *cancelled) override;
|
||||
virtual void ProxyVideo(const QAtomicInt* cancelled, int divider) override;
|
||||
virtual void ProxyAudio(const QAtomicInt* cancelled) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
@@ -168,8 +169,6 @@ private:
|
||||
|
||||
virtual QString GetIndexFilename() override;
|
||||
|
||||
void UnconditionalAudioIndex(const QAtomicInt* cancelled);
|
||||
|
||||
void ClearResources();
|
||||
|
||||
void InitScaler(int divider);
|
||||
|
||||
+14
-2
@@ -370,8 +370,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()
|
||||
@@ -783,6 +792,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 +816,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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -67,7 +67,7 @@ void AudioStream::set_sample_rate(const int &sample_rate)
|
||||
|
||||
const rational &AudioStream::index_length()
|
||||
{
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
QMutexLocker locker(index_access_lock());
|
||||
|
||||
return index_length_;
|
||||
}
|
||||
@@ -75,7 +75,7 @@ const rational &AudioStream::index_length()
|
||||
void AudioStream::set_index_length(const rational &index_length)
|
||||
{
|
||||
{
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
QMutexLocker locker(index_access_lock());
|
||||
|
||||
index_length_ = index_length;
|
||||
}
|
||||
@@ -85,7 +85,7 @@ void AudioStream::set_index_length(const rational &index_length)
|
||||
|
||||
const bool &AudioStream::index_done()
|
||||
{
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
QMutexLocker locker(index_access_lock());
|
||||
|
||||
return index_done_;
|
||||
}
|
||||
@@ -93,7 +93,7 @@ const bool &AudioStream::index_done()
|
||||
void AudioStream::set_index_done(const bool& index_done)
|
||||
{
|
||||
{
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
QMutexLocker locker(index_access_lock());
|
||||
|
||||
index_done_ = index_done;
|
||||
}
|
||||
@@ -103,7 +103,7 @@ void AudioStream::set_index_done(const bool& index_done)
|
||||
|
||||
void AudioStream::clear_index()
|
||||
{
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
QMutexLocker locker(index_access_lock());
|
||||
|
||||
index_done_ = false;
|
||||
index_length_ = 0;
|
||||
@@ -111,7 +111,7 @@ void AudioStream::clear_index()
|
||||
|
||||
bool AudioStream::has_conformed_version(const AudioRenderingParams ¶ms)
|
||||
{
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
QMutexLocker locker(index_access_lock());
|
||||
|
||||
foreach (const AudioRenderingParams& p, conformed_) {
|
||||
if (p == params) {
|
||||
@@ -125,7 +125,7 @@ bool AudioStream::has_conformed_version(const AudioRenderingParams ¶ms)
|
||||
void AudioStream::append_conformed_version(const AudioRenderingParams ¶ms)
|
||||
{
|
||||
{
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
QMutexLocker locker(index_access_lock());
|
||||
|
||||
conformed_.append(params);
|
||||
}
|
||||
|
||||
@@ -68,7 +68,6 @@ private:
|
||||
uint64_t layout_;
|
||||
int sample_rate_;
|
||||
|
||||
QMutex index_access_lock_;
|
||||
rational index_length_;
|
||||
bool index_done_;
|
||||
|
||||
|
||||
@@ -139,16 +139,16 @@ QIcon Stream::IconFromType(const Stream::Type &type)
|
||||
return QIcon();
|
||||
}
|
||||
|
||||
/*StreamID Stream::ToID() const
|
||||
{
|
||||
return StreamID(footage_->filename(), index_);
|
||||
}*/
|
||||
|
||||
QMutex* Stream::index_process_lock()
|
||||
{
|
||||
return &index_process_lock_;
|
||||
}
|
||||
|
||||
QMutex *Stream::index_access_lock()
|
||||
{
|
||||
return &index_access_lock_;
|
||||
}
|
||||
|
||||
void Stream::FootageSetEvent(Footage*)
|
||||
{
|
||||
}
|
||||
@@ -162,10 +162,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,8 @@ public:
|
||||
|
||||
static QIcon IconFromType(const Type& type);
|
||||
|
||||
//StreamID ToID() const;
|
||||
|
||||
QMutex* index_process_lock();
|
||||
QMutex* index_access_lock();
|
||||
|
||||
protected:
|
||||
virtual void FootageSetEvent(Footage*);
|
||||
@@ -134,6 +122,8 @@ private:
|
||||
|
||||
QMutex index_process_lock_;
|
||||
|
||||
QMutex index_access_lock_;
|
||||
|
||||
};
|
||||
|
||||
using StreamPtr = std::shared_ptr<Stream>;
|
||||
|
||||
@@ -73,6 +73,21 @@ void VideoStream::set_image_sequence(bool e)
|
||||
is_image_sequence_ = e;
|
||||
}
|
||||
|
||||
bool VideoStream::has_proxy(const int ÷r)
|
||||
{
|
||||
QMutexLocker locker(index_access_lock());
|
||||
|
||||
return proxies_.contains(divider);
|
||||
}
|
||||
|
||||
void VideoStream::append_proxy(const int ÷r)
|
||||
{
|
||||
QMutexLocker locker(index_access_lock());
|
||||
|
||||
proxies_.append(divider);
|
||||
}
|
||||
|
||||
/*
|
||||
int64_t VideoStream::get_closest_timestamp_in_frame_index(const rational &time)
|
||||
{
|
||||
// Get rough approximation of what the timestamp would be in this timebase
|
||||
@@ -204,5 +219,6 @@ bool VideoStream::save_frame_index(const QString &s)
|
||||
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -49,6 +49,7 @@ public:
|
||||
bool is_image_sequence() const;
|
||||
void set_image_sequence(bool e);
|
||||
|
||||
/*
|
||||
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();
|
||||
@@ -58,6 +59,10 @@ public:
|
||||
|
||||
bool load_frame_index(const QString& s);
|
||||
bool save_frame_index(const QString& s);
|
||||
*/
|
||||
|
||||
bool has_proxy(const int& divider);
|
||||
void append_proxy(const int& divider);
|
||||
|
||||
private:
|
||||
rational frame_rate_;
|
||||
@@ -66,10 +71,10 @@ private:
|
||||
|
||||
int64_t start_time_;
|
||||
|
||||
QMutex index_access_lock_;
|
||||
|
||||
bool is_image_sequence_;
|
||||
|
||||
QVector<int> proxies_;
|
||||
|
||||
};
|
||||
|
||||
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>;
|
||||
|
||||
Reference in New Issue
Block a user