diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index 00b3fe8a4..7b5fc8912 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -29,7 +29,9 @@ #include "codec/oiio/oiiodecoder.h" #include "codec/waveinput.h" #include "codec/waveoutput.h" +#include "common/filefunctions.h" #include "task/taskmanager.h" +#include "project/project.h" OLIVE_NAMESPACE_ENTER @@ -175,6 +177,11 @@ QString Decoder::GetConformedFilename(const AudioParams ¶ms) return index_fn; } +QString Decoder::GetIndexFilename() +{ + return QDir(stream_->footage()->project()->cache_path()).filePath(FileFunctions::GetUniqueFileIdentifier(stream()->footage()->filename()).append(QString::number(stream()->index()))); +} + bool Decoder::ConformAudio(const QAtomicInt *, const AudioParams& ) { return false; diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 1e2bfddfa..60c1b6e90 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -242,19 +242,13 @@ signals: protected: 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. - */ - virtual QString GetIndexFilename() const = 0; - /** * @brief Get the destination filename of an audio stream conformed to a set of parameters */ QString GetConformedFilename(const AudioParams ¶ms); + QString GetIndexFilename(); + bool open_; QMutex mutex_; diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 36a57003f..4c54a42df 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -611,40 +611,6 @@ void FFmpegDecoder::Error(const QString &s) ClearResources(); } -QMutex scaler_lock; -void SaveCacheFrame(FFmpegDecoder* decoder, - SwsContext* scaler, - AVFrame* frame, - VideoParams params, - QString dst_fn) -{ - QByteArray converted_buffer(PixelFormat::GetBufferSize(params.format(), - params.width(), - params.height()), - Qt::Uninitialized); - - uint8_t* converted_data = reinterpret_cast(converted_buffer.data()); - int converted_linesize = PixelFormat::GetBufferSize(params.format(), - params.width(), - 1); - - scaler_lock.lock(); - sws_scale(scaler, - frame->data, - frame->linesize, - 0, - frame->height, - &converted_data, - &converted_linesize); - scaler_lock.unlock(); - - if (!FrameHashCache::SaveCacheFrame(dst_fn, converted_buffer.data(), params, converted_linesize)) { - qCritical() <<" Failed to save cache frame" << dst_fn; - } - - av_frame_free(&frame); -} - bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams &p) { // Iterate through each audio frame and extract the PCM data @@ -774,17 +740,6 @@ bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioParams 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; @@ -914,7 +869,6 @@ void FFmpegDecoderInstance::SetWorking(bool working) void FFmpegDecoderInstance::Seek(int64_t timestamp) { - qDebug() << "Seeking to" << timestamp; avcodec_flush_buffers(codec_ctx_); av_seek_frame(fmt_ctx_, avstream_->index, timestamp, AVSEEK_FLAG_BACKWARD); } @@ -1171,14 +1125,6 @@ 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(FrameHashCache::GetFormatExtension()); - return dst_fn; -} - int64_t FFmpegDecoderInstance::RangeStart() const { if (cached_frames_.isEmpty()) { diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index d4118cc2e..9922cac91 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -172,17 +172,11 @@ private: */ void FFmpegError(int error_code); - virtual QString GetIndexFilename() const override; - - 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); diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index ab8fc311a..238c7c6b8 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -220,11 +220,6 @@ bool OIIODecoder::SupportsVideo() return true; } -QString OIIODecoder::GetIndexFilename() const -{ - return QString(); -} - void OIIODecoder::FrameToBuffer(FramePtr frame, OIIO::ImageBuf *buf) { #if OIIO_VERSION < 20112 diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index 990fc3aff..be0b6fca2 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -51,8 +51,6 @@ public: virtual bool SupportsVideo() override; - virtual QString GetIndexFilename() const override; - static void FrameToBuffer(FramePtr frame, OIIO::ImageBuf* buf); static void BufferToFrame(OIIO::ImageBuf* buf, FramePtr frame); diff --git a/app/common/define.h b/app/common/define.h index dd62ba73d..aa968fcf7 100644 --- a/app/common/define.h +++ b/app/common/define.h @@ -42,6 +42,8 @@ const int kProjectIconSizeMaximum = 256; /// The default size an icon in ProjectExplorer can be const int kProjectIconSizeDefault = 64; +const int kBytesInGigabyte = 1073741824; + OLIVE_NAMESPACE_EXIT #define MACRO_NAME_AS_STR(s) #s diff --git a/app/common/filefunctions.cpp b/app/common/filefunctions.cpp index 7ed065829..f087dd90b 100644 --- a/app/common/filefunctions.cpp +++ b/app/common/filefunctions.cpp @@ -50,35 +50,6 @@ QString FileFunctions::GetUniqueFileIdentifier(const QString &filename) return QString(result.toHex()); } -QString FileFunctions::GetMediaIndexLocation() -{ - QDir local_appdata_dir(Config::Current()["DiskCachePath"].toString()); - - QDir media_index_dir = local_appdata_dir.filePath("mediaindex"); - - // Attempt to ensure this folder exists - media_index_dir.mkpath("."); - - return media_index_dir.absolutePath(); -} - -QString FileFunctions::GetMediaIndexFilename(const QString &filename) -{ - return QDir(GetMediaIndexLocation()).filePath(filename); -} - -QString FileFunctions::GetMediaCacheLocation() -{ - QDir local_appdata_dir(Config::Current()["DiskCachePath"].toString()); - - QDir media_cache_dir = local_appdata_dir.filePath("mediacache"); - - // Attempt to ensure this folder exists - media_cache_dir.mkpath("."); - - return media_cache_dir.absolutePath(); -} - QString FileFunctions::GetConfigurationLocation() { if (IsPortable()) { @@ -112,6 +83,30 @@ QString FileFunctions::GetTempFilePath() return temp_path; } +bool FileFunctions::CanCopyDirectoryWithoutOverwriting(const QString& source, const QString& dest) +{ + QFileInfoList info_list = QDir(source).entryInfoList(); + + foreach (const QFileInfo& info, info_list) { + // QDir::NoDotAndDotDot continues to not work, so we have to check manually + if (info.fileName() == QStringLiteral(".") || info.fileName() == QStringLiteral("..")) { + continue; + } + + QString dest_equivalent = QDir(dest).filePath(info.fileName()); + + if (info.isDir()) { + if (!CanCopyDirectoryWithoutOverwriting(info.absoluteFilePath(), dest_equivalent)) { + return false; + } + } else if (QFileInfo::exists(dest_equivalent)) { + return false; + } + } + + return true; +} + void FileFunctions::CopyDirectory(const QString &source, const QString &dest, bool overwrite) { QDir d(source); @@ -152,4 +147,27 @@ void FileFunctions::CopyDirectory(const QString &source, const QString &dest, bo } } +bool FileFunctions::DirectoryIsValid(const QString &dir, bool try_to_create) +{ + // Empty string is invalid + if (dir.isEmpty()) { + return false; + } + + QDir d(dir); + + // If directory already exists, this is valid + if (d.exists()) { + return true; + } + + // If we can create and creation is successful, this is valid + if (try_to_create && d.mkpath(".")) { + return true; + } + + // Otherwise, invalid + return false; +} + OLIVE_NAMESPACE_EXIT diff --git a/app/common/filefunctions.h b/app/common/filefunctions.h index d1f147c9b..fcfb875be 100644 --- a/app/common/filefunctions.h +++ b/app/common/filefunctions.h @@ -42,20 +42,18 @@ public: static QString GetUniqueFileIdentifier(const QString& filename); - static QString GetMediaIndexLocation(); - - static QString GetMediaIndexFilename(const QString& filename); - - static QString GetMediaCacheLocation(); - static QString GetConfigurationLocation(); static QString GetApplicationPath(); static QString GetTempFilePath(); + static bool CanCopyDirectoryWithoutOverwriting(const QString& source, const QString& dest); + static void CopyDirectory(const QString& source, const QString& dest, bool overwrite = false); + static bool DirectoryIsValid(const QString& dir, bool try_to_create); + }; diff --git a/app/config/config.cpp b/app/config/config.cpp index d2c2b0866..ba2d62348 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -97,11 +97,8 @@ void Config::SetDefaults() config_map_["AudioOutput"] = QString(); config_map_["AudioInput"] = QString(); - config_map_["DiskCachePath"] = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); - config_map_["DiskCacheSize"] = 20.0; config_map_["DiskCacheBehind"] = QVariant::fromValue(rational(1)); config_map_["DiskCacheAhead"] = QVariant::fromValue(rational(5)); - config_map_["ClearDiskCacheOnClose"] = false; config_map_["DefaultSequenceWidth"] = 1920; config_map_["DefaultSequenceHeight"] = 1080; diff --git a/app/core.cpp b/app/core.cpp index fbecb02bf..419e612c0 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -177,10 +177,6 @@ void Core::DeclareTypesForQt() void Core::Start() { - // Reset config (Config sets to default on construction already, but we do it again here as a workaround that fixes - // the fact that some of the config paths set by default rely on the app name having been set (in main()) - Config::Current().SetDefaults(); - // Load application config Config::Load(); diff --git a/app/dialog/preferences/tabs/preferencesdisktab.cpp b/app/dialog/preferences/tabs/preferencesdisktab.cpp index a961c5adf..b62e6f965 100644 --- a/app/dialog/preferences/tabs/preferencesdisktab.cpp +++ b/app/dialog/preferences/tabs/preferencesdisktab.cpp @@ -27,12 +27,15 @@ #include #include -#include "render/diskmanager.h" +#include "common/filefunctions.h" OLIVE_NAMESPACE_ENTER PreferencesDiskTab::PreferencesDiskTab() { + // Get default disk cache folder + default_disk_cache_folder_ = DiskManager::instance()->GetDefaultCacheFolder(); + QVBoxLayout* outer_layout = new QVBoxLayout(this); QGroupBox* disk_management_group = new QGroupBox(tr("Disk Management")); @@ -44,15 +47,9 @@ PreferencesDiskTab::PreferencesDiskTab() disk_management_layout->addWidget(new QLabel(tr("Disk Cache Location:")), row, 0); - disk_cache_location_ = new QLineEdit(); - disk_cache_location_->setText(Config::Current()["DiskCachePath"].toString()); - connect(disk_cache_location_, &QLineEdit::textChanged, this, &PreferencesDiskTab::DiskCacheLineEditChanged); + disk_cache_location_ = new PathWidget(default_disk_cache_folder_->GetPath()); disk_management_layout->addWidget(disk_cache_location_, row, 1); - QPushButton* browse_btn = new QPushButton(tr("Browse")); - connect(browse_btn, &QPushButton::clicked, this, &PreferencesDiskTab::BrowseDiskCachePath); - disk_management_layout->addWidget(browse_btn, row, 2); - row++; disk_management_layout->addWidget(new QLabel(tr("Maximum Disk Cache:")), row, 0); @@ -60,20 +57,20 @@ PreferencesDiskTab::PreferencesDiskTab() maximum_cache_slider_ = new FloatSlider(); maximum_cache_slider_->SetFormat(tr("%1 GB")); maximum_cache_slider_->SetMinimum(1.0); - maximum_cache_slider_->SetValue(Config::Current()["DiskCacheSize"].toDouble()); - disk_management_layout->addWidget(maximum_cache_slider_, row, 1, 1, 2); + maximum_cache_slider_->SetValue(static_cast(default_disk_cache_folder_->GetLimit()) / static_cast(kBytesInGigabyte)); + disk_management_layout->addWidget(maximum_cache_slider_, row, 1); row++; clear_cache_btn_ = new QPushButton(tr("Clear Disk Cache")); connect(clear_cache_btn_, &QPushButton::clicked, this, &PreferencesDiskTab::ClearDiskCache); - disk_management_layout->addWidget(clear_cache_btn_, row, 1, 1, 2); + disk_management_layout->addWidget(clear_cache_btn_, row, 1); row++; clear_disk_cache_ = new QCheckBox(tr("Automatically clear disk cache on close")); - clear_disk_cache_->setChecked(Config::Current()["ClearDiskCacheOnClose"].toBool()); - disk_management_layout->addWidget(clear_disk_cache_, row, 1, 1, 2); + clear_disk_cache_->setChecked(default_disk_cache_folder_->GetClearOnClose()); + disk_management_layout->addWidget(clear_disk_cache_, row, 1); QGroupBox* cache_behavior = new QGroupBox(tr("Cache Behavior")); outer_layout->addWidget(cache_behavior); @@ -100,35 +97,51 @@ PreferencesDiskTab::PreferencesDiskTab() outer_layout->addStretch(); } +bool PreferencesDiskTab::Validate() +{ + if (disk_cache_location_->text() != default_disk_cache_folder_->GetPath()) { + // Disk cache location is changing + + // Check if the user wants to move the cache here + if (QMessageBox::question(this, + tr("Disk Cache"), + tr("You've chosen to change the default disk cache location. This " + "will invalidate your current cache. Would you like to continue?"), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) { + return false; + } + + // Check validity of the new path + if (!FileFunctions::DirectoryIsValid(disk_cache_location_->text(), true)) { + QMessageBox::critical(this, + tr("Disk Cache"), + tr("Failed to set disk cache location. Access was denied.")); + return false; + } + } + + return true; +} + void PreferencesDiskTab::Accept() { - Config::Current()["DiskCachePath"] = disk_cache_location_->text(); - Config::Current()["DiskCacheSize"] = maximum_cache_slider_->GetValue(); - Config::Current()["ClearDiskCacheOnClose"] = clear_disk_cache_->isChecked(); + if (disk_cache_location_->text() != default_disk_cache_folder_->GetPath()) { + default_disk_cache_folder_->SetPath(disk_cache_location_->text()); + } + + qint64 new_disk_cache_limit = qRound64(maximum_cache_slider_->GetValue() * kBytesInGigabyte); + if (new_disk_cache_limit != default_disk_cache_folder_->GetLimit()) { + default_disk_cache_folder_->SetLimit(new_disk_cache_limit); + } + + if (default_disk_cache_folder_->GetClearOnClose() != clear_disk_cache_->isChecked()) { + default_disk_cache_folder_->SetClearOnClose(clear_disk_cache_->isChecked()); + } + Config::Current()["DiskCacheBehind"] = QVariant::fromValue(rational::fromDouble(cache_behind_slider_->GetValue())); Config::Current()["DiskCacheAhead"] = QVariant::fromValue(rational::fromDouble(cache_ahead_slider_->GetValue())); } -void PreferencesDiskTab::DiskCacheLineEditChanged() -{ - QString entered_dir = disk_cache_location_->text(); - - if (!entered_dir.isEmpty() && !QDir(entered_dir).exists()) { - disk_cache_location_->setStyleSheet(QStringLiteral("color: red;")); - } else { - disk_cache_location_->setStyleSheet(QString()); - } -} - -void PreferencesDiskTab::BrowseDiskCachePath() -{ - QString dir = QFileDialog::getExistingDirectory(this, tr("Browse for disk cache path"), disk_cache_location_->text()); - - if (!dir.isEmpty()) { - disk_cache_location_->setText(dir); - } -} - void PreferencesDiskTab::ClearDiskCache() { if (QMessageBox::question(this, @@ -137,7 +150,7 @@ void PreferencesDiskTab::ClearDiskCache() QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { clear_cache_btn_->setEnabled(false); - if (DiskManager::instance()->ClearDiskCache(false)) { + if (DiskManager::instance()->ClearDiskCache(default_disk_cache_folder_->GetPath())) { clear_cache_btn_->setText(tr("Disk Cache Cleared")); } else { QMessageBox::information(this, diff --git a/app/dialog/preferences/tabs/preferencesdisktab.h b/app/dialog/preferences/tabs/preferencesdisktab.h index 052c834ad..c087abca6 100644 --- a/app/dialog/preferences/tabs/preferencesdisktab.h +++ b/app/dialog/preferences/tabs/preferencesdisktab.h @@ -26,7 +26,9 @@ #include #include "preferencestab.h" +#include "render/diskmanager.h" #include "widget/slider/floatslider.h" +#include "widget/path/pathwidget.h" OLIVE_NAMESPACE_ENTER @@ -36,10 +38,12 @@ class PreferencesDiskTab : public PreferencesTab public: PreferencesDiskTab(); + virtual bool Validate() override; + virtual void Accept() override; private: - QLineEdit* disk_cache_location_; + PathWidget* disk_cache_location_; FloatSlider* maximum_cache_slider_; @@ -51,11 +55,9 @@ private: QPushButton* clear_cache_btn_; + DiskCacheFolder* default_disk_cache_folder_; + private slots: - void DiskCacheLineEditChanged(); - - void BrowseDiskCachePath(); - void ClearDiskCache(); }; diff --git a/app/dialog/projectproperties/projectproperties.cpp b/app/dialog/projectproperties/projectproperties.cpp index 423ae414f..8a1b61655 100644 --- a/app/dialog/projectproperties/projectproperties.cpp +++ b/app/dialog/projectproperties/projectproperties.cpp @@ -20,6 +20,7 @@ #include "projectproperties.h" +#include #include #include #include @@ -29,6 +30,7 @@ #include namespace OCIO = OCIO_NAMESPACE::v1; +#include "common/filefunctions.h" #include "config/config.h" #include "core.h" #include "render/colormanager.h" @@ -51,7 +53,10 @@ ProjectPropertiesDialog::ProjectPropertiesDialog(Project* p, QWidget *parent) : // Color management group QWidget* color_group = new QWidget(); - QGridLayout* color_layout = new QGridLayout(color_group); + QVBoxLayout* color_outer_layout = new QVBoxLayout(color_group); + + QGridLayout* color_layout = new QGridLayout(); + color_outer_layout->addLayout(color_layout); int row = 0; @@ -80,26 +85,47 @@ ProjectPropertiesDialog::ProjectPropertiesDialog(Project* p, QWidget *parent) : OCIOFilenameUpdated(); tabs->addTab(color_group, tr("Color Management")); + + color_outer_layout->addStretch(); } { - // Paths group - QWidget* paths_group = new QWidget(); + // Cache group + QWidget* cache_group = new QWidget(); - QGridLayout* paths_layout = new QGridLayout(paths_group); + QVBoxLayout* cache_layout = new QVBoxLayout(cache_group); - cache_path_ = new PathWidget(working_project_->cache_path(), this); + QButtonGroup* disk_cache_btn_group = new QButtonGroup(); - int row = 0; + disk_cache_use_default_btn_ = new QRadioButton(tr("Use Default Location")); + disk_cache_store_alongside_project_btn_ = new QRadioButton(tr("Store Alongside Project")); + disk_cache_use_custom_btn_ = new QRadioButton(tr("Use Custom Location:")); - 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); + disk_cache_btn_group->addButton(disk_cache_use_default_btn_); + disk_cache_btn_group->addButton(disk_cache_store_alongside_project_btn_); + disk_cache_btn_group->addButton(disk_cache_use_custom_btn_); - tabs->addTab(paths_group, tr("Paths")); + cache_layout->addWidget(disk_cache_use_default_btn_); + cache_layout->addWidget(disk_cache_store_alongside_project_btn_); + cache_layout->addWidget(disk_cache_use_custom_btn_); + + cache_path_ = new PathWidget(working_project_->cache_path(false), this); + cache_path_->setEnabled(false); + cache_layout->addWidget(cache_path_); + + connect(disk_cache_use_custom_btn_, &QRadioButton::toggled, cache_path_, &PathWidget::setEnabled); + + if (working_project_->cache_path(false).isEmpty()) { + disk_cache_use_default_btn_->setChecked(true); + } else { + disk_cache_use_custom_btn_->setChecked(true); + } + + cache_layout->addWidget(cache_path_); + + tabs->addTab(cache_group, tr("Disk Cache")); } QDialogButtonBox* dialog_btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, @@ -122,18 +148,26 @@ void ProjectPropertiesDialog::accept() 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(); + if (disk_cache_use_default_btn_->isChecked()) { + // Empty cache path means default + working_project_->set_cache_path(QString()); + } else if (disk_cache_store_alongside_project_btn_->isChecked()) { + QMessageBox::information(this, QString(), tr("\"Store alignside project\" functionality not implemented yet")); return; - } + } else { + if (!FileFunctions::DirectoryIsValid(cache_path_->text(), 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; + } - working_project_->set_cache_path(cache_path_->path_edit()->text()); + working_project_->set_cache_path(cache_path_->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(), @@ -184,54 +218,4 @@ void ProjectPropertiesDialog::OCIOFilenameUpdated() } } -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 0f7abdfab..5c0f31347 100644 --- a/app/dialog/projectproperties/projectproperties.h +++ b/app/dialog/projectproperties/projectproperties.h @@ -26,48 +26,13 @@ #include #include #include +#include #include "project/project.h" +#include "widget/path/pathwidget.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 @@ -89,6 +54,12 @@ private: QString ocio_config_error_; PathWidget* cache_path_; + + QRadioButton* disk_cache_use_default_btn_; + + QRadioButton* disk_cache_store_alongside_project_btn_; + + QRadioButton* disk_cache_use_custom_btn_; private slots: void BrowseForOCIOConfig(); diff --git a/app/project/project.h b/app/project/project.h index ff3f8ce35..2a806c29c 100644 --- a/app/project/project.h +++ b/app/project/project.h @@ -25,6 +25,7 @@ #include #include "render/colormanager.h" +#include "render/diskmanager.h" #include "project/item/folder/folder.h" #include "window/mainwindow/mainwindowlayoutinfo.h" @@ -71,7 +72,10 @@ public: bool is_new() const; - const QString& cache_path() const { + const QString& cache_path(bool default_if_empty = true) const { + if (cache_path_.isEmpty() && default_if_empty) { + return DiskManager::instance()->GetDefaultCachePath(); + } return cache_path_; } diff --git a/app/render/audioplaybackcache.cpp b/app/render/audioplaybackcache.cpp index 73e76a067..3a1a78a34 100644 --- a/app/render/audioplaybackcache.cpp +++ b/app/render/audioplaybackcache.cpp @@ -249,7 +249,7 @@ QList AudioPlaybackCache::NoLockGetValidRanges(const TimeRange& range void AudioPlaybackCache::UpdateFilename(const QString &s) { - filename_ = QDir(FileFunctions::GetMediaCacheLocation()).filePath(s); + filename_ = QDir(GetCacheFilename()).filePath(s); filename_.append(QStringLiteral(".pcm")); } diff --git a/app/render/backend/renderbackend.cpp b/app/render/backend/renderbackend.cpp index 762dfeabd..1de50ab2c 100644 --- a/app/render/backend/renderbackend.cpp +++ b/app/render/backend/renderbackend.cpp @@ -396,6 +396,7 @@ void RenderBackend::RunNextJob() worker->SetRenderMode(render_mode_); worker->SetPreviewGenerationEnabled(generate_audio_previews_); worker->SetCopyMap(©_map_); + worker->SetViewerNode(viewer_node_); // Move ticket from queue to running list RenderTicketPtr ticket = render_queue_.front(); @@ -592,7 +593,8 @@ void RenderBackend::AutoCacheVideoRendered() QFutureWatcher* w = new QFutureWatcher(); autocache_video_download_tasks_.insert(w, hash); connect(w, &QFutureWatcher::finished, this, &RenderBackend::AutoCacheVideoDownloaded); - w->setFuture(QtConcurrent::run(FrameHashCache::SaveCacheFrame, + w->setFuture(QtConcurrent::run(viewer_node_->video_frame_cache(), + &FrameHashCache::SaveCacheFrame, hash, watcher->Get().value())); } diff --git a/app/render/backend/renderworker.cpp b/app/render/backend/renderworker.cpp index 8eb34ef8f..5b670fc0d 100644 --- a/app/render/backend/renderworker.cpp +++ b/app/render/backend/renderworker.cpp @@ -274,21 +274,17 @@ QVariant RenderWorker::GetCachedFrame(const Node* node, const rational& time) if (node->id() == QStringLiteral("org.olivevideoeditor.Olive.videoinput")) { QByteArray hash = HashNode(node, video_params(), time); - QString fn = FrameHashCache::CachePathName(hash); + FramePtr f = viewer_->video_frame_cache()->LoadCacheFrame(hash); - if (QFileInfo::exists(fn)) { - FramePtr f = FrameHashCache::LoadCacheFrame(hash); + if (f) { + // The cached frame won't load with the correct divider by default, so we enforce it here + f->set_video_params(VideoParams(f->width() * video_params_.divider(), + f->height() * video_params_.divider(), + f->video_params().time_base(), + f->video_params().format(), + video_params_.divider())); - if (f) { - // The cached frame won't load with the correct divider by default, so we enforce it here - f->set_video_params(VideoParams(f->width() * video_params_.divider(), - f->height() * video_params_.divider(), - f->video_params().time_base(), - f->video_params().format(), - video_params_.divider())); - - return CachedFrameToTexture(f); - } + return CachedFrameToTexture(f); } } diff --git a/app/render/backend/renderworker.h b/app/render/backend/renderworker.h index 44b945b5a..a709663d8 100644 --- a/app/render/backend/renderworker.h +++ b/app/render/backend/renderworker.h @@ -48,6 +48,11 @@ public: available_ = a; } + void SetViewerNode(ViewerOutput* viewer) + { + viewer_ = viewer; + } + void SetVideoParams(const VideoParams& params) { video_params_ = params; @@ -171,6 +176,8 @@ private: bool generate_audio_previews_; + ViewerOutput* viewer_; + QHash* copy_map_; RenderMode::Mode render_mode_; diff --git a/app/render/diskmanager.cpp b/app/render/diskmanager.cpp index 2c609f357..076bf0475 100644 --- a/app/render/diskmanager.cpp +++ b/app/render/diskmanager.cpp @@ -25,62 +25,56 @@ #include #include #include +#include #include #include "common/filefunctions.h" #include "config/config.h" +#include "core.h" OLIVE_NAMESPACE_ENTER DiskManager* DiskManager::instance_ = nullptr; -DiskManager::DiskManager() : - consumption_(0) +DiskManager::DiskManager() { - // Try to load any current cache index from file - QFile cache_index_file(GetCacheIndexFilename()); + // Add default cache location + QFile default_disk_cache_file(QDir(FileFunctions::GetConfigurationLocation()).filePath(QStringLiteral("defaultdiskcache"))); + if (default_disk_cache_file.open(QFile::ReadOnly)) { + QString default_dir = default_disk_cache_file.readAll(); - if (cache_index_file.open(QFile::ReadOnly)) { - QDataStream ds(&cache_index_file); - - while (!cache_index_file.atEnd()) { - HashTime h; - - ds >> h.file_name; - ds >> h.hash; - ds >> h.access_time; - ds >> h.file_size; - - if (QFileInfo::exists(h.file_name)) { - consumption_ += h.file_size; - disk_data_.append(h); - } + if (FileFunctions::DirectoryIsValid(default_dir, true)) { + GetOpenFolder(default_dir); + } else { + QMessageBox::warning(nullptr, + tr("Disk Cache Error"), + tr("Unable to set custom application disk cache. Using default instead.")); } + + default_disk_cache_file.close(); + } + + // If no custom default was loaded, load default + if (open_folders_.isEmpty()) { + GetOpenFolder(QDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("mediacache")); + } + + QFile disk_cache_index(QDir(FileFunctions::GetConfigurationLocation()).filePath(QStringLiteral("diskcache"))); + + if (disk_cache_index.open(QFile::ReadOnly)) { + QTextStream stream(&disk_cache_index); + + QString line; + while (stream.readLineInto(&line)) { + GetOpenFolder(line); + } + + disk_cache_index.close(); } } DiskManager::~DiskManager() { - if (Config::Current()["ClearDiskCacheOnClose"].toBool()) { - // Clear all cache data - ClearDiskCache(true); - } else { - // Save current cache index - QFile cache_index_file(GetCacheIndexFilename()); - - if (cache_index_file.open(QFile::WriteOnly)) { - QDataStream ds(&cache_index_file); - - foreach (const HashTime& h, disk_data_) { - ds << h.file_name; - ds << h.hash; - ds << h.access_time; - ds << h.file_size; - } - } else { - qWarning() << "Failed to write cache index:" << GetCacheIndexFilename(); - } - } } void DiskManager::CreateInstance() @@ -99,107 +93,178 @@ DiskManager *DiskManager::instance() return instance_; } -void DiskManager::Accessed(const QByteArray &hash) +void DiskManager::Accessed(const QString &cache_folder, const QByteArray &hash) { - lock_.lock(); + DiskCacheFolder* f = GetOpenFolder(cache_folder); - for (int i=disk_data_.size()-1;i>=0;i--) { - const HashTime& h = disk_data_.at(i); + f->Accessed(hash); +} - if (h.hash == hash) { - HashTime moved_hash = h; +void DiskManager::CreatedFile(const QString &cache_folder, const QString &file_name, const QByteArray &hash) +{ + DiskCacheFolder* f = GetOpenFolder(cache_folder); - moved_hash.access_time = QDateTime::currentMSecsSinceEpoch(); + f->CreatedFile(file_name, hash); +} - disk_data_.removeAt(i); - disk_data_.append(moved_hash); - break; +bool DiskManager::ClearDiskCache(const QString &cache_folder) +{ + DiskCacheFolder* f = GetOpenFolder(cache_folder); + + return f->ClearCache(); +} + +DiskCacheFolder *DiskManager::GetOpenFolder(const QString &path) +{ + // If path is empty, this must mean default + if (path.isEmpty()) { + return GetDefaultCacheFolder(); + } + + // See if we have an existing path with this name + foreach (DiskCacheFolder* f, open_folders_) { + if (f->GetPath() == path) { + return f; } } - lock_.unlock(); + // We must have to open this folder + DiskCacheFolder* f = new DiskCacheFolder(path, this); + connect(f, &DiskCacheFolder::DeletedFrame, this, &DiskManager::DeletedFrame); + open_folders_.append(f); + + return f; } -void DiskManager::Accessed(const QString &filename) +DiskCacheFolder::DiskCacheFolder(const QString &path, QObject *parent) : + QObject(parent) { - lock_.lock(); + SetPath(path); +} - for (int i=disk_data_.size()-1;i>=0;i--) { - const HashTime& h = disk_data_.at(i); +DiskCacheFolder::~DiskCacheFolder() +{ + CloseCacheFolder(); +} - if (h.file_name == filename) { - HashTime moved_hash = h; +bool DiskCacheFolder::ClearCache() +{ + bool deleted_files = true; - moved_hash.access_time = QDateTime::currentMSecsSinceEpoch(); + std::list::iterator i = disk_data_.begin(); - disk_data_.removeAt(i); - disk_data_.append(moved_hash); - break; + while (i != disk_data_.end()) { + // We return a false result if any of the files fail to delete, but still try to delete as many as we can + if (QFile::remove(i->file_name) || !QFileInfo::exists(i->file_name)) { + emit DeletedFrame(path_, i->hash); + i = disk_data_.erase(i); + } else { + qWarning() << "Failed to delete" << i->file_name; + deleted_files = false; + i++; } } - lock_.unlock(); + return deleted_files; } -void DiskManager::CreatedFile(const QString &file_name, const QByteArray &hash) +void DiskCacheFolder::Accessed(const QByteArray &hash) { - lock_.lock(); + std::list::iterator i = disk_data_.begin(); + while (i != disk_data_.end()) { + if (i->hash == hash) { + // Copy access data and erase from list + HashTime accessed_hash = *i; + disk_data_.erase(i); + + // Add it to the end + disk_data_.push_back(accessed_hash); + + // End loop + break; + } else { + i++; + } + } +} + +void DiskCacheFolder::CreatedFile(const QString &file_name, const QByteArray &hash) +{ qint64 file_size = QFile(file_name).size(); - disk_data_.append({file_name, hash, QDateTime::currentMSecsSinceEpoch(), file_size}); + disk_data_.push_back({file_name, hash, file_size}); consumption_ += file_size; QList deleted_hashes; - while (consumption_ > DiskLimit()) { + while (consumption_ > limit_) { deleted_hashes.append(DeleteLeastRecent()); } - lock_.unlock(); - foreach (const QByteArray& h, deleted_hashes) { - emit DeletedFrame(h); + emit DeletedFrame(path_, h); } } -bool DiskManager::ClearDiskCache(bool quick_delete) +void DiskCacheFolder::SetPath(const QString &path) { - bool deleted_files; - - lock_.lock(); - - if (quick_delete) { - deleted_files = QDir(FileFunctions::GetMediaCacheLocation()).removeRecursively(); + // If this is currently set to a folder, close it out now + CloseCacheFolder(); + // Signal that disk cache is gone + if (!disk_data_.empty()) { + foreach (const HashTime& h, disk_data_) { + emit DeletedFrame(path_, h.hash); + } disk_data_.clear(); - } else { - deleted_files = true; + } - for (int i=0;i> limit_; + ds >> clear_on_close_; + + while (!cache_index_file.atEnd()) { + HashTime h; + + ds >> h.file_name; + ds >> h.hash; + ds >> h.file_size; + + if (QFileInfo::exists(h.file_name)) { + consumption_ += h.file_size; + disk_data_.push_back(h); } } + + cache_index_file.close(); } - - lock_.unlock(); - - return deleted_files; } -QByteArray DiskManager::DeleteLeastRecent() +QByteArray DiskCacheFolder::DeleteLeastRecent() { - HashTime h = disk_data_.takeFirst(); + HashTime h = disk_data_.front(); + disk_data_.pop_front(); QFile::remove(h.file_name); @@ -208,19 +273,37 @@ QByteArray DiskManager::DeleteLeastRecent() return h.hash; } -qint64 DiskManager::DiskLimit() +void DiskCacheFolder::CloseCacheFolder() { - double gigabytes = Config::Current()["DiskCacheSize"].toDouble(); + if (path_.isEmpty()) { + return; + } - // Convert gigabytes to bytes - return qRound64(gigabytes * 1073741824); -} + if (clear_on_close_) { + // If we're not moving to new and we're set to clear on close, clear now or else it'll never + // get cleared later + ClearCache(); + } -QString DiskManager::GetCacheIndexFilename() -{ - QDir d(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)); - d.mkpath("."); - return d.filePath("diskindex"); + // Save current cache index + QFile cache_index_file(index_path_); + + if (cache_index_file.open(QFile::WriteOnly)) { + QDataStream ds(&cache_index_file); + + ds << limit_; + ds << clear_on_close_; + + foreach (const HashTime& h, disk_data_) { + ds << h.file_name; + ds << h.hash; + ds << h.file_size; + } + + cache_index_file.close(); + } else { + qWarning() << "Failed to write cache index:" << index_path_; + } } OLIVE_NAMESPACE_EXIT diff --git a/app/render/diskmanager.h b/app/render/diskmanager.h index 4df5f6d36..551534c4f 100644 --- a/app/render/diskmanager.h +++ b/app/render/diskmanager.h @@ -21,6 +21,7 @@ #ifndef DISKMANAGER_H #define DISKMANAGER_H +#include #include #include @@ -28,6 +29,75 @@ OLIVE_NAMESPACE_ENTER +class DiskCacheFolder : public QObject +{ + Q_OBJECT +public: + DiskCacheFolder(const QString& path, QObject* parent = nullptr); + + virtual ~DiskCacheFolder() override; + + bool ClearCache(); + + void Accessed(const QByteArray& hash); + + void CreatedFile(const QString& file_name, const QByteArray& hash); + + const QString& GetPath() const + { + return path_; + } + + void SetPath(const QString& path); + + qint64 GetLimit() const + { + return limit_; + } + + bool GetClearOnClose() const + { + return clear_on_close_; + } + + void SetLimit(qint64 l) + { + limit_ = l; + } + + void SetClearOnClose(bool e) + { + clear_on_close_ = e; + } + +signals: + void DeletedFrame(const QString& path, const QByteArray& hash); + +private: + QByteArray DeleteLeastRecent(); + + void CloseCacheFolder(); + + QString path_; + + QString index_path_; + + struct HashTime { + QString file_name; + QByteArray hash; + qint64 file_size; + }; + + std::list disk_data_; + + qint64 consumption_; + + qint64 limit_; + + bool clear_on_close_; + +}; + class DiskManager : public QObject { Q_OBJECT @@ -38,16 +108,33 @@ public: static DiskManager* instance(); - void Accessed(const QByteArray& hash); + bool ClearDiskCache(const QString& cache_folder); - void Accessed(const QString& filename); + DiskCacheFolder* GetDefaultCacheFolder() const + { + // The first folder will always be the default + return open_folders_.first(); + } - void CreatedFile(const QString& file_name, const QByteArray& hash); + const QString& GetDefaultCachePath() const + { + return GetDefaultCacheFolder()->GetPath(); + } - bool ClearDiskCache(bool quick_delete); + DiskCacheFolder* GetOpenFolder(const QString& path); + + const QVector& GetOpenFolders() const + { + return open_folders_; + } + +public slots: + void Accessed(const QString& cache_folder, const QByteArray& hash); + + void CreatedFile(const QString& cache_folder, const QString& file_name, const QByteArray& hash); signals: - void DeletedFrame(const QByteArray& hash); + void DeletedFrame(const QString& path, const QByteArray& hash); private: DiskManager(); @@ -56,24 +143,7 @@ private: static DiskManager* instance_; - QByteArray DeleteLeastRecent(); - - qint64 DiskLimit(); - - static QString GetCacheIndexFilename(); - - struct HashTime { - QString file_name; - QByteArray hash; - qint64 access_time; - qint64 file_size; - }; - - QList disk_data_; - - qint64 consumption_; - - QMutex lock_; + QVector open_folders_; }; diff --git a/app/render/framehashcache.cpp b/app/render/framehashcache.cpp index 6d5b19463..f4e980446 100644 --- a/app/render/framehashcache.cpp +++ b/app/render/framehashcache.cpp @@ -227,13 +227,18 @@ QVector FrameHashCache::GetInvalidatedFrames(const TimeRange &intersec bool FrameHashCache::SaveCacheFrame(const QByteArray& hash, char* data, const VideoParams& vparam, - int linesize_bytes) + int linesize_bytes) const { QString fn = CachePathName(hash); if (SaveCacheFrame(fn, data, vparam, linesize_bytes)) { // Register frame with the disk manager - DiskManager::instance()->CreatedFile(fn, hash); + QMetaObject::invokeMethod(DiskManager::instance(), + "CreatedFile", + Qt::QueuedConnection, + Q_ARG(QString, GetCacheDirectory()), + Q_ARG(QString, fn), + Q_ARG(QByteArray, hash)); return true; } else { @@ -241,7 +246,7 @@ bool FrameHashCache::SaveCacheFrame(const QByteArray& hash, } } -bool FrameHashCache::SaveCacheFrame(const QByteArray &hash, FramePtr frame) +bool FrameHashCache::SaveCacheFrame(const QByteArray &hash, FramePtr frame) const { if (frame) { return SaveCacheFrame(hash, frame->data(), frame->video_params(), frame->linesize_bytes()); @@ -251,12 +256,12 @@ bool FrameHashCache::SaveCacheFrame(const QByteArray &hash, FramePtr frame) } } -FramePtr FrameHashCache::LoadCacheFrame(const QByteArray &hash) +FramePtr FrameHashCache::LoadCacheFrame(const QByteArray &hash) const { return LoadCacheFrame(CachePathName(hash)); } -FramePtr FrameHashCache::LoadCacheFrame(const QString &fn) +FramePtr FrameHashCache::LoadCacheFrame(const QString &fn) const { FramePtr frame = nullptr; @@ -367,8 +372,13 @@ void FrameHashCache::ShiftEvent(const rational &from, const rational &to) } } -void FrameHashCache::HashDeleted(const QByteArray &hash) +void FrameHashCache::HashDeleted(const QString& s, const QByteArray &hash) { + QString cache_dir = GetCacheDirectory(); + if (cache_dir.isEmpty() || s != cache_dir) { + return; + } + QMutexLocker locker(lock()); TimeRangeList invalidated; @@ -390,22 +400,26 @@ void FrameHashCache::HashDeleted(const QByteArray &hash) } } -QString FrameHashCache::CachePathName(const QByteArray& hash) +QString FrameHashCache::CachePathName(const QByteArray& hash) const { QString ext = GetFormatExtension(); - QDir cache_dir(QDir(FileFunctions::GetMediaCacheLocation()).filePath(QString(hash.left(1).toHex()))); + QDir cache_dir(QDir(GetCacheDirectory()).filePath(QString(hash.left(1).toHex()))); cache_dir.mkpath("."); QString filename = QStringLiteral("%1%2").arg(QString(hash.mid(1).toHex()), ext); // Register that in some way this hash has been accessed - DiskManager::instance()->Accessed(hash); + QMetaObject::invokeMethod(DiskManager::instance(), + "Accessed", + Qt::QueuedConnection, + Q_ARG(QString, GetCacheDirectory()), + Q_ARG(QByteArray, hash)); return cache_dir.filePath(filename); } -bool FrameHashCache::SaveCacheFrame(const QString &filename, char *data, const VideoParams &vparam, int linesize_bytes) +bool FrameHashCache::SaveCacheFrame(const QString &filename, char *data, const VideoParams &vparam, int linesize_bytes) const { Q_ASSERT(PixelFormat::FormatIsFloat(vparam.format())); diff --git a/app/render/framehashcache.h b/app/render/framehashcache.h index 60bac1042..5c6fe8c58 100644 --- a/app/render/framehashcache.h +++ b/app/render/framehashcache.h @@ -58,13 +58,13 @@ public: /** * @brief Return the path of the cached image at this time */ - static QString CachePathName(const QByteArray &hash); + QString CachePathName(const QByteArray &hash) const; - static bool SaveCacheFrame(const QString& filename, char *data, const VideoParams &vparam, int linesize_bytes); - static bool SaveCacheFrame(const QByteArray& hash, char *data, const VideoParams &vparam, int linesize_bytes); - static bool SaveCacheFrame(const QByteArray& hash, FramePtr frame); - static FramePtr LoadCacheFrame(const QByteArray& hash); - static FramePtr LoadCacheFrame(const QString& fn); + bool SaveCacheFrame(const QString& filename, char *data, const VideoParams &vparam, int linesize_bytes) const; + bool SaveCacheFrame(const QByteArray& hash, char *data, const VideoParams &vparam, int linesize_bytes) const; + bool SaveCacheFrame(const QByteArray& hash, FramePtr frame) const; + FramePtr LoadCacheFrame(const QByteArray& hash) const; + FramePtr LoadCacheFrame(const QString& fn) const; static QString GetFormatExtension(); @@ -87,7 +87,7 @@ private: rational timebase_; private slots: - void HashDeleted(const QByteArray& hash); + void HashDeleted(const QString &s, const QByteArray& hash); }; diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp index 9bce6a504..3bde6ebe5 100644 --- a/app/render/playbackcache.cpp +++ b/app/render/playbackcache.cpp @@ -22,6 +22,10 @@ #include +#include "node/output/viewer/viewer.h" +#include "project/item/sequence/sequence.h" +#include "project/project.h" + OLIVE_NAMESPACE_ENTER void PlaybackCache::Invalidate(const TimeRange &r) @@ -190,4 +194,25 @@ void PlaybackCache::RemoveRangeFromJobs(const TimeRange &remove) } } +QString PlaybackCache::GetCacheDirectory() const +{ + // NOTE: A lot of assumptions in this behavior + ViewerOutput* viewer = static_cast(parent()); + if (!viewer) { + return QString(); + } + + Sequence* sequence = static_cast(viewer->parent()); + if (!sequence) { + return QString(); + } + + Project* project = sequence->project(); + if (!project) { + return QString(); + } + + return project->cache_path(); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/render/playbackcache.h b/app/render/playbackcache.h index ce54bdd7e..881476830 100644 --- a/app/render/playbackcache.h +++ b/app/render/playbackcache.h @@ -103,6 +103,8 @@ protected: virtual void ShiftEvent(const rational& from, const rational& to); + QString GetCacheDirectory() const; + QMutex* lock() { return &lock_; diff --git a/app/task/precache/precachetask.cpp b/app/task/precache/precachetask.cpp index 220fb36f2..c8baf5bd3 100644 --- a/app/task/precache/precachetask.cpp +++ b/app/task/precache/precachetask.cpp @@ -75,7 +75,7 @@ bool PreCacheTask::Run() QFuture PreCacheTask::DownloadFrame(FramePtr frame, const QByteArray &hash) { - return QtConcurrent::run(&download_threads_, FrameHashCache::SaveCacheFrame, hash, frame); + return QtConcurrent::run(&download_threads_, viewer()->video_frame_cache(), &FrameHashCache::SaveCacheFrame, hash, frame); } void PreCacheTask::FrameDownloaded(const QByteArray &hash, const std::list ×, qint64 job_time) diff --git a/app/widget/CMakeLists.txt b/app/widget/CMakeLists.txt index 67c20349e..01ba47053 100644 --- a/app/widget/CMakeLists.txt +++ b/app/widget/CMakeLists.txt @@ -33,6 +33,7 @@ add_subdirectory(nodeview) add_subdirectory(nodeparamview) add_subdirectory(nodetableview) add_subdirectory(panel) +add_subdirectory(path) add_subdirectory(pixelsampler) add_subdirectory(playbackcontrols) add_subdirectory(projectexplorer) diff --git a/app/widget/path/CMakeLists.txt b/app/widget/path/CMakeLists.txt new file mode 100644 index 000000000..a7bd17910 --- /dev/null +++ b/app/widget/path/CMakeLists.txt @@ -0,0 +1,22 @@ +# 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 . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + widget/path/pathwidget.h + widget/path/pathwidget.cpp + PARENT_SCOPE +) diff --git a/app/widget/path/pathwidget.cpp b/app/widget/path/pathwidget.cpp new file mode 100644 index 000000000..b646e2d40 --- /dev/null +++ b/app/widget/path/pathwidget.cpp @@ -0,0 +1,68 @@ +/*** + + 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 . + +***/ + +#include "pathwidget.h" + +#include +#include +#include + +#include "common/filefunctions.h" + +OLIVE_NAMESPACE_ENTER + +PathWidget::PathWidget(const QString &path, QWidget *parent) : + QWidget(parent) +{ + QHBoxLayout* layout = new QHBoxLayout(this); + layout->setMargin(0); + + path_edit_ = new QLineEdit(); + path_edit_->setText(path); + layout->addWidget(path_edit_); + connect(path_edit_, &QLineEdit::textChanged, this, &PathWidget::LineEditChanged); + + browse_btn_ = new QPushButton(tr("Browse")); + layout->addWidget(browse_btn_); + + connect(browse_btn_, &QPushButton::clicked, this, &PathWidget::BrowseClicked); +} + +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 (FileFunctions::DirectoryIsValid(text(), false)) { + path_edit_->setStyleSheet(QString()); + } else { + path_edit_->setStyleSheet(QStringLiteral("QLineEdit {color: red;}")); + } +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/widget/path/pathwidget.h b/app/widget/path/pathwidget.h new file mode 100644 index 000000000..c35eae30e --- /dev/null +++ b/app/widget/path/pathwidget.h @@ -0,0 +1,57 @@ +/*** + + 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 PATHWIDGET_H +#define PATHWIDGET_H + +#include +#include + +#include "common/define.h" + +OLIVE_NAMESPACE_ENTER + +class PathWidget : public QWidget +{ + Q_OBJECT +public: + PathWidget(const QString& path, + QWidget* parent = nullptr); + + QString text() const + { + return path_edit_->text(); + } + +private slots: + void BrowseClicked(); + + void LineEditChanged(); + +private: + QLineEdit* path_edit_; + + QPushButton* browse_btn_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // PATHWIDGET_H diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 9ae33e60c..d8f561bd0 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -361,9 +361,9 @@ void ViewerWidget::SetGizmos(Node *node) display_widget_->SetGizmos(node); } -FramePtr DecodeCachedImage(const QString &fn, const rational& time) +FramePtr ViewerWidget::DecodeCachedImage(const QString &fn, const rational& time) const { - FramePtr frame = FrameHashCache::LoadCacheFrame(fn); + FramePtr frame = GetConnectedNode()->video_frame_cache()->LoadCacheFrame(fn); if (frame) { frame->set_timestamp(time); @@ -374,7 +374,7 @@ FramePtr DecodeCachedImage(const QString &fn, const rational& time) return frame; } -void DecodeCachedImage(RenderTicketPtr ticket, const QString &fn, const rational& time) +void ViewerWidget::DecodeCachedImage(RenderTicketPtr ticket, const QString &fn, const rational& time) const { ticket->Finish(QVariant::fromValue(DecodeCachedImage(fn, time))); } @@ -606,7 +606,7 @@ RenderTicketPtr ViewerWidget::GetFrame(const rational &t, bool clear_render_queu // Frame has been cached, grab the frame RenderTicketPtr ticket = std::make_shared(RenderTicket::kTypeVideo, QVariant::fromValue(t)); - QtConcurrent::run(DecodeCachedImage, ticket, cache_fn, t); + QtConcurrent::run(this, &ViewerWidget::DecodeCachedImage, ticket, cache_fn, t); return ticket; } diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 6e75c8327..6e171e6e3 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -204,6 +204,10 @@ private: void PopOldestFrameFromPlaybackQueue(); + FramePtr DecodeCachedImage(const QString &fn, const rational& time) const; + + void DecodeCachedImage(RenderTicketPtr ticket, const QString &fn, const rational& time) const; + QStackedWidget* stack_; ViewerSizer* sizer_;