File indexing completed on 2024-04-28 08:44:07

0001 /*
0002 SPDX-FileCopyrightText: 2015 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003 This file is part of Kdenlive. See www.kdenlive.org.
0004 
0005 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #include "clippropertiescontroller.h"
0009 #include "clipcontroller.h"
0010 #include "core.h"
0011 #include "dialogs/profilesdialog.h"
0012 #include "doc/kdenlivedoc.h"
0013 #include "kdenlivesettings.h"
0014 #include "profiles/profilerepository.hpp"
0015 #include "project/projectmanager.h"
0016 #include "widgets/choosecolorwidget.h"
0017 #include "widgets/timecodedisplay.h"
0018 #include <audio/audioStreamInfo.h>
0019 
0020 #include <KDualAction>
0021 #include <KLocalizedString>
0022 
0023 #include "kdenlive_debug.h"
0024 #include <KFileMetaData/ExtractionResult>
0025 #include <KFileMetaData/Extractor>
0026 #include <KFileMetaData/ExtractorCollection>
0027 #include <KFileMetaData/PropertyInfo>
0028 #include <KIO/Global>
0029 #include <KIO/OpenFileManagerWindowJob>
0030 #include <KMessageBox>
0031 #include <QButtonGroup>
0032 #include <QCheckBox>
0033 #include <QClipboard>
0034 #include <QComboBox>
0035 #include <QDesktopServices>
0036 #include <QDoubleSpinBox>
0037 #include <QFile>
0038 #include <QFileDialog>
0039 #include <QFontDatabase>
0040 #include <QHBoxLayout>
0041 #include <QListWidgetItem>
0042 #include <QMenu>
0043 #include <QMimeData>
0044 #include <QMimeDatabase>
0045 #include <QProcess>
0046 #include <QResizeEvent>
0047 #include <QScrollArea>
0048 #include <QSortFilterProxyModel>
0049 #include <QTextEdit>
0050 #include <QToolBar>
0051 #include <QUrl>
0052 #include <QVBoxLayout>
0053 #include <QtMath>
0054 
0055 ElidedLinkLabel::ElidedLinkLabel(QWidget *parent)
0056     : QLabel(parent)
0057 {
0058 }
0059 
0060 void ElidedLinkLabel::setLabelText(const QString &text, const QString &link)
0061 {
0062     m_text = text;
0063     m_link = link;
0064     int width = currentWidth();
0065     updateText(width);
0066 }
0067 
0068 void ElidedLinkLabel::updateText(int width)
0069 {
0070     if (m_link.isEmpty()) {
0071         setText(fontMetrics().elidedText(m_text, Qt::ElideLeft, width));
0072     } else {
0073         setText(QString("<a href=\"%1\">%2</a>").arg(m_link, fontMetrics().elidedText(m_text, Qt::ElideLeft, width)));
0074     }
0075 }
0076 
0077 int ElidedLinkLabel::currentWidth() const
0078 {
0079     int width = 0;
0080     if (isVisible()) {
0081         width = contentsRect().width();
0082     } else {
0083         QMargins mrg = contentsMargins();
0084         width = sizeHint().width() - mrg.left() - mrg.right();
0085     }
0086     return width;
0087 }
0088 
0089 void ElidedLinkLabel::resizeEvent(QResizeEvent *event)
0090 {
0091     int diff = event->size().width() - event->oldSize().width();
0092     updateText(currentWidth() + diff);
0093     QLabel::resizeEvent(event);
0094 }
0095 
0096 AnalysisTree::AnalysisTree(QWidget *parent)
0097     : QTreeWidget(parent)
0098 {
0099     setRootIsDecorated(false);
0100     setColumnCount(2);
0101     setAlternatingRowColors(true);
0102     setHeaderHidden(true);
0103     setDragEnabled(true);
0104 }
0105 
0106 // virtual
0107 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0108 QMimeData *AnalysisTree::mimeData(const QList<QTreeWidgetItem *> &list) const
0109 #else
0110 QMimeData *AnalysisTree::mimeData(const QList<QTreeWidgetItem *> list) const
0111 #endif
0112 {
0113     QString mimeData;
0114     for (QTreeWidgetItem *item : list) {
0115         if ((item->flags() & Qt::ItemIsDragEnabled) != 0) {
0116             mimeData.append(item->text(1));
0117         }
0118     }
0119     auto *mime = new QMimeData;
0120     mime->setData(QStringLiteral("kdenlive/geometry"), mimeData.toUtf8());
0121     return mime;
0122 }
0123 
0124 class ExtractionResult : public KFileMetaData::ExtractionResult
0125 {
0126 public:
0127     ExtractionResult(const QString &filename, const QString &mimetype, QTreeWidget *tree)
0128         : KFileMetaData::ExtractionResult(filename, mimetype, KFileMetaData::ExtractionResult::ExtractMetaData)
0129         , m_tree(tree)
0130     {
0131     }
0132 
0133     void append(const QString & /*text*/) override {}
0134 
0135     void addType(KFileMetaData::Type::Type /*type*/) override {}
0136 
0137     void add(KFileMetaData::Property::Property property, const QVariant &value) override
0138     {
0139         bool decode = false;
0140         switch (property) {
0141         case KFileMetaData::Property::Manufacturer:
0142         case KFileMetaData::Property::Model:
0143         case KFileMetaData::Property::ImageDateTime:
0144         case KFileMetaData::Property::BitRate:
0145         case KFileMetaData::Property::TrackNumber:
0146         case KFileMetaData::Property::ReleaseYear:
0147         case KFileMetaData::Property::Composer:
0148         case KFileMetaData::Property::Genre:
0149         case KFileMetaData::Property::Artist:
0150         case KFileMetaData::Property::Album:
0151         case KFileMetaData::Property::Title:
0152         case KFileMetaData::Property::Comment:
0153         case KFileMetaData::Property::Copyright:
0154         case KFileMetaData::Property::PhotoFocalLength:
0155         case KFileMetaData::Property::PhotoExposureTime:
0156         case KFileMetaData::Property::PhotoFNumber:
0157         case KFileMetaData::Property::PhotoApertureValue:
0158         case KFileMetaData::Property::PhotoWhiteBalance:
0159         case KFileMetaData::Property::PhotoGpsLatitude:
0160         case KFileMetaData::Property::PhotoGpsLongitude:
0161             decode = true;
0162             break;
0163         default:
0164             break;
0165         }
0166         if (decode) {
0167             KFileMetaData::PropertyInfo info(property);
0168             if (info.valueType() == QVariant::DateTime) {
0169                 QLocale locale;
0170                 new QTreeWidgetItem(m_tree, {info.displayName(), locale.toDateTime(value.toString(), QLocale::ShortFormat).toString()});
0171             } else if (info.valueType() == QVariant::Int) {
0172                 int val = value.toInt();
0173                 if (property == KFileMetaData::Property::BitRate) {
0174                     // Adjust unit for bitrate
0175                     new QTreeWidgetItem(
0176                         m_tree, QStringList{info.displayName(), QString::number(val / 1000) + QLatin1Char(' ') + i18nc("Kilobytes per seconds", "kb/s")});
0177                 } else {
0178                     new QTreeWidgetItem(m_tree, QStringList{info.displayName(), QString::number(val)});
0179                 }
0180             } else if (info.valueType() == QVariant::Double) {
0181                 new QTreeWidgetItem(m_tree, QStringList{info.displayName(), QString::number(value.toDouble())});
0182             } else {
0183                 new QTreeWidgetItem(m_tree, QStringList{info.displayName(), value.toString()});
0184             }
0185         }
0186     }
0187 
0188 private:
0189     QTreeWidget *m_tree;
0190     Q_DISABLE_COPY(ExtractionResult)
0191 };
0192 
0193 ClipPropertiesController::ClipPropertiesController(const QString &clipName, ClipController *controller, QWidget *parent)
0194     : QWidget(parent)
0195     , m_controller(controller)
0196     , m_id(controller->binId())
0197     , m_type(controller->clipType())
0198     , m_properties(new Mlt::Properties(controller->properties()))
0199     , m_sourceProperties(new Mlt::Properties())
0200     , m_audioStream(nullptr)
0201     , m_textEdit(nullptr)
0202     , m_audioStreamsView(nullptr)
0203     , m_activeAudioStreams(-1)
0204 {
0205     m_controller->mirrorOriginalProperties(m_sourceProperties);
0206     setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
0207     setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
0208     auto *lay = new QVBoxLayout;
0209     lay->setContentsMargins(0, 0, 0, 0);
0210     m_clipLabel = new ElidedLinkLabel(this);
0211 
0212     if (m_type == ClipType::Color || m_type == ClipType::Timeline || controller->clipUrl().isEmpty()) {
0213         m_clipLabel->setLabelText(clipName, QString());
0214     } else {
0215         m_clipLabel->setLabelText(controller->clipUrl(), controller->clipUrl());
0216     }
0217     connect(m_clipLabel, &QLabel::linkActivated, [](const QString &link) { KIO::highlightInFileManager({QUrl::fromLocalFile(link)}); });
0218     lay->addWidget(m_clipLabel);
0219     lay->addWidget(&m_warningMessage);
0220     m_warningMessage.setCloseButtonVisible(false);
0221     m_warningMessage.setWordWrap(true);
0222     m_warningMessage.hide();
0223     m_tabWidget = new QTabWidget(this);
0224     lay->addWidget(m_tabWidget);
0225     setLayout(lay);
0226     m_tabWidget->setDocumentMode(true);
0227     m_tabWidget->setTabPosition(QTabWidget::East);
0228     m_tabWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
0229     auto *forcePage = new QScrollArea(this);
0230     auto *forceAudioPage = new QScrollArea(this);
0231     m_propertiesPage = new QWidget(this);
0232     m_metaPage = new QWidget(this);
0233     m_analysisPage = new QWidget(this);
0234 
0235     // Clip properties
0236     auto *propsBox = new QVBoxLayout;
0237     m_propertiesTree = new QTreeWidget(this);
0238     m_propertiesTree->setRootIsDecorated(false);
0239     m_propertiesTree->setColumnCount(2);
0240     m_propertiesTree->setAlternatingRowColors(true);
0241     m_propertiesTree->sortByColumn(0, Qt::AscendingOrder);
0242     m_propertiesTree->setHeaderHidden(true);
0243     propsBox->addWidget(m_propertiesTree);
0244     fillProperties();
0245     m_propertiesPage->setLayout(propsBox);
0246 
0247     // metadata
0248     auto *m2Box = new QVBoxLayout;
0249     auto *metaTree = new QTreeWidget;
0250     metaTree->setRootIsDecorated(true);
0251     metaTree->setColumnCount(2);
0252     metaTree->setAlternatingRowColors(true);
0253     metaTree->setHeaderHidden(true);
0254     m2Box->addWidget(metaTree);
0255     slotFillMeta(metaTree);
0256     m_metaPage->setLayout(m2Box);
0257 
0258     // Clip analysis
0259     auto *aBox = new QVBoxLayout;
0260     m_analysisTree = new AnalysisTree(this);
0261     aBox->addWidget(new QLabel(i18n("Analysis data")));
0262     aBox->addWidget(m_analysisTree);
0263     auto *bar2 = new QToolBar;
0264     bar2->addAction(QIcon::fromTheme(QStringLiteral("trash-empty")), i18n("Delete analysis"), this, SLOT(slotDeleteAnalysis()));
0265     bar2->setWhatsThis(xi18nc("@info:whatsthis", "Deletes the data set(s)."));
0266     bar2->addAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n("Export analysis…"), this, SLOT(slotSaveAnalysis()));
0267     bar2->setWhatsThis(xi18nc("@info:whatsthis", "Opens a file dialog window to export/save the analysis data."));
0268     bar2->addAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Import analysis…"), this, SLOT(slotLoadAnalysis()));
0269     bar2->setWhatsThis(xi18nc("@info:whatsthis", "Opens a file dialog window to import/load analysis data."));
0270     aBox->addWidget(bar2);
0271 
0272     slotFillAnalysisData();
0273     m_analysisPage->setLayout(aBox);
0274 
0275     // Force properties
0276     auto *fpBox = new QVBoxLayout;
0277     fpBox->setSpacing(0);
0278 
0279     // Force Audio properties
0280     auto *audioVbox = new QVBoxLayout;
0281     audioVbox->setSpacing(0);
0282 
0283     if (m_type == ClipType::Text || m_type == ClipType::SlideShow || m_type == ClipType::TextTemplate) {
0284         QPushButton *editButton = new QPushButton(i18n("Edit Clip"), this);
0285         connect(editButton, &QAbstractButton::clicked, this, &ClipPropertiesController::editClip);
0286         fpBox->addWidget(editButton);
0287     }
0288     if (m_type == ClipType::Color || m_type == ClipType::Image || m_type == ClipType::AV || m_type == ClipType::Video || m_type == ClipType::TextTemplate) {
0289         // Edit duration widget
0290         m_originalProperties.insert(QStringLiteral("out"), m_properties->get("out"));
0291         int kdenlive_length = m_properties->time_to_frames(m_properties->get("kdenlive:duration"));
0292         if (kdenlive_length > 0) {
0293             m_originalProperties.insert(QStringLiteral("kdenlive:duration"), m_properties->get("kdenlive:duration"));
0294         }
0295         m_originalProperties.insert(QStringLiteral("length"), m_properties->get("length"));
0296         auto *hlay = new QHBoxLayout;
0297         QCheckBox *box = new QCheckBox(i18n("Duration:"), this);
0298         box->setObjectName(QStringLiteral("force_duration"));
0299         hlay->addWidget(box);
0300         auto *timePos = new TimecodeDisplay(this);
0301         timePos->setObjectName(QStringLiteral("force_duration_value"));
0302         timePos->setValue(kdenlive_length > 0 ? kdenlive_length : m_properties->get_int("length"));
0303         int original_length = m_properties->get_int("kdenlive:original_length");
0304         if (original_length > 0) {
0305             box->setChecked(true);
0306         } else {
0307             timePos->setEnabled(false);
0308         }
0309         hlay->addWidget(timePos);
0310         fpBox->addLayout(hlay);
0311         connect(box, &QAbstractButton::toggled, timePos, &QWidget::setEnabled);
0312         connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce);
0313         connect(timePos, &TimecodeDisplay::timeCodeEditingFinished, this, &ClipPropertiesController::slotDurationChanged);
0314         connect(this, SIGNAL(modified(int)), timePos, SLOT(setValue(int)));
0315 
0316         // Autorotate
0317         if (m_type == ClipType::Image) {
0318             int autorotate = m_properties->get_int("disable_exif");
0319             m_originalProperties.insert(QStringLiteral("disable_exif"), QString::number(autorotate));
0320             hlay = new QHBoxLayout;
0321             box = new QCheckBox(i18n("Disable autorotate"), this);
0322             connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce);
0323             box->setObjectName(QStringLiteral("disable_exif"));
0324             box->setChecked(autorotate == 1);
0325             hlay->addWidget(box);
0326             fpBox->addLayout(hlay);
0327         }
0328         // connect(this, static_cast<void(ClipPropertiesController::*)(int)>(&ClipPropertiesController::modified), timePos, &TimecodeDisplay::setValue);
0329     }
0330     if (m_type == ClipType::TextTemplate) {
0331         // Edit text widget
0332         QString currentText = m_properties->get("templatetext");
0333         m_originalProperties.insert(QStringLiteral("templatetext"), currentText);
0334         m_textEdit = new QTextEdit(this);
0335         m_textEdit->setAcceptRichText(false);
0336         m_textEdit->setPlainText(currentText);
0337         m_textEdit->setPlaceholderText(i18n("Enter template text here"));
0338         fpBox->addWidget(m_textEdit);
0339         QPushButton *button = new QPushButton(i18n("Apply"), this);
0340         fpBox->addWidget(button);
0341         connect(button, &QPushButton::clicked, this, &ClipPropertiesController::slotTextChanged);
0342     } else if (m_type == ClipType::Color) {
0343         // Edit color widget
0344         m_originalProperties.insert(QStringLiteral("resource"), m_properties->get("resource"));
0345         mlt_color color = m_properties->get_color("resource");
0346 
0347         QLabel *label = new QLabel(i18n("Color"), this);
0348         ChooseColorWidget *choosecolor = new ChooseColorWidget(this, QColor::fromRgb(color.r, color.g, color.b), false);
0349 
0350         auto *colorLay = new QHBoxLayout;
0351         colorLay->setContentsMargins(0, 0, 0, 0);
0352         colorLay->setSpacing(0);
0353         colorLay->addWidget(label);
0354         colorLay->addStretch();
0355         colorLay->addWidget(choosecolor);
0356 
0357         fpBox->addLayout(colorLay);
0358         connect(choosecolor, &ChooseColorWidget::modified, this, &ClipPropertiesController::slotColorModified);
0359         connect(this, static_cast<void (ClipPropertiesController::*)(const QColor &)>(&ClipPropertiesController::modified), choosecolor,
0360                 &ChooseColorWidget::slotColorModified);
0361     }
0362     if (m_type == ClipType::AV || m_type == ClipType::Video || m_type == ClipType::Image) {
0363         // Aspect ratio
0364         int force_ar_num = m_properties->get_int("force_aspect_num");
0365         int force_ar_den = m_properties->get_int("force_aspect_den");
0366         m_originalProperties.insert(QStringLiteral("force_aspect_den"), (force_ar_den == 0) ? QString() : QString::number(force_ar_den));
0367         m_originalProperties.insert(QStringLiteral("force_aspect_num"), (force_ar_num == 0) ? QString() : QString::number(force_ar_num));
0368         auto *hlay = new QHBoxLayout;
0369         QCheckBox *box = new QCheckBox(i18n("Aspect ratio:"), this);
0370         box->setObjectName(QStringLiteral("force_ar"));
0371         fpBox->addWidget(box);
0372         connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce);
0373         auto *spin1 = new QSpinBox(this);
0374         spin1->setMaximum(8000);
0375         spin1->setObjectName(QStringLiteral("force_aspect_num_value"));
0376         hlay->addStretch(10);
0377         hlay->addWidget(spin1);
0378         hlay->addWidget(new QLabel(QStringLiteral(":")));
0379         auto *spin2 = new QSpinBox(this);
0380         spin2->setMinimum(1);
0381         spin2->setMaximum(8000);
0382         spin2->setObjectName(QStringLiteral("force_aspect_den_value"));
0383         hlay->addWidget(spin2);
0384         if (force_ar_num == 0) {
0385             // use current ratio
0386             int num = m_properties->get_int("meta.media.sample_aspect_num");
0387             int den = m_properties->get_int("meta.media.sample_aspect_den");
0388             if (den == 0) {
0389                 num = 1;
0390                 den = 1;
0391             }
0392             spin1->setEnabled(false);
0393             spin2->setEnabled(false);
0394             spin1->setValue(num);
0395             spin2->setValue(den);
0396         } else {
0397             box->setChecked(true);
0398             spin1->setEnabled(true);
0399             spin2->setEnabled(true);
0400             spin1->setValue(force_ar_num);
0401             spin2->setValue(force_ar_den);
0402         }
0403         connect(spin2, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &ClipPropertiesController::slotAspectValueChanged);
0404         connect(spin1, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &ClipPropertiesController::slotAspectValueChanged);
0405         connect(box, &QAbstractButton::toggled, spin1, &QWidget::setEnabled);
0406         connect(box, &QAbstractButton::toggled, spin2, &QWidget::setEnabled);
0407         fpBox->addLayout(hlay);
0408     }
0409 
0410     if (m_type == ClipType::AV || m_type == ClipType::Video || m_type == ClipType::Image || m_type == ClipType::Playlist) {
0411         // Proxy
0412         QString proxy = m_properties->get("kdenlive:proxy");
0413         m_originalProperties.insert(QStringLiteral("kdenlive:proxy"), proxy);
0414         auto *hlay = new QHBoxLayout;
0415         auto *bg = new QGroupBox(this);
0416         bg->setCheckable(false);
0417         bg->setFlat(true);
0418         auto *groupLay = new QHBoxLayout;
0419         groupLay->setContentsMargins(0, 0, 0, 0);
0420         auto *pbox = new QCheckBox(i18n("Proxy clip"), this);
0421         pbox->setTristate(true);
0422         // Proxy codec label
0423         QLabel *lab = new QLabel(this);
0424         pbox->setObjectName(QStringLiteral("kdenlive:proxy"));
0425         bool hasProxy = m_controller->hasProxy();
0426         if (hasProxy) {
0427             bg->setToolTip(proxy);
0428             bool proxyReady = (QFileInfo(proxy).fileName() == QFileInfo(m_properties->get("resource")).fileName());
0429             if (proxyReady) {
0430                 pbox->setCheckState(Qt::Checked);
0431                 if (!m_properties->property_exists("video_index")) {
0432                     // Probable an image proxy
0433                     lab->setText(i18n("Image"));
0434                 } else {
0435                     lab->setText(m_properties->get(QString("meta.media.%1.codec.name").arg(m_properties->get_int("video_index")).toUtf8().constData()));
0436                 }
0437             } else {
0438                 pbox->setCheckState(Qt::PartiallyChecked);
0439             }
0440         } else {
0441             pbox->setCheckState(Qt::Unchecked);
0442         }
0443         pbox->setEnabled(pCore->projectManager()->current()->useProxy());
0444         connect(pbox, &QCheckBox::stateChanged, this, [this, pbox](int state) {
0445             Q_EMIT requestProxy(state == Qt::PartiallyChecked);
0446             if (state == Qt::Checked) {
0447                 QSignalBlocker bk(pbox);
0448                 pbox->setCheckState(Qt::Unchecked);
0449             }
0450         });
0451         connect(this, &ClipPropertiesController::enableProxy, pbox, &QCheckBox::setEnabled);
0452         connect(this, &ClipPropertiesController::proxyModified, this, [this, pbox, bg, lab](const QString &pxy) {
0453             bool hasProxyClip = pxy.length() > 2;
0454             QSignalBlocker bk(pbox);
0455             pbox->setCheckState(hasProxyClip ? Qt::Checked : Qt::Unchecked);
0456             bg->setEnabled(pbox->isChecked());
0457             bg->setToolTip(pxy);
0458             lab->setText(hasProxyClip ? m_properties->get(QString("meta.media.%1.codec.name").arg(m_properties->get_int("video_index")).toUtf8().constData())
0459                                       : QString());
0460         });
0461         hlay->addWidget(pbox);
0462         bg->setEnabled(pbox->checkState() == Qt::Checked);
0463 
0464         groupLay->addWidget(lab);
0465 
0466         // Delete button
0467         auto *tb = new QToolButton(this);
0468         tb->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
0469         tb->setAutoRaise(true);
0470         connect(tb, &QToolButton::clicked, this, [this, proxy]() { Q_EMIT deleteProxy(); });
0471         tb->setToolTip(i18n("Delete proxy file"));
0472         groupLay->addWidget(tb);
0473         // Folder button
0474         tb = new QToolButton(this);
0475         auto *pMenu = new QMenu(this);
0476         tb->setIcon(QIcon::fromTheme(QStringLiteral("application-menu")));
0477         tb->setToolTip(i18n("Proxy options"));
0478         tb->setMenu(pMenu);
0479         tb->setAutoRaise(true);
0480         tb->setPopupMode(QToolButton::InstantPopup);
0481 
0482         QAction *ac = new QAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open folder…"), this);
0483         connect(ac, &QAction::triggered, this, [this]() {
0484             QString pxy = m_properties->get("kdenlive:proxy");
0485             QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(pxy).path()));
0486         });
0487         pMenu->addAction(ac);
0488         ac = new QAction(QIcon::fromTheme(QStringLiteral("media-playback-start")), i18n("Play proxy clip"), this);
0489         connect(ac, &QAction::triggered, this, [this]() {
0490             QString pxy = m_properties->get("kdenlive:proxy");
0491             QDesktopServices::openUrl(QUrl::fromLocalFile(pxy));
0492         });
0493         pMenu->addAction(ac);
0494         ac = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy file location to clipboard"), this);
0495         connect(ac, &QAction::triggered, this, [this]() {
0496             QString pxy = m_properties->get("kdenlive:proxy");
0497             QGuiApplication::clipboard()->setText(pxy);
0498         });
0499         pMenu->addAction(ac);
0500         groupLay->addWidget(tb);
0501         bg->setLayout(groupLay);
0502         hlay->addWidget(bg);
0503         fpBox->addLayout(hlay);
0504     }
0505 
0506     if (m_type == ClipType::AV || m_type == ClipType::Video) {
0507         // Fps
0508         QString force_fps = m_properties->get("force_fps");
0509         m_originalProperties.insert(QStringLiteral("force_fps"), force_fps.isEmpty() ? QStringLiteral("-") : force_fps);
0510         auto *hlay = new QHBoxLayout;
0511         QCheckBox *box = new QCheckBox(i18n("Frame rate:"), this);
0512         box->setObjectName(QStringLiteral("force_fps"));
0513         connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce);
0514         auto *spin = new QDoubleSpinBox(this);
0515         spin->setMaximum(1000);
0516         connect(spin, SIGNAL(valueChanged(double)), this, SLOT(slotValueChanged(double)));
0517         // connect(spin, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this, &ClipPropertiesController::slotValueChanged);
0518         spin->setObjectName(QStringLiteral("force_fps_value"));
0519         if (force_fps.isEmpty()) {
0520             spin->setValue(controller->originalFps());
0521         } else {
0522             spin->setValue(force_fps.toDouble());
0523         }
0524         connect(box, &QAbstractButton::toggled, spin, &QWidget::setEnabled);
0525         box->setChecked(!force_fps.isEmpty());
0526         spin->setEnabled(!force_fps.isEmpty());
0527         hlay->addWidget(box);
0528         hlay->addWidget(spin);
0529         fpBox->addLayout(hlay);
0530 
0531         // Scanning
0532         QString force_prog = m_properties->get("force_progressive");
0533         m_originalProperties.insert(QStringLiteral("force_progressive"), force_prog.isEmpty() ? QStringLiteral("-") : force_prog);
0534         hlay = new QHBoxLayout;
0535         box = new QCheckBox(i18n("Scanning:"), this);
0536         connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce);
0537         box->setObjectName(QStringLiteral("force_progressive"));
0538         auto *combo = new QComboBox(this);
0539         combo->addItem(i18n("Interlaced"), 0);
0540         combo->addItem(i18n("Progressive"), 1);
0541         connect(combo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ClipPropertiesController::slotComboValueChanged);
0542         combo->setObjectName(QStringLiteral("force_progressive_value"));
0543         if (!force_prog.isEmpty()) {
0544             combo->setCurrentIndex(force_prog.toInt());
0545         }
0546         connect(box, &QAbstractButton::toggled, combo, &QWidget::setEnabled);
0547         box->setChecked(!force_prog.isEmpty());
0548         combo->setEnabled(!force_prog.isEmpty());
0549         hlay->addWidget(box);
0550         hlay->addWidget(combo);
0551         fpBox->addLayout(hlay);
0552 
0553         // Field order
0554         QString force_tff = m_properties->get("force_tff");
0555         m_originalProperties.insert(QStringLiteral("force_tff"), force_tff.isEmpty() ? QStringLiteral("-") : force_tff);
0556         hlay = new QHBoxLayout;
0557         box = new QCheckBox(i18n("Field order:"), this);
0558         connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce);
0559         box->setObjectName(QStringLiteral("force_tff"));
0560         combo = new QComboBox(this);
0561         combo->addItem(i18n("Bottom First"), 0);
0562         combo->addItem(i18n("Top First"), 1);
0563         connect(combo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ClipPropertiesController::slotComboValueChanged);
0564         combo->setObjectName(QStringLiteral("force_tff_value"));
0565         if (!force_tff.isEmpty()) {
0566             combo->setCurrentIndex(force_tff.toInt());
0567         }
0568         connect(box, &QAbstractButton::toggled, combo, &QWidget::setEnabled);
0569         box->setChecked(!force_tff.isEmpty());
0570         combo->setEnabled(!force_tff.isEmpty());
0571         hlay->addWidget(box);
0572         hlay->addWidget(combo);
0573         fpBox->addLayout(hlay);
0574 
0575         // Autorotate
0576         QString autorotate = m_properties->get("autorotate");
0577         m_originalProperties.insert(QStringLiteral("autorotate"), autorotate);
0578         hlay = new QHBoxLayout;
0579         box = new QCheckBox(i18n("Disable autorotate"), this);
0580         box->setObjectName(QStringLiteral("autorotate"));
0581         box->setChecked(autorotate == QLatin1String("0"));
0582         int vix = m_sourceProperties->get_int("video_index");
0583         const QString query = QString("meta.media.%1.codec.rotate").arg(vix);
0584         int angle = m_sourceProperties->get_int(query.toUtf8().constData());
0585         connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce);
0586 
0587         // Rotate
0588         int rotate = 0;
0589         if (m_properties->property_exists("rotate")) {
0590             rotate = m_properties->get_int("rotate");
0591             m_originalProperties.insert(QStringLiteral("rotate"), QString::number(rotate));
0592         } else {
0593             // Display automatic rotation
0594             rotate = angle;
0595         }
0596         combo = new QComboBox(this);
0597         combo->setObjectName(QStringLiteral("rotate_value"));
0598         for (int i = 0; i < 4; i++) {
0599             int a = 90 * i;
0600             if (a == angle) {
0601                 combo->addItem(i18n("%1 (default)", a), a);
0602             } else {
0603                 combo->addItem(QString::number(a), a);
0604             }
0605         }
0606         if (rotate > 0) {
0607             combo->setCurrentIndex(combo->findData(rotate));
0608         }
0609         // Disable force rotate when autorotate is disabled
0610         combo->setEnabled(!box->isChecked());
0611         connect(box, &QCheckBox::stateChanged, this, [combo](int state) { combo->setEnabled(state != Qt::Unchecked); });
0612         connect(combo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ClipPropertiesController::slotComboValueChanged);
0613         hlay->addWidget(box);
0614         hlay->addWidget(combo);
0615         fpBox->addLayout(hlay);
0616 
0617         // Video index
0618         if (!m_videoStreams.isEmpty()) {
0619             QString vix = m_properties->get("video_index");
0620             m_originalProperties.insert(QStringLiteral("video_index"), vix);
0621             hlay = new QHBoxLayout;
0622 
0623             KDualAction *ac = new KDualAction(i18n("Disable video"), i18n("Enable video"), this);
0624             ac->setInactiveIcon(QIcon::fromTheme(QStringLiteral("kdenlive-show-video")));
0625             ac->setActiveIcon(QIcon::fromTheme(QStringLiteral("kdenlive-hide-video")));
0626             auto *tbv = new QToolButton(this);
0627             tbv->setToolButtonStyle(Qt::ToolButtonIconOnly);
0628             tbv->setDefaultAction(ac);
0629             tbv->setAutoRaise(true);
0630             hlay->addWidget(tbv);
0631             hlay->addWidget(new QLabel(i18n("Video stream")));
0632             auto *videoStream = new QComboBox(this);
0633             int ix = 1;
0634             for (int stream : qAsConst(m_videoStreams)) {
0635                 videoStream->addItem(i18n("Video stream %1", ix), stream);
0636                 ix++;
0637             }
0638             if (!vix.isEmpty() && vix.toInt() > -1) {
0639                 videoStream->setCurrentIndex(videoStream->findData(QVariant(vix)));
0640             }
0641             ac->setActive(vix.toInt() == -1);
0642             videoStream->setEnabled(vix.toInt() > -1);
0643             videoStream->setVisible(m_videoStreams.size() > 1);
0644             connect(ac, &KDualAction::activeChanged, this, [this, videoStream](bool activated) {
0645                 QMap<QString, QString> properties;
0646                 int vindx = -1;
0647                 if (activated) {
0648                     videoStream->setEnabled(false);
0649                 } else {
0650                     videoStream->setEnabled(true);
0651                     vindx = videoStream->currentData().toInt();
0652                 }
0653                 properties.insert(QStringLiteral("video_index"), QString::number(vindx));
0654                 properties.insert(QStringLiteral("set.test_image"), vindx > -1 ? QStringLiteral("0") : QStringLiteral("1"));
0655                 Q_EMIT updateClipProperties(m_id, m_originalProperties, properties);
0656                 m_originalProperties = properties;
0657             });
0658             QObject::connect(videoStream, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [this, videoStream]() {
0659                 QMap<QString, QString> properties;
0660                 properties.insert(QStringLiteral("video_index"), QString::number(videoStream->currentData().toInt()));
0661                 Q_EMIT updateClipProperties(m_id, m_originalProperties, properties);
0662                 m_originalProperties = properties;
0663             });
0664             hlay->addWidget(videoStream);
0665             fpBox->addLayout(hlay);
0666         }
0667 
0668         // Audio index
0669         QMap<int, QString> audioStreamsInfo = m_controller->audioStreams();
0670         if (!audioStreamsInfo.isEmpty()) {
0671             QList<int> enabledStreams = m_controller->activeStreams().keys();
0672             QString vix = m_sourceProperties->get("audio_index");
0673             m_originalProperties.insert(QStringLiteral("audio_index"), vix);
0674             QStringList streamString;
0675             for (int streamIx : qAsConst(enabledStreams)) {
0676                 streamString << QString::number(streamIx);
0677             }
0678             m_originalProperties.insert(QStringLiteral("kdenlive:active_streams"), streamString.join(QLatin1Char(';')));
0679             hlay = new QHBoxLayout;
0680 
0681             KDualAction *ac = new KDualAction(i18n("Disable audio"), i18n("Enable audio"), this);
0682             ac->setInactiveIcon(QIcon::fromTheme(QStringLiteral("kdenlive-show-audio")));
0683             ac->setActiveIcon(QIcon::fromTheme(QStringLiteral("kdenlive-hide-audio")));
0684             auto *tbv = new QToolButton(this);
0685             tbv->setToolButtonStyle(Qt::ToolButtonIconOnly);
0686             tbv->setDefaultAction(ac);
0687             tbv->setAutoRaise(true);
0688             hlay->addWidget(tbv);
0689             hlay->addWidget(new QLabel(i18n("Audio streams")));
0690             audioVbox->addLayout(hlay);
0691             m_audioStreamsView = new QListWidget(this);
0692             m_audioStreamsView->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
0693             audioVbox->addWidget(m_audioStreamsView);
0694             QMapIterator<int, QString> i(audioStreamsInfo);
0695             while (i.hasNext()) {
0696                 i.next();
0697                 auto *item = new QListWidgetItem(i.value(), m_audioStreamsView);
0698                 // Store stream index
0699                 item->setData(Qt::UserRole, i.key());
0700                 // Store oringinal name
0701                 item->setData(Qt::UserRole + 1, i.value());
0702                 item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
0703                 if (enabledStreams.contains(i.key())) {
0704                     item->setCheckState(Qt::Checked);
0705                 } else {
0706                     item->setCheckState(Qt::Unchecked);
0707                 }
0708                 updateStreamIcon(m_audioStreamsView->row(item), i.key());
0709             }
0710             if (audioStreamsInfo.count() > 1) {
0711                 QListWidgetItem *item = new QListWidgetItem(i18n("Merge all streams"), m_audioStreamsView);
0712                 item->setData(Qt::UserRole, INT_MAX);
0713                 item->setData(Qt::UserRole + 1, item->text());
0714                 item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
0715                 if (enabledStreams.contains(INT_MAX)) {
0716                     item->setCheckState(Qt::Checked);
0717                 } else {
0718                     item->setCheckState(Qt::Unchecked);
0719                 }
0720             }
0721             connect(m_audioStreamsView, &QListWidget::currentRowChanged, this, [this](int row) {
0722                 if (row > -1) {
0723                     m_audioEffectGroup->setEnabled(true);
0724                     QListWidgetItem *item = m_audioStreamsView->item(row);
0725                     m_activeAudioStreams = item->data(Qt::UserRole).toInt();
0726                     QStringList effects = m_controller->getAudioStreamEffect(m_activeAudioStreams);
0727                     QSignalBlocker bk(m_swapChannels);
0728                     QSignalBlocker bk1(m_copyChannelGroup);
0729                     QSignalBlocker bk2(m_normalize);
0730                     m_swapChannels->setChecked(effects.contains(QLatin1String("channelswap")));
0731                     m_copyChannel1->setChecked(effects.contains(QStringLiteral("channelcopy from=0 to=1")));
0732                     m_copyChannel2->setChecked(effects.contains(QStringLiteral("channelcopy from=1 to=0")));
0733                     m_normalize->setChecked(effects.contains(QStringLiteral("dynamic_loudness")));
0734                     int gain = 0;
0735                     for (const QString &st : qAsConst(effects)) {
0736                         if (st.startsWith(QLatin1String("volume "))) {
0737                             QSignalBlocker bk3(m_gain);
0738                             gain = st.section(QLatin1Char('='), 1).toInt();
0739                             break;
0740                         }
0741                     }
0742                     QSignalBlocker bk3(m_gain);
0743                     m_gain->setValue(gain);
0744                 } else {
0745                     m_activeAudioStreams = -1;
0746                     m_audioEffectGroup->setEnabled(false);
0747                 }
0748             });
0749             connect(m_audioStreamsView, &QListWidget::itemChanged, this, [this](QListWidgetItem *item) {
0750                 if (!item) {
0751                     return;
0752                 }
0753                 bool checked = item->checkState() == Qt::Checked;
0754                 int streamId = item->data(Qt::UserRole).toInt();
0755                 bool streamModified = false;
0756                 QString currentStreams = m_originalProperties.value(QStringLiteral("kdenlive:active_streams"));
0757                 QStringList activeStreams = currentStreams.split(QLatin1Char(';'));
0758                 if (activeStreams.contains(QString::number(streamId))) {
0759                     if (!checked) {
0760                         // Stream was unselected
0761                         activeStreams.removeAll(QString::number(streamId));
0762                         streamModified = true;
0763                     }
0764                 } else if (checked) {
0765                     // Stream was selected
0766                     if (streamId == INT_MAX) {
0767                         // merge all streams should not have any other stream selected
0768                         activeStreams.clear();
0769                     } else {
0770                         activeStreams.removeAll(QString::number(INT_MAX));
0771                     }
0772                     activeStreams << QString::number(streamId);
0773                     activeStreams.sort();
0774                     streamModified = true;
0775                 }
0776                 if (streamModified) {
0777                     if (activeStreams.isEmpty()) {
0778                         activeStreams << QStringLiteral("-1");
0779                     }
0780                     QMap<QString, QString> properties;
0781                     properties.insert(QStringLiteral("kdenlive:active_streams"), activeStreams.join(QLatin1Char(';')));
0782                     Q_EMIT updateClipProperties(m_id, m_originalProperties, properties);
0783                     m_originalProperties = properties;
0784                 } else if (item->text() != item->data(Qt::UserRole + 1).toString()) {
0785                     // Rename event
0786                     QString txt = item->text();
0787                     int row = m_audioStreamsView->row(item) + 1;
0788                     if (!txt.startsWith(QString("%1|").arg(row))) {
0789                         txt.prepend(QString("%1|").arg(row));
0790                     }
0791                     m_controller->renameAudioStream(streamId, txt);
0792                     QSignalBlocker bk(m_audioStreamsView);
0793                     item->setText(txt);
0794                     item->setData(Qt::UserRole + 1, txt);
0795                 }
0796             });
0797             ac->setActive(vix.toInt() == -1);
0798             connect(ac, &KDualAction::activeChanged, this, [this, audioStreamsInfo](bool activated) {
0799                 QMap<QString, QString> properties;
0800                 int vindx = -1;
0801                 if (activated) {
0802                     properties.insert(QStringLiteral("kdenlive:active_streams"), QStringLiteral("-1"));
0803                 } else {
0804                     properties.insert(QStringLiteral("kdenlive:active_streams"), QString());
0805                     vindx = audioStreamsInfo.firstKey();
0806                 }
0807                 properties.insert(QStringLiteral("audio_index"), QString::number(vindx));
0808                 // Find stream position in index
0809                 QMap<int, QString>::const_iterator it = audioStreamsInfo.constFind(vindx);
0810                 if (it != audioStreamsInfo.constEnd()) {
0811                     properties.insert(QStringLiteral("astream"), QString::number(std::distance(audioStreamsInfo.begin(), it)));
0812                 }
0813                 properties.insert(QStringLiteral("set.test_audio"), vindx > -1 ? QStringLiteral("0") : QStringLiteral("1"));
0814                 Q_EMIT updateClipProperties(m_id, m_originalProperties, properties);
0815                 m_originalProperties = properties;
0816             });
0817             // Audio effects
0818             m_audioEffectGroup = new QGroupBox(this);
0819             m_audioEffectGroup->setEnabled(false);
0820             auto *vbox = new QVBoxLayout;
0821             // Normalize
0822             m_normalize = new QCheckBox(i18n("Normalize"), this);
0823             connect(m_normalize, &QCheckBox::stateChanged, this, [this](int state) {
0824                 if (m_activeAudioStreams == -1) {
0825                     // No stream selected, abort
0826                     return;
0827                 }
0828                 if (state == Qt::Checked) {
0829                     // Add swap channels effect
0830                     m_controller->requestAddStreamEffect(m_activeAudioStreams, QStringLiteral("dynamic_loudness"));
0831                 } else {
0832                     // Remove swap channels effect
0833                     m_controller->requestRemoveStreamEffect(m_activeAudioStreams, QStringLiteral("dynamic_loudness"));
0834                 }
0835                 updateStreamIcon(m_audioStreamsView->currentRow(), m_activeAudioStreams);
0836             });
0837             vbox->addWidget(m_normalize);
0838 
0839             // Swap channels
0840             m_swapChannels = new QCheckBox(i18n("Swap channels"), this);
0841             connect(m_swapChannels, &QCheckBox::stateChanged, this, [this](int state) {
0842                 if (m_activeAudioStreams == -1) {
0843                     // No stream selected, abort
0844                     return;
0845                 }
0846                 if (state == Qt::Checked) {
0847                     // Add swap channels effect
0848                     m_controller->requestAddStreamEffect(m_activeAudioStreams, QStringLiteral("channelswap"));
0849                 } else {
0850                     // Remove swap channels effect
0851                     m_controller->requestRemoveStreamEffect(m_activeAudioStreams, QStringLiteral("channelswap"));
0852                 }
0853                 updateStreamIcon(m_audioStreamsView->currentRow(), m_activeAudioStreams);
0854             });
0855             vbox->addWidget(m_swapChannels);
0856             // Copy channel
0857             auto *copyLay = new QHBoxLayout;
0858             copyLay->addWidget(new QLabel(i18n("Copy channel:"), this));
0859             m_copyChannel1 = new QCheckBox(i18n("1"), this);
0860             m_copyChannel2 = new QCheckBox(i18n("2"), this);
0861             m_copyChannelGroup = new QButtonGroup(this);
0862             m_copyChannelGroup->addButton(m_copyChannel1);
0863             m_copyChannelGroup->addButton(m_copyChannel2);
0864             m_copyChannelGroup->setExclusive(false);
0865             copyLay->addWidget(m_copyChannel1);
0866             copyLay->addWidget(m_copyChannel2);
0867             copyLay->addStretch(1);
0868             vbox->addLayout(copyLay);
0869             connect(m_copyChannelGroup, QOverload<QAbstractButton *, bool>::of(&QButtonGroup::buttonToggled), this, [this](QAbstractButton *but, bool) {
0870                 if (but == m_copyChannel1) {
0871                     QSignalBlocker bk(m_copyChannelGroup);
0872                     m_copyChannel2->setChecked(false);
0873                 } else {
0874                     QSignalBlocker bk(m_copyChannelGroup);
0875                     m_copyChannel1->setChecked(false);
0876                 }
0877                 if (m_copyChannel1->isChecked()) {
0878                     m_controller->requestAddStreamEffect(m_activeAudioStreams, QStringLiteral("channelcopy from=0 to=1"));
0879                 } else if (m_copyChannel2->isChecked()) {
0880                     m_controller->requestAddStreamEffect(m_activeAudioStreams, QStringLiteral("channelcopy from=1 to=0"));
0881                 } else {
0882                     // Remove swap channels effect
0883                     m_controller->requestRemoveStreamEffect(m_activeAudioStreams, QStringLiteral("channelcopy"));
0884                 }
0885                 updateStreamIcon(m_audioStreamsView->currentRow(), m_activeAudioStreams);
0886             });
0887             // Gain
0888             auto *gainLay = new QHBoxLayout;
0889             gainLay->addWidget(new QLabel(i18n("Gain:"), this));
0890             m_gain = new QSpinBox(this);
0891             m_gain->setRange(-100, 60);
0892             m_gain->setSuffix(i18n("dB"));
0893             connect(m_gain, QOverload<int>::of(&QSpinBox::valueChanged), this, [this](int value) {
0894                 if (m_activeAudioStreams == -1) {
0895                     // No stream selected, abort
0896                     return;
0897                 }
0898                 if (value == 0) {
0899                     // Remove effect
0900                     m_controller->requestRemoveStreamEffect(m_activeAudioStreams, QStringLiteral("volume"));
0901                 } else {
0902                     m_controller->requestAddStreamEffect(m_activeAudioStreams, QString("volume level=%1").arg(value));
0903                 }
0904                 updateStreamIcon(m_audioStreamsView->currentRow(), m_activeAudioStreams);
0905             });
0906             gainLay->addWidget(m_gain);
0907             gainLay->addStretch(1);
0908             vbox->addLayout(gainLay);
0909 
0910             vbox->addStretch(1);
0911             m_audioEffectGroup->setLayout(vbox);
0912             audioVbox->addWidget(m_audioEffectGroup);
0913 
0914             // Audio sync
0915             hlay = new QHBoxLayout;
0916             hlay->addWidget(new QLabel(i18n("Audio sync:")));
0917             auto *spinSync = new QSpinBox(this);
0918             spinSync->setSuffix(i18n("ms"));
0919             spinSync->setRange(-1000, 1000);
0920             spinSync->setValue(qRound(1000 * m_sourceProperties->get_double("video_delay")));
0921             spinSync->setObjectName(QStringLiteral("video_delay"));
0922             if (spinSync->value() != 0) {
0923                 m_originalProperties.insert(QStringLiteral("video_delay"), QString::number(m_sourceProperties->get_double("video_delay"), 'f'));
0924             }
0925             QObject::connect(spinSync, &QSpinBox::editingFinished, this, [this, spinSync]() {
0926                 QMap<QString, QString> properties;
0927                 properties.insert(QStringLiteral("video_delay"), QString::number(spinSync->value() / 1000., 'f'));
0928                 Q_EMIT updateClipProperties(m_id, m_originalProperties, properties);
0929                 m_originalProperties = properties;
0930             });
0931             hlay->addWidget(spinSync);
0932             audioVbox->addLayout(hlay);
0933         }
0934 
0935         // Colorspace
0936         hlay = new QHBoxLayout;
0937         box = new QCheckBox(i18n("Color space:"), this);
0938         box->setObjectName(QStringLiteral("force_colorspace"));
0939         connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce);
0940         combo = new QComboBox(this);
0941         combo->setObjectName(QStringLiteral("force_colorspace_value"));
0942         combo->addItem(ProfileRepository::getColorspaceDescription(240), 240);
0943         combo->addItem(ProfileRepository::getColorspaceDescription(601), 601);
0944         combo->addItem(ProfileRepository::getColorspaceDescription(709), 709);
0945         combo->addItem(ProfileRepository::getColorspaceDescription(10), 10);
0946         int force_colorspace = m_properties->get_int("force_colorspace");
0947         m_originalProperties.insert(QStringLiteral("force_colorspace"), force_colorspace == 0 ? QStringLiteral("-") : QString::number(force_colorspace));
0948         int colorspace = controller->videoCodecProperty(QStringLiteral("colorspace")).toInt();
0949         if (colorspace == 9) {
0950             colorspace = 10;
0951         }
0952         if (force_colorspace > 0) {
0953             box->setChecked(true);
0954             combo->setEnabled(true);
0955             combo->setCurrentIndex(combo->findData(force_colorspace));
0956         } else if (colorspace > 0) {
0957             combo->setEnabled(false);
0958             combo->setCurrentIndex(combo->findData(colorspace));
0959         } else {
0960             combo->setEnabled(false);
0961         }
0962         connect(box, &QAbstractButton::toggled, combo, &QWidget::setEnabled);
0963         connect(combo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ClipPropertiesController::slotComboValueChanged);
0964         hlay->addWidget(box);
0965         hlay->addWidget(combo);
0966         fpBox->addLayout(hlay);
0967 
0968         // Color range
0969         hlay = new QHBoxLayout;
0970         box = new QCheckBox(i18n("Color range:"), this);
0971         box->setObjectName(QStringLiteral("force_color_range"));
0972         connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce);
0973         combo = new QComboBox(this);
0974         combo->setObjectName(QStringLiteral("force_color_range_value"));
0975         combo->addItem(i18n("Broadcast limited (MPEG)"), 1);
0976         combo->addItem(i18n("Full (JPEG)"), 2);
0977         int color_range = m_properties->get_int("color_range");
0978         if (color_range == 0) {
0979             combo->setCurrentIndex(combo->findData(m_controller->isFullRange() ? 2 : 1));
0980             combo->setEnabled(false);
0981         } else {
0982             box->setChecked(true);
0983             combo->setCurrentIndex(combo->findData(color_range));
0984         }
0985         connect(box, &QAbstractButton::toggled, combo, &QWidget::setEnabled);
0986         connect(combo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ClipPropertiesController::slotComboValueChanged);
0987         hlay->addWidget(box);
0988         hlay->addWidget(combo);
0989         fpBox->addLayout(hlay);
0990 
0991         // Check for variable frame rate
0992         if (m_properties->get_int("meta.media.variable_frame_rate")) {
0993             m_warningMessage.setText(i18n("File uses a variable frame rate, not recommended"));
0994             QAction *ac = new QAction(i18n("Transcode"));
0995             QObject::connect(ac, &QAction::triggered, [id = m_id, resource = controller->clipUrl()]() {
0996                 QMetaObject::invokeMethod(pCore->bin(), "requestTranscoding", Qt::QueuedConnection, Q_ARG(QString, resource), Q_ARG(QString, id), Q_ARG(int, 0),
0997                                           Q_ARG(bool, false));
0998             });
0999             m_warningMessage.setMessageType(KMessageWidget::Warning);
1000             m_warningMessage.addAction(ac);
1001             m_warningMessage.show();
1002         } else {
1003             m_warningMessage.hide();
1004         }
1005     }
1006     // Force properties page
1007     QWidget *forceProp = new QWidget(this);
1008     forceProp->setLayout(fpBox);
1009     forcePage->setWidget(forceProp);
1010     forcePage->setWidgetResizable(true);
1011     // Force audio properties page
1012     QWidget *forceAudioProp = new QWidget(this);
1013     forceAudioProp->setLayout(audioVbox);
1014     forceAudioPage->setWidget(forceAudioProp);
1015     forceAudioPage->setWidgetResizable(true);
1016 
1017     fpBox->addStretch(10);
1018     m_tabWidget->addTab(m_propertiesPage, QString());
1019     m_tabWidget->addTab(forcePage, QString());
1020     m_tabWidget->addTab(forceAudioPage, QString());
1021     m_tabWidget->addTab(m_metaPage, QString());
1022     m_tabWidget->addTab(m_analysisPage, QString());
1023     m_tabWidget->setTabIcon(0, QIcon::fromTheme(QStringLiteral("edit-find")));
1024     m_tabWidget->setTabToolTip(0, i18n("File info"));
1025     m_tabWidget->setWhatsThis(xi18nc("@info:whatsthis", "Displays detailed information about the file."));
1026     m_tabWidget->setTabIcon(1, QIcon::fromTheme(QStringLiteral("document-edit")));
1027     m_tabWidget->setTabToolTip(1, i18n("Properties"));
1028     m_tabWidget->setWhatsThis(xi18nc("@info:whatsthis", "Displays detailed information about the video data/codec."));
1029     m_tabWidget->setTabIcon(2, QIcon::fromTheme(QStringLiteral("audio-volume-high")));
1030     m_tabWidget->setTabToolTip(2, i18n("Audio Properties"));
1031     m_tabWidget->setWhatsThis(xi18nc("@info:whatsthis", "Displays detailed information about the audio streams/data/codec."));
1032     m_tabWidget->setTabIcon(3, QIcon::fromTheme(QStringLiteral("view-grid")));
1033     m_tabWidget->setTabToolTip(3, i18n("Metadata"));
1034     m_tabWidget->setTabIcon(4, QIcon::fromTheme(QStringLiteral("visibility")));
1035     m_tabWidget->setTabToolTip(4, i18n("Analysis"));
1036     m_tabWidget->setWhatsThis(xi18nc("@info:whatsthis", "Displays analysis data."));
1037     m_tabWidget->setCurrentIndex(KdenliveSettings::properties_panel_page());
1038     if (m_type == ClipType::Color) {
1039         m_tabWidget->setTabEnabled(0, false);
1040     }
1041     connect(m_tabWidget, &QTabWidget::currentChanged, this, &ClipPropertiesController::updateTab);
1042 }
1043 
1044 ClipPropertiesController::~ClipPropertiesController() = default;
1045 
1046 void ClipPropertiesController::updateStreamIcon(int row, int streamIndex)
1047 {
1048     QStringList effects = m_controller->getAudioStreamEffect(streamIndex);
1049     QListWidgetItem *item = m_audioStreamsView->item(row);
1050     if (item) {
1051         item->setIcon(effects.isEmpty() ? QIcon() : QIcon::fromTheme(QStringLiteral("favorite")));
1052     }
1053 }
1054 
1055 void ClipPropertiesController::updateTab(int ix)
1056 {
1057     KdenliveSettings::setProperties_panel_page(ix);
1058 }
1059 
1060 void ClipPropertiesController::slotReloadProperties()
1061 {
1062     mlt_color color;
1063     m_properties.reset(new Mlt::Properties(m_controller->properties()));
1064     m_sourceProperties.reset(new Mlt::Properties());
1065     m_controller->mirrorOriginalProperties(m_sourceProperties);
1066     m_clipLabel->setText(m_properties->get("kdenlive:clipname"));
1067     switch (m_type) {
1068     case ClipType::Color:
1069         m_originalProperties.insert(QStringLiteral("resource"), m_properties->get("resource"));
1070         m_originalProperties.insert(QStringLiteral("out"), m_properties->get("out"));
1071         m_originalProperties.insert(QStringLiteral("length"), m_properties->get("length"));
1072         Q_EMIT modified(m_properties->get_int("length"));
1073         color = m_properties->get_color("resource");
1074         Q_EMIT modified(QColor::fromRgb(color.r, color.g, color.b));
1075         break;
1076     case ClipType::TextTemplate:
1077         m_textEdit->setPlainText(m_properties->get("templatetext"));
1078         break;
1079     case ClipType::Playlist:
1080     case ClipType::Image:
1081     case ClipType::AV:
1082     case ClipType::Video: {
1083         QString proxy = m_properties->get("kdenlive:proxy");
1084         if (proxy != m_originalProperties.value(QStringLiteral("kdenlive:proxy"))) {
1085             m_originalProperties.insert(QStringLiteral("kdenlive:proxy"), proxy);
1086             Q_EMIT proxyModified(proxy);
1087         }
1088         if (m_audioStreamsView && m_audioStreamsView->count() > 0) {
1089             int audio_ix = m_properties->get_int("audio_index");
1090             m_originalProperties.insert(QStringLiteral("kdenlive:active_streams"), m_properties->get("kdenlive:active_streams"));
1091             if (audio_ix != m_originalProperties.value(QStringLiteral("audio_index")).toInt()) {
1092                 QSignalBlocker bk(m_audioStream);
1093                 m_originalProperties.insert(QStringLiteral("audio_index"), QString::number(audio_ix));
1094             }
1095             QList<int> enabledStreams = m_controller->activeStreams().keys();
1096             qDebug() << "=== GOT ACTIVE STREAMS: " << enabledStreams;
1097             QSignalBlocker bk(m_audioStreamsView);
1098             for (int ix = 0; ix < m_audioStreamsView->count(); ix++) {
1099                 QListWidgetItem *item = m_audioStreamsView->item(ix);
1100                 int stream = item->data(Qt::UserRole).toInt();
1101                 item->setCheckState(enabledStreams.contains(stream) ? Qt::Checked : Qt::Unchecked);
1102             }
1103         }
1104         break;
1105     }
1106     case ClipType::Timeline: {
1107         int tracks = m_properties->get_int("kdenlive:sequenceproperties.tracksCount");
1108         QList<QStringList> propertyMap;
1109         propertyMap.append({i18n("Tracks:"), QString::number(tracks)});
1110         fillProperties();
1111         break;
1112     }
1113     default:
1114         break;
1115     }
1116 }
1117 
1118 void ClipPropertiesController::slotColorModified(const QColor &newcolor)
1119 {
1120     QMap<QString, QString> properties;
1121     properties.insert(QStringLiteral("resource"), newcolor.name(QColor::HexArgb));
1122     QMap<QString, QString> oldProperties;
1123     oldProperties.insert(QStringLiteral("resource"), m_properties->get("resource"));
1124     Q_EMIT updateClipProperties(m_id, oldProperties, properties);
1125 }
1126 
1127 void ClipPropertiesController::slotDurationChanged(int duration)
1128 {
1129     QMap<QString, QString> properties;
1130     // kdenlive_length is the default duration for image / title clips
1131     int kdenlive_length = m_properties->time_to_frames(m_properties->get("kdenlive:duration"));
1132     int current_length = m_properties->get_int("length");
1133     if (kdenlive_length > 0) {
1134         // special case, image/title clips store default duration in kdenlive:duration property
1135         properties.insert(QStringLiteral("kdenlive:duration"), m_properties->frames_to_time(duration));
1136         if (duration > current_length) {
1137             properties.insert(QStringLiteral("length"), m_properties->frames_to_time(duration));
1138             properties.insert(QStringLiteral("out"), m_properties->frames_to_time(duration - 1));
1139         }
1140     } else {
1141         properties.insert(QStringLiteral("length"), m_properties->frames_to_time(duration));
1142         properties.insert(QStringLiteral("out"), m_properties->frames_to_time(duration - 1));
1143     }
1144     Q_EMIT updateClipProperties(m_id, m_originalProperties, properties);
1145     m_originalProperties = properties;
1146 }
1147 
1148 void ClipPropertiesController::slotEnableForce(int state)
1149 {
1150     auto *box = qobject_cast<QCheckBox *>(sender());
1151     if (!box) {
1152         return;
1153     }
1154     QString param = box->objectName();
1155     QMap<QString, QString> properties;
1156     if (state == Qt::Unchecked) {
1157         // The force property was disable, remove it / reset default if necessary
1158         if (param == QLatin1String("force_duration")) {
1159             // special case, reset original duration
1160             auto *timePos = findChild<TimecodeDisplay *>(param + QStringLiteral("_value"));
1161             timePos->setValue(m_properties->get_int("kdenlive:original_length"));
1162             int original = m_properties->get_int("kdenlive:original_length");
1163             m_properties->set("kdenlive:original_length", nullptr);
1164             slotDurationChanged(original);
1165             return;
1166         }
1167         if (param == QLatin1String("kdenlive:transparency")) {
1168             properties.insert(param, QString());
1169         } else if (param == QLatin1String("force_ar")) {
1170             properties.insert(QStringLiteral("force_aspect_den"), QString());
1171             properties.insert(QStringLiteral("force_aspect_num"), QString());
1172             properties.insert(QStringLiteral("force_aspect_ratio"), QString());
1173         } else if (param == QLatin1String("autorotate")) {
1174             properties.insert(QStringLiteral("autorotate"), QString());
1175         } else if (param == QLatin1String("force_color_range")) {
1176             properties.insert(QStringLiteral("color_range"), QString());
1177         } else {
1178             properties.insert(param, QString());
1179         }
1180     } else {
1181         // A force property was set
1182         if (param == QLatin1String("force_duration")) {
1183             int original_length = m_properties->get_int("kdenlive:original_length");
1184             if (original_length == 0) {
1185                 int kdenlive_length = m_properties->time_to_frames(m_properties->get("kdenlive:duration"));
1186                 m_properties->set("kdenlive:original_length", kdenlive_length > 0 ? m_properties->get("kdenlive:duration") : m_properties->get("length"));
1187             }
1188         } else if (param == QLatin1String("force_fps")) {
1189             auto *spin = findChild<QDoubleSpinBox *>(param + QStringLiteral("_value"));
1190             if (!spin) {
1191                 return;
1192             }
1193             properties.insert(param, QString::number(spin->value(), 'f'));
1194         } else if (param == QLatin1String("threads")) {
1195             auto *spin = findChild<QSpinBox *>(param + QStringLiteral("_value"));
1196             if (!spin) {
1197                 return;
1198             }
1199             properties.insert(param, QString::number(spin->value()));
1200         } else if (param == QLatin1String("force_colorspace") || param == QLatin1String("force_progressive") || param == QLatin1String("force_tff")) {
1201             auto *combo = findChild<QComboBox *>(param + QStringLiteral("_value"));
1202             if (!combo) {
1203                 return;
1204             }
1205             properties.insert(param, QString::number(combo->currentData().toInt()));
1206         } else if (param == QLatin1String("force_color_range")) {
1207             auto *combo = findChild<QComboBox *>(param + QStringLiteral("_value"));
1208             if (!combo) {
1209                 return;
1210             }
1211             properties.insert(QStringLiteral("color_range"), QString::number(combo->currentData().toInt()));
1212         } else if (param == QLatin1String("autorotate")) {
1213             properties.insert(QStringLiteral("autorotate"), QStringLiteral("0"));
1214         } else if (param == QLatin1String("force_ar")) {
1215             auto *spin = findChild<QSpinBox *>(QStringLiteral("force_aspect_num_value"));
1216             auto *spin2 = findChild<QSpinBox *>(QStringLiteral("force_aspect_den_value"));
1217             if ((spin == nullptr) || (spin2 == nullptr)) {
1218                 return;
1219             }
1220             properties.insert(QStringLiteral("force_aspect_den"), QString::number(spin2->value()));
1221             properties.insert(QStringLiteral("force_aspect_num"), QString::number(spin->value()));
1222             properties.insert(QStringLiteral("force_aspect_ratio"), QString::number(double(spin->value()) / spin2->value(), 'f'));
1223         } else if (param == QLatin1String("disable_exif")) {
1224             properties.insert(QStringLiteral("disable_exif"), QString::number(1));
1225         }
1226     }
1227     if (properties.isEmpty()) {
1228         return;
1229     }
1230     Q_EMIT updateClipProperties(m_id, m_originalProperties, properties);
1231     m_originalProperties = properties;
1232 }
1233 
1234 void ClipPropertiesController::slotValueChanged(double value)
1235 {
1236     auto *box = qobject_cast<QDoubleSpinBox *>(sender());
1237     if (!box) {
1238         return;
1239     }
1240     QString param = box->objectName().section(QLatin1Char('_'), 0, -2);
1241     QMap<QString, QString> properties;
1242     properties.insert(param, QString::number(value, 'f'));
1243     Q_EMIT updateClipProperties(m_id, m_originalProperties, properties);
1244     m_originalProperties = properties;
1245 }
1246 
1247 void ClipPropertiesController::slotValueChanged(int value)
1248 {
1249     auto *box = qobject_cast<QSpinBox *>(sender());
1250     if (!box) {
1251         return;
1252     }
1253     QString param = box->objectName().section(QLatin1Char('_'), 0, -2);
1254     QMap<QString, QString> properties;
1255     properties.insert(param, QString::number(value));
1256     Q_EMIT updateClipProperties(m_id, m_originalProperties, properties);
1257     m_originalProperties = properties;
1258 }
1259 
1260 void ClipPropertiesController::slotAspectValueChanged(int)
1261 {
1262     auto *spin = findChild<QSpinBox *>(QStringLiteral("force_aspect_num_value"));
1263     auto *spin2 = findChild<QSpinBox *>(QStringLiteral("force_aspect_den_value"));
1264     if ((spin == nullptr) || (spin2 == nullptr)) {
1265         return;
1266     }
1267     QMap<QString, QString> properties;
1268     properties.insert(QStringLiteral("force_aspect_den"), QString::number(spin2->value()));
1269     properties.insert(QStringLiteral("force_aspect_num"), QString::number(spin->value()));
1270     properties.insert(QStringLiteral("force_aspect_ratio"), QString::number(double(spin->value()) / spin2->value(), 'f'));
1271     Q_EMIT updateClipProperties(m_id, m_originalProperties, properties);
1272     m_originalProperties = properties;
1273 }
1274 
1275 void ClipPropertiesController::slotComboValueChanged()
1276 {
1277     auto *box = qobject_cast<QComboBox *>(sender());
1278     if (!box) {
1279         return;
1280     }
1281     QString param = box->objectName().section(QLatin1Char('_'), 0, -2);
1282     if (param == QLatin1String("force_color_range")) {
1283         param = QStringLiteral("color_range");
1284     }
1285     QMap<QString, QString> properties;
1286     properties.insert(param, QString::number(box->currentData().toInt()));
1287     Q_EMIT updateClipProperties(m_id, m_originalProperties, properties);
1288     m_originalProperties = properties;
1289 }
1290 
1291 void ClipPropertiesController::fillProperties()
1292 {
1293     m_clipProperties.clear();
1294     QList<QStringList> propertyMap;
1295     m_propertiesTree->clear();
1296     m_propertiesTree->setSortingEnabled(false);
1297 
1298     if (m_type == ClipType::Image || m_type == ClipType::AV || m_type == ClipType::Audio || m_type == ClipType::Video) {
1299         // Read File Metadata through KDE's metadata system
1300         KFileMetaData::ExtractorCollection metaDataCollection;
1301         QMimeDatabase mimeDatabase;
1302         QMimeType mimeType = mimeDatabase.mimeTypeForFile(m_controller->clipUrl());
1303         for (KFileMetaData::Extractor *plugin : metaDataCollection.fetchExtractors(mimeType.name())) {
1304             ExtractionResult extractionResult(m_controller->clipUrl(), mimeType.name(), m_propertiesTree);
1305             plugin->extract(&extractionResult);
1306         }
1307     }
1308 
1309     // Get MLT's metadata
1310     if (m_type == ClipType::Image) {
1311         int width = m_sourceProperties->get_int("meta.media.width");
1312         int height = m_sourceProperties->get_int("meta.media.height");
1313         propertyMap.append({i18n("Image size:"), QString::number(width) + QLatin1Char('x') + QString::number(height)});
1314     } else if (m_type == ClipType::SlideShow) {
1315         int ttl = m_sourceProperties->get_int("ttl");
1316         propertyMap.append({i18n("Image duration:"), m_properties->frames_to_time(ttl)});
1317         if (ttl > 0) {
1318             int length = m_sourceProperties->get_int("length");
1319             if (length == 0) {
1320                 length = m_properties->time_to_frames(m_sourceProperties->get("length"));
1321             }
1322             int cnt = qCeil(length / ttl);
1323             propertyMap.append({i18n("Image count:"), QString::number(cnt)});
1324         }
1325     } else if (m_type == ClipType::AV || m_type == ClipType::Video || m_type == ClipType::Audio) {
1326         int vindex = m_sourceProperties->get_int("video_index");
1327         int default_audio = m_sourceProperties->get_int("audio_index");
1328 
1329         // Find maximum stream index values
1330         m_videoStreams.clear();
1331         int aStreams = m_sourceProperties->get_int("meta.media.nb_streams");
1332         for (int ix = 0; ix < aStreams; ++ix) {
1333             char property[200];
1334             snprintf(property, sizeof(property), "meta.media.%d.stream.type", ix);
1335             QString type = m_sourceProperties->get(property);
1336             if (type == QLatin1String("video")) {
1337                 m_videoStreams << ix;
1338             }
1339         }
1340         m_clipProperties.insert(QStringLiteral("default_video"), QString::number(vindex));
1341         m_clipProperties.insert(QStringLiteral("default_audio"), QString::number(default_audio));
1342 
1343         if (vindex > -1) {
1344             // We have a video stream
1345             QString codecInfo = QString("meta.media.%1.codec.").arg(vindex);
1346             QString streamInfo = QString("meta.media.%1.stream.").arg(vindex);
1347             QString property = codecInfo + QStringLiteral("long_name");
1348             QString codec = m_sourceProperties->get(property.toUtf8().constData());
1349             if (!codec.isEmpty()) {
1350                 propertyMap.append({i18n("Video codec:"), codec});
1351             }
1352             int width = m_sourceProperties->get_int("meta.media.width");
1353             int height = m_sourceProperties->get_int("meta.media.height");
1354             propertyMap.append({i18n("Frame size:"), QString::number(width) + QLatin1Char('x') + QString::number(height)});
1355 
1356             property = streamInfo + QStringLiteral("frame_rate");
1357             QString fpsValue = m_sourceProperties->get(property.toUtf8().constData());
1358             if (!fpsValue.isEmpty()) {
1359                 propertyMap.append({i18n("Frame rate:"), fpsValue});
1360             } else {
1361                 int rate_den = m_sourceProperties->get_int("meta.media.frame_rate_den");
1362                 if (rate_den > 0) {
1363                     double fps = double(m_sourceProperties->get_int("meta.media.frame_rate_num")) / rate_den;
1364                     propertyMap.append({i18n("Frame rate:"), QString::number(fps, 'f', 2)});
1365                 }
1366             }
1367             property = codecInfo + QStringLiteral("bit_rate");
1368             int bitrate = m_sourceProperties->get_int(property.toUtf8().constData()) / 1000;
1369             if (bitrate > 0) {
1370                 propertyMap.append({i18n("Video bitrate:"), QString::number(bitrate) + QLatin1Char(' ') + i18nc("Kilobytes per seconds", "kb/s")});
1371             }
1372 
1373             int scan = m_sourceProperties->get_int("meta.media.progressive");
1374             propertyMap.append({i18n("Scanning:"), (scan == 1 ? i18n("Progressive") : i18n("Interlaced"))});
1375 
1376             property = codecInfo + QStringLiteral("sample_aspect_ratio");
1377             double par = m_sourceProperties->get_double(property.toUtf8().constData());
1378             if (qFuzzyIsNull(par)) {
1379                 // Read media aspect ratio
1380                 par = m_sourceProperties->get_double("meta.media.sample_aspect_num");
1381                 double den = m_sourceProperties->get_double("meta.media.sample_aspect_den");
1382                 if (den > 0) {
1383                     par /= den;
1384                 }
1385             }
1386             propertyMap.append({i18n("Pixel aspect ratio:"), QString::number(par, 'f', 3)});
1387             property = codecInfo + QStringLiteral("pix_fmt");
1388             propertyMap.append({i18n("Pixel format:"), m_sourceProperties->get(property.toUtf8().constData())});
1389             property = codecInfo + QStringLiteral("colorspace");
1390             int colorspace = m_sourceProperties->get_int(property.toUtf8().constData());
1391             propertyMap.append({i18n("Colorspace:"), ProfileRepository::getColorspaceDescription(colorspace)});
1392 
1393             int b_frames = m_sourceProperties->get_int("meta.media.has_b_frames");
1394             propertyMap.append({i18n("B frames:"), (b_frames == 1 ? i18n("Yes") : i18n("No"))});
1395         }
1396         if (default_audio > -1) {
1397             propertyMap.append({i18n("Audio streams:"), QString::number(m_controller->audioStreamsCount())});
1398 
1399             QString codecInfo = QString("meta.media.%1.codec.").arg(default_audio);
1400             QString property = codecInfo + QStringLiteral("long_name");
1401             QString codec = m_sourceProperties->get(property.toUtf8().constData());
1402             if (!codec.isEmpty()) {
1403                 propertyMap.append({i18n("Audio codec:"), codec});
1404             }
1405             property = codecInfo + QStringLiteral("channels");
1406             int channels = m_sourceProperties->get_int(property.toUtf8().constData());
1407             propertyMap.append({i18n("Audio channels:"), QString::number(channels)});
1408 
1409             property = codecInfo + QStringLiteral("sample_rate");
1410             int srate = m_sourceProperties->get_int(property.toUtf8().constData());
1411             propertyMap.append({i18n("Audio frequency:"), QString::number(srate) + QLatin1Char(' ') + i18nc("Herz", "Hz")});
1412 
1413             property = codecInfo + QStringLiteral("bit_rate");
1414             int bitrate = m_sourceProperties->get_int(property.toUtf8().constData()) / 1000;
1415             if (bitrate > 0) {
1416                 propertyMap.append({i18n("Audio bitrate:"), QString::number(bitrate) + QLatin1Char(' ') + i18nc("Kilobytes per seconds", "kb/s")});
1417             }
1418         }
1419     } else if (m_type == ClipType::Timeline) {
1420         int tracks = m_sourceProperties->get_int("kdenlive:sequenceproperties.tracksCount");
1421         qDebug() << "============\nUPDATING TRACKS CNT: " << tracks << "\n============";
1422         propertyMap.append({i18n("Tracks:"), QString::number(tracks)});
1423     }
1424 
1425     qint64 filesize = m_sourceProperties->get_int64("kdenlive:file_size");
1426     if (filesize > 0) {
1427         QLocale locale(QLocale::system()); // use the user's locale for getting proper separators!
1428         propertyMap.append({i18n("File size:"), KIO::convertSize(size_t(filesize)) + QStringLiteral(" (") + locale.toString(filesize) + QLatin1Char(')')});
1429     }
1430     for (int i = 0; i < propertyMap.count(); i++) {
1431         auto *item = new QTreeWidgetItem(m_propertiesTree, propertyMap.at(i));
1432         item->setToolTip(1, propertyMap.at(i).at(1));
1433     }
1434     m_propertiesTree->setSortingEnabled(true);
1435     m_propertiesTree->resizeColumnToContents(0);
1436 }
1437 
1438 void ClipPropertiesController::slotFillMeta(QTreeWidget *tree)
1439 {
1440     tree->clear();
1441     if (m_type != ClipType::AV && m_type != ClipType::Video && m_type != ClipType::Image) {
1442         // Currently, we only use exiftool on video files
1443         return;
1444     }
1445     int exifUsed = m_controller->getProducerIntProperty(QStringLiteral("kdenlive:exiftool"));
1446     if (exifUsed == 1) {
1447         Mlt::Properties subProperties;
1448         subProperties.pass_values(*m_properties, "kdenlive:meta.exiftool.");
1449         if (subProperties.count() > 0) {
1450             QTreeWidgetItem *exif = new QTreeWidgetItem(tree, {i18n("Exif"), QString()});
1451             exif->setExpanded(true);
1452             for (int i = 0; i < subProperties.count(); i++) {
1453                 new QTreeWidgetItem(exif, {subProperties.get_name(i), subProperties.get(i)});
1454             }
1455         }
1456     } else if (KdenliveSettings::use_exiftool()) {
1457         QString url = m_controller->clipUrl();
1458         // Check for Canon THM file
1459         url = url.section(QLatin1Char('.'), 0, -2) + QStringLiteral(".THM");
1460         if (QFile::exists(url)) {
1461             QString exifToolBinary = QStandardPaths::findExecutable(QStringLiteral("exiftool"));
1462             if (!exifToolBinary.isEmpty()) {
1463                 // Read the exif metadata embedded in the THM file
1464                 QProcess p;
1465                 QStringList args = {QStringLiteral("-g"), QStringLiteral("-args"), url};
1466                 p.start(exifToolBinary, args);
1467                 p.waitForFinished();
1468                 QString res = p.readAllStandardOutput();
1469                 m_controller->setProducerProperty(QStringLiteral("kdenlive:exiftool"), 1);
1470                 QTreeWidgetItem *exif = nullptr;
1471                 QStringList list = res.split(QLatin1Char('\n'));
1472                 for (const QString &tagline : qAsConst(list)) {
1473                     if (tagline.startsWith(QLatin1String("-File")) || tagline.startsWith(QLatin1String("-ExifTool"))) {
1474                         continue;
1475                     }
1476                     QString tag = tagline.section(QLatin1Char(':'), 1).simplified();
1477                     if (tag.startsWith(QLatin1String("ImageWidth")) || tag.startsWith(QLatin1String("ImageHeight"))) {
1478                         continue;
1479                     }
1480                     if (!tag.section(QLatin1Char('='), 0, 0).isEmpty() && !tag.section(QLatin1Char('='), 1).simplified().isEmpty()) {
1481                         if (!exif) {
1482                             exif = new QTreeWidgetItem(tree, {i18n("Exif"), QString()});
1483                             exif->setExpanded(true);
1484                         }
1485                         m_controller->setProducerProperty("kdenlive:meta.exiftool." + tag.section(QLatin1Char('='), 0, 0),
1486                                                           tag.section(QLatin1Char('='), 1).simplified());
1487                         new QTreeWidgetItem(exif, {tag.section(QLatin1Char('='), 0, 0), tag.section(QLatin1Char('='), 1).simplified()});
1488                     }
1489                 }
1490             }
1491         } else {
1492             if (m_type == ClipType::Image || m_controller->codec(false) == QLatin1String("h264")) {
1493                 QString exifToolBinary = QStandardPaths::findExecutable(QStringLiteral("exiftool"));
1494                 if (!exifToolBinary.isEmpty()) {
1495                     QProcess p;
1496                     QStringList args = {QStringLiteral("-g"), QStringLiteral("-args"), m_controller->clipUrl()};
1497                     p.start(exifToolBinary, args);
1498                     p.waitForFinished();
1499                     QString res = p.readAllStandardOutput();
1500                     if (m_type != ClipType::Image) {
1501                         m_controller->setProducerProperty(QStringLiteral("kdenlive:exiftool"), 1);
1502                     }
1503                     QTreeWidgetItem *exif = nullptr;
1504                     QStringList list = res.split(QLatin1Char('\n'));
1505                     for (const QString &tagline : qAsConst(list)) {
1506                         if (m_type != ClipType::Image && !tagline.startsWith(QLatin1String("-H264"))) {
1507                             continue;
1508                         }
1509                         QString tag = tagline.section(QLatin1Char(':'), 1);
1510                         if (tag.startsWith(QLatin1String("ImageWidth")) || tag.startsWith(QLatin1String("ImageHeight"))) {
1511                             continue;
1512                         }
1513                         if (!exif) {
1514                             exif = new QTreeWidgetItem(tree, {i18n("Exif"), QString()});
1515                             exif->setExpanded(true);
1516                         }
1517                         if (m_type != ClipType::Image) {
1518                             // Do not store image exif metadata in project file, would be too much noise
1519                             m_controller->setProducerProperty("kdenlive:meta.exiftool." + tag.section(QLatin1Char('='), 0, 0),
1520                                                               tag.section(QLatin1Char('='), 1).simplified());
1521                         }
1522                         new QTreeWidgetItem(exif, {tag.section(QLatin1Char('='), 0, 0), tag.section(QLatin1Char('='), 1).simplified()});
1523                     }
1524                 }
1525             }
1526         }
1527     }
1528     int magic = m_controller->getProducerIntProperty(QStringLiteral("kdenlive:magiclantern"));
1529     if (magic == 1) {
1530         Mlt::Properties subProperties;
1531         subProperties.pass_values(*m_properties, "kdenlive:meta.magiclantern.");
1532         QTreeWidgetItem *magicL = nullptr;
1533         for (int i = 0; i < subProperties.count(); i++) {
1534             if (!magicL) {
1535                 magicL = new QTreeWidgetItem(tree, {i18n("Magic Lantern"), QString()});
1536                 QIcon icon(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("meta_magiclantern.png")));
1537                 magicL->setIcon(0, icon);
1538                 magicL->setExpanded(true);
1539             }
1540             new QTreeWidgetItem(magicL, {subProperties.get_name(i), subProperties.get(i)});
1541         }
1542     } else if (m_type != ClipType::Image && KdenliveSettings::use_magicLantern()) {
1543         QString url = m_controller->clipUrl();
1544         url = url.section(QLatin1Char('.'), 0, -2) + QStringLiteral(".LOG");
1545         if (QFile::exists(url)) {
1546             QFile file(url);
1547             if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
1548                 m_controller->setProducerProperty(QStringLiteral("kdenlive:magiclantern"), 1);
1549                 QTreeWidgetItem *magicL = nullptr;
1550                 while (!file.atEnd()) {
1551                     QString line = file.readLine().simplified();
1552                     if (line.startsWith('#') || line.isEmpty() || !line.contains(QLatin1Char(':'))) {
1553                         continue;
1554                     }
1555                     if (line.startsWith(QLatin1String("CSV data"))) {
1556                         break;
1557                     }
1558                     m_controller->setProducerProperty("kdenlive:meta.magiclantern." + line.section(QLatin1Char(':'), 0, 0).simplified(),
1559                                                       line.section(QLatin1Char(':'), 1).simplified());
1560                     if (!magicL) {
1561                         magicL = new QTreeWidgetItem(tree, {i18n("Magic Lantern"), QString()});
1562                         QIcon icon(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("meta_magiclantern.png")));
1563                         magicL->setIcon(0, icon);
1564                         magicL->setExpanded(true);
1565                     }
1566                     new QTreeWidgetItem(magicL, {line.section(QLatin1Char(':'), 0, 0).simplified(), line.section(QLatin1Char(':'), 1).simplified()});
1567                 }
1568             }
1569         }
1570 
1571         // if (!meta.isEmpty())
1572         // clip->setMetadata(meta, "Magic Lantern");
1573         // clip->setProperty("magiclantern", "1");
1574     }
1575     tree->resizeColumnToContents(0);
1576 }
1577 
1578 void ClipPropertiesController::slotFillAnalysisData()
1579 {
1580     m_analysisTree->clear();
1581     Mlt::Properties subProperties;
1582     subProperties.pass_values(*m_properties, "kdenlive:clipanalysis.");
1583     if (subProperties.count() > 0) {
1584         for (int i = 0; i < subProperties.count(); i++) {
1585             new QTreeWidgetItem(m_analysisTree, {subProperties.get_name(i), subProperties.get(i)});
1586         }
1587     }
1588     m_analysisTree->resizeColumnToContents(0);
1589 }
1590 
1591 void ClipPropertiesController::slotDeleteAnalysis()
1592 {
1593     QTreeWidgetItem *current = m_analysisTree->currentItem();
1594     if (!current) {
1595         return;
1596     }
1597     Q_EMIT editAnalysis(m_id, "kdenlive:clipanalysis." + current->text(0), QString());
1598 }
1599 
1600 void ClipPropertiesController::slotSaveAnalysis()
1601 {
1602     const QString url = QFileDialog::getSaveFileName(this, i18nc("@title:window", "Save Analysis Data"), QFileInfo(m_controller->clipUrl()).absolutePath(),
1603                                                      i18n("Text File (*.txt)"));
1604     if (url.isEmpty()) {
1605         return;
1606     }
1607     KSharedConfigPtr config = KSharedConfig::openConfig(url, KConfig::SimpleConfig);
1608     KConfigGroup analysisConfig(config, "Analysis");
1609     QTreeWidgetItem *current = m_analysisTree->currentItem();
1610     analysisConfig.writeEntry(current->text(0), current->text(1));
1611 }
1612 
1613 void ClipPropertiesController::slotLoadAnalysis()
1614 {
1615     const QString url = QFileDialog::getOpenFileName(this, i18nc("@title:window", "Open Analysis Data"), QFileInfo(m_controller->clipUrl()).absolutePath(),
1616                                                      i18n("Text File (*.txt)"));
1617     if (url.isEmpty()) {
1618         return;
1619     }
1620     KSharedConfigPtr config = KSharedConfig::openConfig(url, KConfig::SimpleConfig);
1621     KConfigGroup transConfig(config, "Analysis");
1622     // read the entries
1623     QMap<QString, QString> profiles = transConfig.entryMap();
1624     QMapIterator<QString, QString> i(profiles);
1625     while (i.hasNext()) {
1626         i.next();
1627         Q_EMIT editAnalysis(m_id, "kdenlive:clipanalysis." + i.key(), i.value());
1628     }
1629 }
1630 
1631 void ClipPropertiesController::slotTextChanged()
1632 {
1633     QMap<QString, QString> properties;
1634     properties.insert(QStringLiteral("templatetext"), m_textEdit->toPlainText());
1635     Q_EMIT updateClipProperties(m_id, m_originalProperties, properties);
1636     m_originalProperties = properties;
1637 }
1638 
1639 void ClipPropertiesController::activatePage(int ix)
1640 {
1641     m_tabWidget->setCurrentIndex(ix);
1642 }
1643 
1644 void ClipPropertiesController::updateStreamInfo(int streamIndex)
1645 {
1646     QStringList effects = m_controller->getAudioStreamEffect(m_activeAudioStreams);
1647     QListWidgetItem *item = nullptr;
1648     for (int ix = 0; ix < m_audioStreamsView->count(); ix++) {
1649         QListWidgetItem *it = m_audioStreamsView->item(ix);
1650         int stream = it->data(Qt::UserRole).toInt();
1651         if (stream == m_activeAudioStreams) {
1652             item = it;
1653             break;
1654         }
1655     }
1656     if (item) {
1657         item->setIcon(effects.isEmpty() ? QIcon() : QIcon::fromTheme(QStringLiteral("favorite")));
1658     }
1659     if (streamIndex == m_activeAudioStreams) {
1660         QSignalBlocker bk(m_swapChannels);
1661         QSignalBlocker bk1(m_copyChannelGroup);
1662         QSignalBlocker bk2(m_normalize);
1663         m_swapChannels->setChecked(effects.contains(QLatin1String("channelswap")));
1664         m_copyChannel1->setChecked(effects.contains(QStringLiteral("channelcopy from=0 to=1")));
1665         m_copyChannel2->setChecked(effects.contains(QStringLiteral("channelcopy from=1 to=0")));
1666         m_normalize->setChecked(effects.contains(QStringLiteral("dynamic_loudness")));
1667         int gain = 0;
1668         for (const QString &st : qAsConst(effects)) {
1669             if (st.startsWith(QLatin1String("volume "))) {
1670                 QSignalBlocker bk3(m_gain);
1671                 gain = st.section(QLatin1Char('='), 1).toInt();
1672                 break;
1673             }
1674         }
1675         QSignalBlocker bk3(m_gain);
1676         m_gain->setValue(gain);
1677     }
1678 }