File indexing completed on 2024-03-24 04:54:35
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 }