File indexing completed on 2024-04-21 04:51:19

0001 /*
0002     SPDX-FileCopyrightText: 2008 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003 
0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "encodingprofilesdialog.h"
0008 #include "core.h"
0009 #include "dialogs/wizard.h"
0010 #include "kdenlivesettings.h"
0011 #include "profiles/profilemodel.hpp"
0012 #include "profiles/profilerepository.hpp"
0013 
0014 #include "klocalizedstring.h"
0015 #include <KMessageWidget>
0016 #include <QLineEdit>
0017 #include <QPlainTextEdit>
0018 #include <QStandardItemModel>
0019 #include <QStandardPaths>
0020 #include <QVBoxLayout>
0021 
0022 QString EncodingProfilesManager::configGroupName(ProfileType type)
0023 {
0024     QString groupName;
0025     switch (type) {
0026     case ProfileType::ProxyClips:
0027         groupName = QStringLiteral("proxy");
0028         break;
0029     case ProfileType::V4LCapture:
0030         groupName = QStringLiteral("video4linux");
0031         break;
0032     case ProfileType::ScreenCapture:
0033         groupName = QStringLiteral("screengrab");
0034         break;
0035     case ProfileType::DecklinkCapture:
0036         groupName = QStringLiteral("decklink");
0037         break;
0038     case ProfileType::TimelinePreview:
0039     default:
0040         groupName = QStringLiteral("timelinepreview");
0041         break;
0042     }
0043     return groupName;
0044 }
0045 
0046 EncodingProfilesDialog::EncodingProfilesDialog(EncodingProfilesManager::ProfileType profileType, QWidget *parent)
0047     : QDialog(parent)
0048     , m_configGroup(nullptr)
0049 {
0050     setupUi(this);
0051     setWindowTitle(i18nc("@title:window", "Manage Encoding Profiles"));
0052     profile_type->addItem(i18n("Proxy Clips"), EncodingProfilesManager::ProxyClips);
0053     profile_type->addItem(i18n("Timeline Preview"), EncodingProfilesManager::TimelinePreview);
0054     profile_type->addItem(i18n("Video4Linux Capture"), EncodingProfilesManager::V4LCapture);
0055     profile_type->addItem(i18n("Screen Capture"), EncodingProfilesManager::ScreenCapture);
0056     profile_type->addItem(i18n("Decklink Capture"), EncodingProfilesManager::DecklinkCapture);
0057 
0058     m_configFile = new KConfig(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
0059     profile_type->setCurrentIndex(profileType);
0060     connect(profile_type, static_cast<void (KComboBox::*)(int)>(&KComboBox::currentIndexChanged), this, &EncodingProfilesDialog::slotLoadProfiles);
0061     connect(profile_list, &QListWidget::currentRowChanged, this, &EncodingProfilesDialog::slotShowParams);
0062     connect(button_delete, &QAbstractButton::clicked, this, &EncodingProfilesDialog::slotDeleteProfile);
0063     connect(button_add, &QAbstractButton::clicked, this, &EncodingProfilesDialog::slotAddProfile);
0064     connect(button_edit, &QAbstractButton::clicked, this, &EncodingProfilesDialog::slotEditProfile);
0065     profile_parameters->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5);
0066     slotLoadProfiles();
0067 }
0068 
0069 EncodingProfilesDialog::~EncodingProfilesDialog()
0070 {
0071     delete m_configGroup;
0072     delete m_configFile;
0073 }
0074 
0075 void EncodingProfilesDialog::slotLoadProfiles()
0076 {
0077     profile_list->blockSignals(true);
0078     profile_list->clear();
0079 
0080     delete m_configGroup;
0081     m_configGroup =
0082         new KConfigGroup(m_configFile, EncodingProfilesManager::configGroupName(EncodingProfilesManager::ProfileType(profile_type->currentIndex())));
0083     QMap<QString, QString> values = m_configGroup->entryMap();
0084     QMapIterator<QString, QString> i(values);
0085     while (i.hasNext()) {
0086         i.next();
0087         auto *item = new QListWidgetItem(i.key(), profile_list);
0088         item->setData(Qt::UserRole, i.value());
0089         // cout << i.key() << ": " << i.value() << endl;
0090     }
0091     profile_list->blockSignals(false);
0092     profile_list->setCurrentRow(0);
0093     const bool multiProfile(profile_list->count() > 0);
0094     button_delete->setEnabled(multiProfile);
0095     button_edit->setEnabled(multiProfile);
0096 }
0097 
0098 void EncodingProfilesDialog::slotShowParams()
0099 {
0100     profile_parameters->clear();
0101     QListWidgetItem *item = profile_list->currentItem();
0102     if (!item) {
0103         return;
0104     }
0105     profile_parameters->setPlainText(item->data(Qt::UserRole).toString().section(QLatin1Char(';'), 0, 0));
0106 }
0107 
0108 void EncodingProfilesDialog::slotDeleteProfile()
0109 {
0110     QListWidgetItem *item = profile_list->currentItem();
0111     if (!item) {
0112         return;
0113     }
0114     QString profile = item->text();
0115     m_configGroup->deleteEntry(profile);
0116     slotLoadProfiles();
0117 }
0118 
0119 void EncodingProfilesDialog::slotAddProfile()
0120 {
0121     QPointer<QDialog> d = new QDialog(this);
0122     auto *l = new QVBoxLayout;
0123     l->addWidget(new QLabel(i18n("Profile name:")));
0124     auto *pname = new QLineEdit;
0125     l->addWidget(pname);
0126     l->addWidget(new QLabel(i18n("Parameters:")));
0127     auto *pparams = new QPlainTextEdit;
0128     l->addWidget(pparams);
0129     l->addWidget(new QLabel(i18n("File extension:")));
0130     auto *pext = new QLineEdit;
0131     l->addWidget(pext);
0132     QDialogButtonBox *box = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel);
0133     connect(box, &QDialogButtonBox::accepted, d.data(), &QDialog::accept);
0134     connect(box, &QDialogButtonBox::rejected, d.data(), &QDialog::reject);
0135     l->addWidget(box);
0136     d->setLayout(l);
0137 
0138     QListWidgetItem *item = profile_list->currentItem();
0139     if (item) {
0140         QString profilestr = item->data(Qt::UserRole).toString();
0141         pparams->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0));
0142         pext->setText(profilestr.section(QLatin1Char(';'), 1, 1));
0143     }
0144     if (d->exec() == QDialog::Accepted) {
0145         m_configGroup->writeEntry(pname->text(), QString(pparams->toPlainText() + QLatin1Char(';') + pext->text()));
0146         slotLoadProfiles();
0147     }
0148     delete d;
0149 }
0150 
0151 void EncodingProfilesDialog::slotEditProfile()
0152 {
0153     QPointer<QDialog> d = new QDialog(this);
0154     auto *l = new QVBoxLayout;
0155     l->addWidget(new QLabel(i18n("Profile name:")));
0156     auto *pname = new QLineEdit;
0157     l->addWidget(pname);
0158     l->addWidget(new QLabel(i18n("Parameters:")));
0159     auto *pparams = new QPlainTextEdit;
0160     l->addWidget(pparams);
0161     l->addWidget(new QLabel(i18n("File extension:")));
0162     auto *pext = new QLineEdit;
0163     l->addWidget(pext);
0164     QDialogButtonBox *box = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel);
0165     connect(box, &QDialogButtonBox::accepted, d.data(), &QDialog::accept);
0166     connect(box, &QDialogButtonBox::rejected, d.data(), &QDialog::reject);
0167     l->addWidget(box);
0168     d->setLayout(l);
0169 
0170     QListWidgetItem *item = profile_list->currentItem();
0171     if (item) {
0172         pname->setText(item->text());
0173         QString profilestr = item->data(Qt::UserRole).toString();
0174         pparams->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0));
0175         pext->setText(profilestr.section(QLatin1Char(';'), 1, 1));
0176         pparams->setFocus();
0177     }
0178     if (d->exec() == QDialog::Accepted) {
0179         m_configGroup->writeEntry(pname->text(), QString(pparams->toPlainText().simplified() + QLatin1Char(';') + pext->text()));
0180         slotLoadProfiles();
0181     }
0182     delete d;
0183 }
0184 
0185 EncodingProfilesChooser::EncodingProfilesChooser(QWidget *parent, EncodingProfilesManager::ProfileType type, bool showAutoItem, const QString &configName,
0186                                                  bool native)
0187     : QWidget(parent)
0188     , m_type(type)
0189     , m_showAutoItem(showAutoItem)
0190 {
0191     QVBoxLayout *verticalLayout = new QVBoxLayout(this);
0192     verticalLayout->setContentsMargins(0, 0, 0, 0);
0193     m_profilesCombo = new QComboBox(this);
0194     if (!configName.isEmpty()) {
0195         m_profilesCombo->setObjectName(QStringLiteral("kcfg_%1").arg(configName));
0196     }
0197 
0198     QToolButton *buttonConfigure = new QToolButton();
0199     buttonConfigure->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
0200     buttonConfigure->setText(i18n("Configure profiles"));
0201     buttonConfigure->setToolTip(i18n("Manage Encoding Profiles"));
0202     QToolButton *buttonInfo = new QToolButton();
0203     buttonInfo->setCheckable(true);
0204     buttonInfo->setIcon(QIcon::fromTheme(QStringLiteral("help-about")));
0205     buttonConfigure->setToolTip(i18n("Show Profile Parameters"));
0206     buttonConfigure->setWhatsThis(
0207         xi18nc("@info:whatsthis", "Opens a window with encoding profiles and their parameters. From here you can create, change, delete and download profiles "
0208                                   "used for timeline preview, proxy generation, and video, screen and decklink capture."));
0209 
0210     m_info = new QPlainTextEdit();
0211     m_info->setReadOnly(true);
0212     m_info->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 4);
0213     QHBoxLayout *hor = new QHBoxLayout;
0214     hor->addWidget(m_profilesCombo);
0215     hor->addWidget(buttonConfigure);
0216     hor->addWidget(buttonInfo);
0217 
0218     // Message widget
0219     m_messageWidget = new KMessageWidget(this);
0220     m_messageWidget->setWordWrap(true);
0221     m_messageWidget->setCloseButtonVisible(true);
0222 
0223     verticalLayout->addLayout(hor);
0224     verticalLayout->addWidget(m_info);
0225     verticalLayout->addWidget(m_messageWidget);
0226     m_info->setVisible(false);
0227     m_messageWidget->hide();
0228     connect(buttonConfigure, &QAbstractButton::clicked, this, &EncodingProfilesChooser::slotManageEncodingProfile);
0229     connect(buttonInfo, &QAbstractButton::clicked, m_info, &QWidget::setVisible);
0230     connect(m_profilesCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &EncodingProfilesChooser::slotUpdateProfile);
0231     if (native) {
0232         loadEncodingProfiles();
0233         if (!configName.isEmpty()) {
0234             KConfigGroup resourceConfig(KSharedConfig::openConfig(), "project");
0235             int ix = resourceConfig.readEntry(configName).toInt();
0236             m_profilesCombo->setCurrentIndex(ix);
0237             slotUpdateProfile(ix);
0238         }
0239     }
0240     setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
0241 }
0242 
0243 void EncodingProfilesChooser::slotManageEncodingProfile()
0244 {
0245     QPointer<EncodingProfilesDialog> dia = new EncodingProfilesDialog(m_type);
0246     dia->exec();
0247     delete dia;
0248     loadEncodingProfiles();
0249     filterPreviewProfiles(pCore->getCurrentProfilePath());
0250 }
0251 
0252 void EncodingProfilesChooser::loadEncodingProfiles()
0253 {
0254     QSignalBlocker bk(m_profilesCombo);
0255     QString currentItem = m_profilesCombo->currentText();
0256     m_profilesCombo->clear();
0257     if (m_showAutoItem) {
0258         if (m_type == EncodingProfilesManager::TimelinePreview && !KdenliveSettings::supportedHWCodecs().isEmpty()) {
0259             m_profilesCombo->addItem(QIcon::fromTheme(QStringLiteral("speedometer")), i18n("Automatic (%1)", Wizard::getHWCodecFriendlyName()));
0260         } else {
0261             m_profilesCombo->addItem(i18n("Automatic"));
0262         }
0263     }
0264 
0265     KConfig conf(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
0266     KConfigGroup group(&conf, EncodingProfilesManager::configGroupName(m_type));
0267     QMap<QString, QString> values = group.entryMap();
0268     QMapIterator<QString, QString> i(values);
0269     while (i.hasNext()) {
0270         i.next();
0271         if (!i.key().isEmpty()) {
0272             m_profilesCombo->addItem(i.key(), i.value());
0273         }
0274     }
0275     if (!currentItem.isEmpty()) {
0276         int ix = m_profilesCombo->findText(currentItem);
0277         m_profilesCombo->setCurrentIndex(ix);
0278         slotUpdateProfile(ix);
0279     }
0280 }
0281 
0282 QString EncodingProfilesChooser::currentExtension()
0283 {
0284     QString profilestr = m_profilesCombo->currentData().toString();
0285     if (profilestr.isEmpty()) {
0286         return {};
0287     }
0288     return profilestr.section(QLatin1Char(';'), 1, 1);
0289 }
0290 
0291 QString EncodingProfilesChooser::currentParams()
0292 {
0293     QString profilestr = m_profilesCombo->currentData().toString();
0294     if (profilestr.isEmpty()) {
0295         return {};
0296     }
0297     return profilestr.section(QLatin1Char(';'), 0, 0);
0298 }
0299 
0300 void EncodingProfilesChooser::slotUpdateProfile(int ix)
0301 {
0302     m_info->clear();
0303     QString profilestr = m_profilesCombo->itemData(ix).toString();
0304     if (!profilestr.isEmpty()) {
0305         m_info->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0));
0306     }
0307 }
0308 
0309 void EncodingProfilesChooser::hideMessage()
0310 {
0311     m_messageWidget->hide();
0312 }
0313 
0314 void EncodingProfilesChooser::filterPreviewProfiles(const QString & /*profile*/) {}
0315 
0316 EncodingTimelinePreviewProfilesChooser::EncodingTimelinePreviewProfilesChooser(QWidget *parent, bool showAutoItem, const QString &defaultValue,
0317                                                                                bool selectFromConfig)
0318     : EncodingProfilesChooser(parent, EncodingProfilesManager::TimelinePreview, showAutoItem, QString(), false)
0319 {
0320     loadEncodingProfiles();
0321     if (selectFromConfig) {
0322         KConfigGroup resourceConfig(KSharedConfig::openConfig(), "project");
0323         int ix = resourceConfig.readEntry(defaultValue).toInt();
0324         m_profilesCombo->setCurrentIndex(ix);
0325         slotUpdateProfile(ix);
0326     } else if (!defaultValue.isEmpty()) {
0327         int ix = m_profilesCombo->findData(defaultValue);
0328         if (ix == -1) {
0329             m_profilesCombo->addItem(i18n("Current Settings"), defaultValue);
0330             ix = m_profilesCombo->findData(defaultValue);
0331         }
0332         if (ix > -1) {
0333             m_profilesCombo->setCurrentIndex(ix);
0334             slotUpdateProfile(ix);
0335         }
0336     }
0337     connect(m_profilesCombo, &KComboBox::currentIndexChanged, m_messageWidget, &KMessageWidget::hide);
0338     connect(m_profilesCombo, &KComboBox::currentIndexChanged, this, &EncodingTimelinePreviewProfilesChooser::currentIndexChanged);
0339 }
0340 
0341 void EncodingTimelinePreviewProfilesChooser::loadEncodingProfiles()
0342 {
0343     QSignalBlocker bk(m_profilesCombo);
0344     QString currentItem = m_profilesCombo->currentText();
0345     m_profilesCombo->clear();
0346     if (m_showAutoItem) {
0347         if (!KdenliveSettings::supportedHWCodecs().isEmpty()) {
0348             m_profilesCombo->addItem(QIcon::fromTheme(QStringLiteral("speedometer")), i18n("Automatic (%1)", Wizard::getHWCodecFriendlyName()));
0349         } else {
0350             m_profilesCombo->addItem(i18n("Automatic"));
0351         }
0352     }
0353 
0354     KConfig conf(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
0355     KConfigGroup group(&conf, EncodingProfilesManager::configGroupName(m_type));
0356     QMap<QString, QString> values = group.entryMap();
0357     QMapIterator<QString, QString> i(values);
0358     const QStringList allHWCodecs = Wizard::codecs();
0359     while (i.hasNext()) {
0360         i.next();
0361         if (!i.key().isEmpty()) {
0362             // We filter out incompatible profiles, find out vcodec if mentioned
0363             QString itemCodec;
0364             QStringList values = i.value().split(QLatin1Char(' '));
0365             for (auto &v : values) {
0366                 if (v.startsWith(QLatin1String("vcodec=")) || v.startsWith(QLatin1String("codec:v=")) || v.startsWith(QLatin1String("c:v="))) {
0367                     itemCodec = v.section(QLatin1Char('='), 1);
0368                     break;
0369                 }
0370             }
0371             if (!itemCodec.isEmpty()) {
0372                 if (allHWCodecs.contains(itemCodec)) {
0373                     // This is an HW codec, check if supported
0374                     if (KdenliveSettings::supportedHWCodecs().contains(itemCodec)) {
0375                         m_profilesCombo->addItem(QIcon::fromTheme(QStringLiteral("speedometer")), i.key(), i.value());
0376                     }
0377                     continue;
0378                 }
0379                 m_profilesCombo->addItem(i.key(), i.value());
0380             }
0381         }
0382     }
0383     if (!currentItem.isEmpty()) {
0384         int ix = m_profilesCombo->findText(currentItem);
0385         m_profilesCombo->setCurrentIndex(ix);
0386         slotUpdateProfile(ix);
0387     }
0388 }
0389 
0390 void EncodingTimelinePreviewProfilesChooser::filterPreviewProfiles(const QString &profile)
0391 {
0392     QStandardItemModel *model = qobject_cast<QStandardItemModel *>(m_profilesCombo->model());
0393     Q_ASSERT(model != nullptr);
0394     int max = m_profilesCombo->count();
0395     int current = m_profilesCombo->currentIndex();
0396     double projectFps = ProfileRepository::get()->getProfile(profile)->fps();
0397     for (int i = 0; i < max; i++) {
0398         QString itemData = m_profilesCombo->itemData(i).toString();
0399         double fps = 0.;
0400         if (itemData.startsWith(QStringLiteral("r="))) {
0401             QString fpsString = itemData.section(QLatin1Char('='), 1).section(QLatin1Char(' '), 0, 0);
0402             fps = fpsString.toDouble();
0403         } else if (itemData.contains(QStringLiteral(" r="))) {
0404             QString fpsString = itemData.section(QLatin1String(" r="), 1).section(QLatin1Char(' '), 0, 0);
0405             // This profile has a hardcoded framerate, check if same as project
0406             fps = fpsString.toDouble();
0407         }
0408         QStandardItem *item = model->item(i);
0409         if (fps > 0. && qAbs(fps - projectFps) > 0.01) {
0410             // Fps does not match, disable
0411             item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
0412             continue;
0413         }
0414         item->setFlags(item->flags() | Qt::ItemIsEnabled);
0415     }
0416     QStandardItem *item = model->item(current);
0417     if (!item || !(item->flags() & Qt::ItemIsEnabled)) {
0418         // Currently selected profile is not usable, switch back to automatic
0419         for (int i = 0; i < max; i++) {
0420             if (m_profilesCombo->itemData(i).isNull()) {
0421                 m_profilesCombo->setCurrentIndex(i);
0422                 m_messageWidget->setText(i18n("Selected Timeline preview profile is not compatible with the project framerate,\nreverting to Automatic."));
0423                 m_messageWidget->animatedShow();
0424                 break;
0425             }
0426         }
0427     }
0428 }