diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index b68cf7194..e090238b1 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -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 *) { } diff --git a/app/codec/decoder.h b/app/codec/decoder.h index ba38502c5..2a6e1a81d 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -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; diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 79487e0e3..c7438cd37 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -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(stream())->set_index_done(true); - std::static_pointer_cast(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(stream())->set_index_done(true); + std::static_pointer_cast(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(av_get_default_channel_layout(index_instance.stream()->codecpar->channels)); } + AudioStreamPtr audio_stream = std::static_pointer_cast(stream()); + + // This should be unnecessary, but just in case... + audio_stream->clear_index(); + + SwrContext* resampler = nullptr; + AVSampleFormat src_sample_fmt = static_cast(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(index_instance.stream()->codecpar->channel_layout), + dst_sample_fmt, + index_instance.stream()->codecpar->sample_rate, + static_cast(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(&data), + nb_samples, + const_cast(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(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(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(av_get_default_channel_layout(index_instance.stream()->codecpar->channels)); - } - - AudioStreamPtr audio_stream = std::static_pointer_cast(stream()); - - // This should be unnecessary, but just in case... - audio_stream->clear_index(); - - SwrContext* resampler = nullptr; - AVSampleFormat src_sample_fmt = static_cast(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(index_instance.stream()->codecpar->channel_layout), - dst_sample_fmt, - index_instance.stream()->codecpar->sample_rate, - static_cast(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(&data), - nb_samples, - const_cast(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(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(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; diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index f05d29541..44d102520 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -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); diff --git a/app/core.cpp b/app/core.cpp index 15699cb5c..93dbc4585 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -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 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: diff --git a/app/dialog/preferences/preferences.cpp b/app/dialog/preferences/preferences.cpp index bddb46e62..872cab711 100644 --- a/app/dialog/preferences/preferences.cpp +++ b/app/dialog/preferences/preferences.cpp @@ -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() diff --git a/app/dialog/projectproperties/projectproperties.cpp b/app/dialog/projectproperties/projectproperties.cpp index 24e53a250..6e79a1aae 100644 --- a/app/dialog/projectproperties/projectproperties.cpp +++ b/app/dialog/projectproperties/projectproperties.cpp @@ -22,7 +22,6 @@ #include #include -#include #include #include #include @@ -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(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 diff --git a/app/dialog/projectproperties/projectproperties.h b/app/dialog/projectproperties/projectproperties.h index 1fed66c6b..b7bafa012 100644 --- a/app/dialog/projectproperties/projectproperties.h +++ b/app/dialog/projectproperties/projectproperties.h @@ -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 . - + ***/ #ifndef PROJECTPROPERTIESDIALOG_H #define PROJECTPROPERTIESDIALOG_H +#include #include #include +#include #include #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 diff --git a/app/project/item/footage/audiostream.cpp b/app/project/item/footage/audiostream.cpp index 1ddfbf95c..6fb93267a 100644 --- a/app/project/item/footage/audiostream.cpp +++ b/app/project/item/footage/audiostream.cpp @@ -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); } diff --git a/app/project/item/footage/audiostream.h b/app/project/item/footage/audiostream.h index 1c5a6c4a5..b3a432309 100644 --- a/app/project/item/footage/audiostream.h +++ b/app/project/item/footage/audiostream.h @@ -68,7 +68,6 @@ private: uint64_t layout_; int sample_rate_; - QMutex index_access_lock_; rational index_length_; bool index_done_; diff --git a/app/project/item/footage/stream.cpp b/app/project/item/footage/stream.cpp index e0fe629f5..6ee029a71 100644 --- a/app/project/item/footage/stream.cpp +++ b/app/project/item/footage/stream.cpp @@ -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 diff --git a/app/project/item/footage/stream.h b/app/project/item/footage/stream.h index 4ba139811..663b15eba 100644 --- a/app/project/item/footage/stream.h +++ b/app/project/item/footage/stream.h @@ -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; diff --git a/app/project/item/footage/videostream.cpp b/app/project/item/footage/videostream.cpp index 8bbd09125..a20899362 100644 --- a/app/project/item/footage/videostream.cpp +++ b/app/project/item/footage/videostream.cpp @@ -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 diff --git a/app/project/item/footage/videostream.h b/app/project/item/footage/videostream.h index 8ce99a688..721df3277 100644 --- a/app/project/item/footage/videostream.h +++ b/app/project/item/footage/videostream.h @@ -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 proxies_; + }; using VideoStreamPtr = std::shared_ptr; diff --git a/app/project/project.h b/app/project/project.h index 5eb7123b9..09c898ff2 100644 --- a/app/project/project.h +++ b/app/project/project.h @@ -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;