/* * Oak Video Editor - Non-Linear Video Editor * Copyright (C) 2026 Oak 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 "proxydialog.h" #include #include #include #include #include #include #include #include #include #include #include "common/configwrapper.h" #include "oakengine/project.h" #include "oakengine/timeline.h" #include "oakutil/oaknode.h" namespace olive { ProxyDialog::ProxyDialog(QWidget *parent, const QVector &footage) : QDialog(parent) , footage_(footage) , footage_tree_(nullptr) , custom_params_checkbox_(nullptr) { setWindowTitle(tr("Proxy Settings")); oak_proxy_params params; oakengine_proxy_params_from_config(¶ms); QVBoxLayout *layout = new QVBoxLayout(this); if (!footage_.isEmpty()) { QGroupBox *footage_group = new QGroupBox(tr("Selected Footage")); layout->addWidget(footage_group); QVBoxLayout *footage_layout = new QVBoxLayout(footage_group); footage_tree_ = new QTreeWidget(); footage_tree_->setHeaderLabels({ tr("Footage"), tr("Proxy State") }); footage_tree_->setRootIsDecorated(false); footage_layout->addWidget(footage_tree_); refresh_footage_list(); custom_params_checkbox_ = new QCheckBox(tr("Use custom settings for selected footage")); bool any_custom = false; for (OakEngineNode *item : footage_) { // WRAPPER-GAP: oak::Footage::has_custom_proxy_params oak::Footage f = oak::Footage::borrow(item); if (f && oakengine_footage_has_custom_proxy_params(f.handle())) { any_custom = true; break; } } custom_params_checkbox_->setChecked(any_custom); footage_layout->addWidget(custom_params_checkbox_); } QGroupBox *settings_group = new QGroupBox(tr("Global Proxy Settings")); layout->addWidget(settings_group); QGridLayout *settings_layout = new QGridLayout(settings_group); int row = 0; settings_layout->addWidget(new QLabel(tr("Proxy Resolution:")), row, 0); resolution_combo_ = new QComboBox(); resolution_combo_->addItem(tr("Custom size"), 1); resolution_combo_->addItem(tr("1/2 of source"), 2); resolution_combo_->addItem(tr("1/4 of source"), 4); resolution_combo_->addItem(tr("1/8 of source"), 8); const int divider_index = resolution_combo_->findData(params.divider); if (divider_index >= 0) { resolution_combo_->setCurrentIndex(divider_index); } settings_layout->addWidget(resolution_combo_, row, 1, 1, 3); row++; settings_layout->addWidget(new QLabel(tr("Proxy Width:")), row, 0); width_slider_ = new IntegerSlider(); width_slider_->set_minimum(160); width_slider_->set_maximum(4096); width_slider_->set_value(params.width); settings_layout->addWidget(width_slider_, row, 1); settings_layout->addWidget(new QLabel(tr("Proxy Height:")), row, 2); height_slider_ = new IntegerSlider(); height_slider_->set_minimum(120); height_slider_->set_maximum(2160); height_slider_->set_value(params.height); settings_layout->addWidget(height_slider_, row, 3); // Absolute size only applies in "Custom size" mode const auto update_size_sliders_enabled = [this]() { const bool custom_size = resolution_combo_->currentData().toInt() == 1; width_slider_->setEnabled(custom_size); height_slider_->setEnabled(custom_size); }; connect(resolution_combo_, &QComboBox::currentIndexChanged, this, update_size_sliders_enabled); update_size_sliders_enabled(); row++; settings_layout->addWidget(new QLabel(tr("Proxy CRF:")), row, 0); crf_slider_ = new IntegerSlider(); crf_slider_->set_minimum(0); crf_slider_->set_maximum(51); crf_slider_->set_value(params.crf); settings_layout->addWidget(crf_slider_, row, 1); settings_layout->addWidget(new QLabel(tr("Proxy Preset:")), row, 2); preset_combo_ = new QComboBox(); const QStringList presets = { QStringLiteral("ultrafast"), QStringLiteral("superfast"), QStringLiteral("veryfast"), QStringLiteral("faster"), QStringLiteral("fast"), QStringLiteral("medium"), QStringLiteral("slow"), QStringLiteral("slower"), QStringLiteral("veryslow"), }; for (const QString &preset : presets) { preset_combo_->addItem(preset); } preset_combo_->setCurrentText(params.preset); settings_layout->addWidget(preset_combo_, row, 3); row++; include_audio_checkbox_ = new QCheckBox(tr("Include audio in proxies")); include_audio_checkbox_->setChecked(params.include_audio); settings_layout->addWidget(include_audio_checkbox_, row, 0, 1, 2); row++; settings_layout->addWidget(new QLabel(tr("ffmpeg Executable:")), row, 0); ffmpeg_path_edit_ = new QLineEdit(OAK_CONFIG("FFmpegPath").toString()); ffmpeg_path_edit_->setPlaceholderText(tr("Auto-detect")); settings_layout->addWidget(ffmpeg_path_edit_, row, 1); QPushButton *ffmpeg_browse_btn = new QPushButton(tr("Browse...")); connect(ffmpeg_browse_btn, &QPushButton::clicked, this, &ProxyDialog::browse_for_f_fmpeg); settings_layout->addWidget(ffmpeg_browse_btn, row, 2); QHBoxLayout *button_layout = new QHBoxLayout(); layout->addLayout(button_layout); if (!footage_.isEmpty()) { QPushButton *generate_btn = new QPushButton(tr("Generate Proxies")); connect(generate_btn, &QPushButton::clicked, this, &ProxyDialog::generate_proxies); button_layout->addWidget(generate_btn); QPushButton *delete_btn = new QPushButton(tr("Delete Proxies")); connect(delete_btn, &QPushButton::clicked, this, &ProxyDialog::delete_proxies); button_layout->addWidget(delete_btn); } button_layout->addStretch(); QPushButton *close_btn = new QPushButton(tr("Close")); connect(close_btn, &QPushButton::clicked, this, &ProxyDialog::accept); button_layout->addWidget(close_btn); } void ProxyDialog::accept() { save_global_settings(); if (!footage_.isEmpty()) { for (OakEngineNode *item : footage_) { // WRAPPER-GAP: oak::Footage::{set,clear}_custom_proxy_params oak::Footage f = oak::Footage::borrow(item); if (!f) { continue; } if (custom_params_checkbox_->isChecked()) { oak_proxy_params p = current_params(); oakengine_footage_set_custom_proxy_params(f.handle(), &p); } else { oakengine_footage_clear_custom_proxy_params(f.handle()); } } } QDialog::accept(); } int ProxyDialog::proxy_width() const { return static_cast(width_slider_->get_value()); } int ProxyDialog::proxy_height() const { return static_cast(height_slider_->get_value()); } int ProxyDialog::proxy_divider() const { return resolution_combo_->currentData().toInt(); } int ProxyDialog::proxy_crf() const { return static_cast(crf_slider_->get_value()); } QString ProxyDialog::proxy_preset() const { return preset_combo_->currentText(); } bool ProxyDialog::proxy_include_audio() const { return include_audio_checkbox_->isChecked(); } QString ProxyDialog::f_fmpeg_path() const { return ffmpeg_path_edit_->text(); } void ProxyDialog::set_proxy_width(int width) { width_slider_->set_value(width); } void ProxyDialog::set_proxy_height(int height) { height_slider_->set_value(height); } void ProxyDialog::set_proxy_divider(int divider) { const int index = resolution_combo_->findData(divider); if (index >= 0) { resolution_combo_->setCurrentIndex(index); } } void ProxyDialog::set_proxy_crf(int crf) { crf_slider_->set_value(crf); } void ProxyDialog::set_proxy_preset(const QString &preset) { preset_combo_->setCurrentText(preset); } void ProxyDialog::set_proxy_include_audio(bool include_audio) { include_audio_checkbox_->setChecked(include_audio); } void ProxyDialog::set_f_fmpeg_path(const QString &path) { ffmpeg_path_edit_->setText(path); } oak_proxy_params ProxyDialog::current_params() const { oak_proxy_params params; oakengine_proxy_params_from_config(¶ms); params.width = static_cast(width_slider_->get_value()); params.height = static_cast(height_slider_->get_value()); params.divider = resolution_combo_->currentData().toInt(); params.crf = static_cast(crf_slider_->get_value()); strncpy(params.preset, preset_combo_->currentText().toUtf8().constData(), sizeof(params.preset) - 1); params.preset[sizeof(params.preset) - 1] = '\0'; params.include_audio = include_audio_checkbox_->isChecked() ? 1 : 0; return params; } void ProxyDialog::save_global_settings() { OAK_CONFIG("ProxyWidth") = static_cast(width_slider_->get_value()); OAK_CONFIG("ProxyHeight") = static_cast(height_slider_->get_value()); OAK_CONFIG("ProxyDivider") = resolution_combo_->currentData().toInt(); OAK_CONFIG("ProxyCRF") = static_cast(crf_slider_->get_value()); OAK_CONFIG("ProxyPreset") = preset_combo_->currentText(); OAK_CONFIG("ProxyIncludeAudio") = include_audio_checkbox_->isChecked(); OAK_CONFIG("FFmpegPath") = ffmpeg_path_edit_->text().trimmed(); } void ProxyDialog::refresh_footage_list() { if (!footage_tree_) { return; } footage_tree_->clear(); for (OakEngineNode *item : footage_) { // WRAPPER-GAP: oak::Footage::{proxy_state,has_custom_proxy_params} oak::Footage f = oak::Footage::borrow(item); QTreeWidgetItem *tree_item = new QTreeWidgetItem(footage_tree_); tree_item->setText(0, f.filename()); { char state_buf[256]; int state_len = oakengine_proxy_state_to_string( oakengine_footage_proxy_get_state(f.handle()), state_buf, sizeof(state_buf)); QString state = (state_len > 0) ? QString::fromUtf8(state_buf, state_len) : QString(); if (oakengine_footage_has_custom_proxy_params(f.handle())) { state = tr("%1 (custom settings)").arg(state); } tree_item->setText(1, state); } } } void ProxyDialog::generate_proxies() { for (OakEngineNode *item : footage_) { // WRAPPER-GAP: oak::Footage::{get_effective_proxy_params,set_proxy} oak::Footage f = oak::Footage::borrow(item); oak_video_params _vp; oakengine_viewer_get_first_enabled_video_stream(item, &_vp); if (!oakengine_video_params_is_valid(&_vp)) { qWarning() << "ProxyDialog::GenerateProxies: skipping item with no valid video stream" << f.filename(); continue; } // Index of the first enabled video stream // (VideoParams::stream_index() has no facade equivalent). int stream_index = -1; const QVector> streams = oak::Node(item).enabled_streams(); for (const QPair &s : streams) { if (s.first == OAKENGINE_TRACK_TYPE_VIDEO) { stream_index = s.second; break; } } if (stream_index < 0) { continue; } oak_proxy_params params; if (custom_params_checkbox_->isChecked()) { params = current_params(); } else { oakengine_footage_get_effective_proxy_params(f.handle(), ¶ms); } oak_proxy_result proxy; char cache_buf[512]; oakengine_project_cache_path(oakengine_node_get_project(item), cache_buf, sizeof(cache_buf)); int ret = oakengine_proxy_get_or_start( cache_buf, f.filename().toUtf8().constData(), stream_index, ¶ms, &proxy); if (ret != 0) { qWarning() << "ProxyDialog::GenerateProxies: failed to get/start proxy for" << f.filename(); continue; } oakengine_footage_set_proxy(f.handle(), proxy.filename, proxy.state, stream_index, 1, params.version); f.invalidate(); } refresh_footage_list(); } void ProxyDialog::delete_proxies() { for (OakEngineNode *item : footage_) { // WRAPPER-GAP: oak::Footage::clear_proxy oak::Footage f = oak::Footage::borrow(item); if (f.proxy_path().isEmpty()) { continue; } QFile::remove(f.proxy_path()); { char wbuf[4096]; int wlen = oakengine_proxy_get_working_filename( f.proxy_path().toUtf8().constData(), wbuf, sizeof(wbuf)); if (wlen > 0) { QFile::remove(QString::fromUtf8(wbuf, wlen)); } } oakengine_footage_clear_proxy(f.handle()); f.invalidate(); } refresh_footage_list(); } void ProxyDialog::browse_for_f_fmpeg() { const QString file = QFileDialog::getOpenFileName(this, tr("Select ffmpeg Executable")); if (!file.isEmpty()) { ffmpeg_path_edit_->setText(file); } } }