Warning, file /multimedia/kdenlive/src/dialogs/exportguidesdialog.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "exportguidesdialog.h"
0008 
0009 #include "bin/model/markerlistmodel.hpp"
0010 #include "core.h"
0011 #include "doc/kdenlivedoc.h"
0012 #include "kdenlivesettings.h"
0013 #include "project/projectmanager.h"
0014 
0015 #include "kdenlive_debug.h"
0016 #include <KMessageWidget>
0017 #include <QAction>
0018 #include <QClipboard>
0019 #include <QDateTimeEdit>
0020 #include <QFileDialog>
0021 #include <QFontDatabase>
0022 #include <QPushButton>
0023 #include <QTime>
0024 
0025 #include "klocalizedstring.h"
0026 
0027 #define YT_FORMAT "{{timecode}} {{comment}}"
0028 
0029 ExportGuidesDialog::ExportGuidesDialog(const MarkerListModel *model, const GenTime duration, QWidget *parent)
0030     : QDialog(parent)
0031     , m_markerListModel(model)
0032     , m_projectDuration(duration)
0033 {
0034     //    setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
0035     setupUi(this);
0036     setWindowTitle(i18n("Export guides as chapters description"));
0037 
0038     // We should setup TimecodeDisplay since it requires a proper Timecode
0039     offsetTimeSpinbox->setTimecode(Timecode(Timecode::HH_MM_SS_FF, pCore->getCurrentFps()));
0040 
0041     const QString defaultFormat(YT_FORMAT);
0042     formatEdit->setText(KdenliveSettings::exportGuidesFormat().isEmpty() ? defaultFormat : KdenliveSettings::exportGuidesFormat());
0043     categoryChooser->setMarkerModel(m_markerListModel);
0044     messageWidget->setText(i18n("If you are using the exported text for YouTube, you might want to check:\n"
0045                                 "1. The start time of 00:00 must have a chapter.\n"
0046                                 "2. There must be at least three timestamps in ascending order.\n"
0047                                 "3. The minimum length for video chapters is 10 seconds."));
0048     messageWidget->setVisible(false);
0049 
0050     updateContentByModel();
0051 
0052     QPushButton *btn = buttonBox->addButton(i18n("Copy to Clipboard"), QDialogButtonBox::ActionRole);
0053     btn->setIcon(QIcon::fromTheme("edit-copy"));
0054     QPushButton *btn2 = buttonBox->addButton(i18n("Save"), QDialogButtonBox::ActionRole);
0055     btn2->setIcon(QIcon::fromTheme("document-save"));
0056 
0057     connect(categoryChooser, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this]() { updateContentByModel(); });
0058 
0059     connect(offsetTimeComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int newIndex) {
0060         offsetTimeSpinbox->setEnabled(newIndex != 0);
0061         updateContentByModel();
0062     });
0063 
0064     connect(offsetTimeSpinbox, &TimecodeDisplay::timeCodeUpdated, this, [this]() { updateContentByModel(); });
0065 
0066     connect(formatEdit, &QLineEdit::textEdited, this, [this]() { updateContentByModel(); });
0067 
0068     connect(btn, &QAbstractButton::clicked, this, [this]() {
0069         QClipboard *clipboard = QGuiApplication::clipboard();
0070         clipboard->setText(this->generatedContent->toPlainText());
0071     });
0072 
0073     connect(btn2, &QAbstractButton::clicked, this, [this]() {
0074         QString filter = format_text->isChecked() ? QString("%1 (*.txt)").arg(i18n("Text File")) : QString("%1 (*.json)").arg(i18n("JSON File"));
0075         const QString startFolder = pCore->projectManager()->current()->projectDataFolder();
0076         QString filename = QFileDialog::getSaveFileName(this, i18nc("@title:window", "Export Guides Data"), startFolder, filter);
0077         QFile file(filename);
0078         if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
0079             messageWidget->setText(i18n("Cannot write to file %1", QUrl::fromLocalFile(filename).fileName()));
0080             messageWidget->setMessageType(KMessageWidget::Warning);
0081             messageWidget->animatedShow();
0082             return;
0083         }
0084         file.write(generatedContent->toPlainText().toUtf8());
0085         file.close();
0086         messageWidget->setText(i18n("Guides saved to %1", QUrl::fromLocalFile(filename).fileName()));
0087         messageWidget->setMessageType(KMessageWidget::Positive);
0088         messageWidget->animatedShow();
0089     });
0090 
0091     connect(format_json, &QRadioButton::toggled, this, [this](bool jsonFormat) {
0092         textOptions->setEnabled(!jsonFormat);
0093         updateContentByModel();
0094     });
0095 
0096     connect(buttonReset, &QAbstractButton::clicked, [this, defaultFormat]() {
0097         formatEdit->setText(defaultFormat);
0098         updateContentByModel();
0099     });
0100 
0101     // fill info button menu
0102     QMap<QString, QString> infoMenu;
0103     infoMenu.insert(QStringLiteral("{{category}}"), i18n("Category name"));
0104     infoMenu.insert(QStringLiteral("{{index}}"), i18n("Guide number"));
0105     infoMenu.insert(QStringLiteral("{{realtimecode}}"), i18n("Guide position in HH:MM:SS:FF"));
0106     infoMenu.insert(QStringLiteral("{{timecode}}"), i18n("Guide position in (HH:)MM.SS"));
0107     infoMenu.insert(QStringLiteral("{{nexttimecode}}"), i18n("Next guide position in (HH:)MM.SS"));
0108     infoMenu.insert(QStringLiteral("{{frame}}"), i18n("Guide position in frames"));
0109     infoMenu.insert(QStringLiteral("{{comment}}"), i18n("Guide comment"));
0110     QMapIterator<QString, QString> i(infoMenu);
0111     QAction *a;
0112     while (i.hasNext()) {
0113         i.next();
0114         a = new QAction(this);
0115         a->setText(QString("%1 - %2").arg(i.value(), i.key()));
0116         a->setData(i.key());
0117         infoButton->addAction(a);
0118     }
0119     connect(infoButton, &QToolButton::triggered, [this](QAction *a) {
0120         formatEdit->insert(a->data().toString());
0121         updateContentByModel();
0122     });
0123     adjustSize();
0124 }
0125 
0126 ExportGuidesDialog::~ExportGuidesDialog()
0127 {
0128     KdenliveSettings::setExportGuidesFormat(formatEdit->text());
0129 }
0130 
0131 GenTime ExportGuidesDialog::offsetTime() const
0132 {
0133     switch (offsetTimeComboBox->currentIndex()) {
0134     case 1: // Add
0135         return offsetTimeSpinbox->gentime();
0136     case 2: // Subtract
0137         return -offsetTimeSpinbox->gentime();
0138     case 0: // Disabled
0139     default:
0140         return GenTime(0);
0141     }
0142 }
0143 
0144 QString chapterTimeStringFromMs(double timeMs)
0145 {
0146     int totalSec = qAbs(timeMs / 1000);
0147     bool negative = timeMs < 0 && totalSec > 0; // since our minimal unit is second.
0148     int hour = totalSec / 3600;
0149     int min = totalSec % 3600 / 60;
0150     int sec = totalSec % 3600 % 60;
0151     if (hour == 0) {
0152         return QString::asprintf("%s%d:%02d", negative ? "-" : "", min, sec);
0153     } else {
0154         return QString::asprintf("%s%d:%02d:%02d", negative ? "-" : "", hour, min, sec);
0155     }
0156 }
0157 
0158 void ExportGuidesDialog::updateContentByModel() const
0159 {
0160     const int markerIndex = categoryChooser->currentCategory();
0161     if (format_json->isChecked()) {
0162         messageWidget->setVisible(false);
0163         generatedContent->setPlainText(m_markerListModel->toJson({markerIndex}));
0164         return;
0165     }
0166     const QString format(formatEdit->text());
0167     const GenTime offset(offsetTime());
0168 
0169     QStringList chapterTexts;
0170     QList<CommentedTime> markers(m_markerListModel->getAllMarkers(markerIndex));
0171     bool needCheck = format == YT_FORMAT;
0172     bool needShowInfoMsg = false;
0173 
0174     const int markerCount = markers.length();
0175     const double currentFps = pCore->getCurrentFps();
0176     for (int i = 0; i < markers.length(); i++) {
0177         const CommentedTime &currentMarker = markers.at(i);
0178         const GenTime &nextGenTime = markerCount - 1 == i ? m_projectDuration : markers.at(i + 1).time();
0179 
0180         QString line(format);
0181         GenTime currentTime = currentMarker.time() + offset;
0182         GenTime nextTime = nextGenTime + offset;
0183 
0184         if (i == 0 && needCheck && !qFuzzyCompare(currentTime.seconds(), 0)) {
0185             needShowInfoMsg = true;
0186         }
0187 
0188         if (needCheck && std::abs(nextTime.seconds() - currentTime.seconds()) < 10) {
0189             needShowInfoMsg = true;
0190         }
0191 
0192         line.replace("{{index}}", QString::number(i + 1));
0193         line.replace("{{realtimecode}}", pCore->timecode().getDisplayTimecode(currentTime, false));
0194         line.replace("{{timecode}}", chapterTimeStringFromMs(currentTime.ms()));
0195         line.replace("{{nexttimecode}}", chapterTimeStringFromMs(nextTime.ms()));
0196         line.replace("{{frame}}", QString::number(currentTime.frames(currentFps)));
0197         line.replace("{{comment}}", currentMarker.comment());
0198         line.replace("{{category}}", pCore->markerTypes[currentMarker.markerType()].displayName);
0199         chapterTexts.append(line);
0200     }
0201 
0202     generatedContent->setPlainText(chapterTexts.join('\n'));
0203 
0204     if (needCheck && markerCount < 3) {
0205         needShowInfoMsg = true;
0206     }
0207 
0208     messageWidget->setVisible(needShowInfoMsg);
0209 }
0210 
0211 #undef YT_FORMAT