File indexing completed on 2024-12-01 04:28:36
0001 /* 0002 SPDX-FileCopyrightText: 2021 Jean-Baptiste Mardelle <jb@kdenlive.org> 0003 0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "cuttask.h" 0008 #include "bin/bin.h" 0009 #include "bin/projectclip.h" 0010 #include "bin/projectfolder.h" 0011 #include "bin/projectitemmodel.h" 0012 #include "core.h" 0013 #include "kdenlive_debug.h" 0014 #include "kdenlivesettings.h" 0015 #include "macros.hpp" 0016 #include "mainwindow.h" 0017 #include "profiles/profilemodel.hpp" 0018 #include "ui_cutjobdialog_ui.h" 0019 #include "utils/qstringutils.h" 0020 #include "xml/xml.hpp" 0021 0022 #include <KIO/RenameDialog> 0023 #include <KLineEdit> 0024 #include <KLocalizedString> 0025 #include <KMessageBox> 0026 #include <QComboBox> 0027 #include <QObject> 0028 #include <QProcess> 0029 #include <QThread> 0030 0031 CutTask::CutTask(const ObjectId &owner, const QString &destination, const QStringList &encodingParams, int in, int out, bool addToProject, QObject *object) 0032 : AbstractTask(owner, AbstractTask::CUTJOB, object) 0033 , m_inPoint(GenTime(in, pCore->getCurrentFps())) 0034 , m_outPoint(GenTime(out, pCore->getCurrentFps())) 0035 , m_destination(destination) 0036 , m_encodingParams(encodingParams) 0037 , m_jobDuration(0) 0038 , m_addToProject(addToProject) 0039 { 0040 m_description = i18n("Extracting zone"); 0041 } 0042 0043 void CutTask::start(const ObjectId &owner, int in, int out, QObject *object, bool force) 0044 { 0045 auto binClip = pCore->projectItemModel()->getClipByBinID(QString::number(owner.itemId)); 0046 ClipType::ProducerType type = binClip->clipType(); 0047 if (type != ClipType::AV && type != ClipType::Audio && type != ClipType::Video) { 0048 // m_errorMessage.prepend(i18n("Cannot extract zone for this clip type.")); 0049 return; 0050 } 0051 const QString source = binClip->url(); 0052 QString videoCodec = binClip->codec(false); 0053 QString audioCodec = binClip->codec(true); 0054 // Check if the audio/video codecs are supported for encoding (required for the codec copy feature) 0055 QProcess checkProcess; 0056 QStringList params = {QStringLiteral("-codecs")}; 0057 checkProcess.start(KdenliveSettings::ffmpegpath(), params); 0058 checkProcess.waitForFinished(); // sets current thread to sleep and waits for pingProcess end 0059 QString output(checkProcess.readAllStandardOutput()); 0060 QString line; 0061 QTextStream stream(&output); 0062 bool videoOk = videoCodec.isEmpty(); 0063 bool audioOk = audioCodec.isEmpty(); 0064 while (stream.readLineInto(&line)) { 0065 if (!videoOk && line.contains(videoCodec)) { 0066 if (line.simplified().section(QLatin1Char(' '), 0, 0).contains(QLatin1Char('E'))) { 0067 videoOk = true; 0068 } 0069 } else if (!audioOk && line.contains(audioCodec)) { 0070 if (line.simplified().section(QLatin1Char(' '), 0, 0).contains(QLatin1Char('E'))) { 0071 audioOk = true; 0072 } 0073 } 0074 if (audioOk && videoOk) { 0075 break; 0076 } 0077 } 0078 QString warnMessage; 0079 if (!videoOk) { 0080 warnMessage = i18n("Cannot copy video codec %1, will re-encode.", videoCodec); 0081 } 0082 if (!audioOk) { 0083 if (!videoOk) { 0084 warnMessage.append(QLatin1Char('\n')); 0085 } 0086 warnMessage.append(i18n("Cannot copy audio codec %1, will re-encode.", audioCodec)); 0087 } 0088 0089 QFileInfo finfo(source); 0090 QDir dir = finfo.absoluteDir(); 0091 QString inString = QString::number(int(GenTime(in, pCore->getCurrentFps()).seconds())); 0092 QString outString = QString::number(int(GenTime(out, pCore->getCurrentFps()).seconds())); 0093 QString fileName = QStringUtils::appendToFilename(finfo.fileName(), QString("-%1-%2").arg(inString, outString)); 0094 QString path = dir.absoluteFilePath(fileName); 0095 0096 QPointer<QDialog> d = new QDialog(QApplication::activeWindow()); 0097 Ui::CutJobDialog_UI ui; 0098 ui.setupUi(d); 0099 ui.extra_params->setVisible(false); 0100 ui.message->setText(warnMessage); 0101 ui.message->setVisible(!warnMessage.isEmpty()); 0102 if (videoCodec.isEmpty()) { 0103 ui.video_codec->setText(i18n("none")); 0104 ui.vcodec->setEnabled(false); 0105 } else { 0106 ui.video_codec->setText(videoCodec); 0107 ui.vcodec->addItem(i18n("Copy stream"), QStringLiteral("copy")); 0108 ui.vcodec->addItem(i18n("X264 encoding"), QStringLiteral("libx264")); 0109 ui.vcodec->addItem(i18n("Disable stream")); 0110 } 0111 if (audioCodec.isEmpty()) { 0112 ui.audio_codec->setText(i18n("none")); 0113 ui.acodec->setEnabled(false); 0114 } else { 0115 ui.audio_codec->setText(audioCodec); 0116 ui.acodec->addItem(i18n("Copy stream"), QStringLiteral("copy")); 0117 ui.acodec->addItem(i18n("PCM encoding"), QStringLiteral("pcm_s24le")); 0118 ui.acodec->addItem(i18n("AAC encoding"), QStringLiteral("aac")); 0119 ui.acodec->addItem(i18n("Disable stream")); 0120 } 0121 ui.audio_codec->setText(audioCodec); 0122 ui.add_clip->setChecked(KdenliveSettings::add_new_clip()); 0123 ui.file_url->setMode(KFile::File); 0124 ui.extra_params->setMaximumHeight(QFontMetrics(QApplication::font()).lineSpacing() * 5); 0125 ui.file_url->setUrl(QUrl::fromLocalFile(path)); 0126 0127 QString transcoderExt = QLatin1Char('.') + finfo.suffix(); 0128 0129 std::function<void()> callBack = [&ui, transcoderExt]() { 0130 if (ui.acodec->currentData().isNull()) { 0131 // Video only 0132 ui.extra_params->setPlainText(QString("-an -c:v %1").arg(ui.vcodec->currentData().toString())); 0133 } else if (ui.vcodec->currentData().isNull()) { 0134 // Audio only 0135 ui.extra_params->setPlainText(QString("-vn -c:a %1").arg(ui.acodec->currentData().toString())); 0136 } else { 0137 ui.extra_params->setPlainText(QString("-c:a %1 -c:v %2").arg(ui.acodec->currentData().toString(), ui.vcodec->currentData().toString())); 0138 } 0139 QString path = ui.file_url->url().toLocalFile(); 0140 QString fileName = path.section(QLatin1Char('.'), 0, -2); 0141 if (ui.acodec->currentData() == QLatin1String("copy") && ui.vcodec->currentData() == QLatin1String("copy")) { 0142 fileName.append(transcoderExt); 0143 } else { 0144 fileName.append(QStringLiteral(".mov")); 0145 } 0146 ui.file_url->setUrl(QUrl::fromLocalFile(fileName)); 0147 }; 0148 0149 QObject::connect(ui.acodec, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), d.data(), [callBack]() { callBack(); }); 0150 0151 QObject::connect(ui.vcodec, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), d.data(), [callBack]() { callBack(); }); 0152 QFontMetrics fm = ui.file_url->lineEdit()->fontMetrics(); 0153 ui.file_url->setMinimumWidth(int(fm.boundingRect(ui.file_url->text().left(50)).width() * 1.4)); 0154 callBack(); 0155 QString mess = i18n("Extracting %1 out of %2", Timecode::getStringTimecode(out - in, pCore->getCurrentFps(), true), binClip->getStringDuration()); 0156 ui.info_label->setText(mess); 0157 if (!videoOk) { 0158 ui.vcodec->setCurrentIndex(1); 0159 } 0160 if (!audioOk) { 0161 ui.acodec->setCurrentIndex(1); 0162 } 0163 if (d->exec() != QDialog::Accepted) { 0164 delete d; 0165 return; 0166 } 0167 path = ui.file_url->url().toLocalFile(); 0168 QStringList encodingParams = ui.extra_params->toPlainText().split(QLatin1Char(' '), Qt::SkipEmptyParts); 0169 KdenliveSettings::setAdd_new_clip(ui.add_clip->isChecked()); 0170 delete d; 0171 0172 if (QFile::exists(path)) { 0173 KIO::RenameDialog renameDialog(qApp->activeWindow(), i18n("File already exists"), QUrl::fromLocalFile(path), QUrl::fromLocalFile(path), 0174 KIO::RenameDialog_Option::RenameDialog_Overwrite); 0175 if (renameDialog.exec() != QDialog::Rejected) { 0176 QUrl final = renameDialog.newDestUrl(); 0177 if (final.isValid()) { 0178 path = final.toLocalFile(); 0179 } 0180 } else { 0181 return; 0182 } 0183 } 0184 CutTask *task = new CutTask(owner, path, encodingParams, in, out, KdenliveSettings::add_new_clip(), object); 0185 // Otherwise, start a filter thread. 0186 task->m_isForce = force; 0187 pCore->taskManager.startTask(owner.itemId, task); 0188 } 0189 0190 void CutTask::run() 0191 { 0192 AbstractTaskDone whenFinished(m_owner.itemId, this); 0193 if (m_isCanceled || pCore->taskManager.isBlocked()) { 0194 return; 0195 } 0196 QMutexLocker lock(&m_runMutex); 0197 m_running = true; 0198 qDebug() << " + + + + + + + + STARTING STAB TASK"; 0199 0200 QString url; 0201 auto binClip = pCore->projectItemModel()->getClipByBinID(QString::number(m_owner.itemId)); 0202 if (binClip) { 0203 // Filter applied on a timeline or bin clip 0204 url = binClip->url(); 0205 QString folder = QStringLiteral("-1"); 0206 auto containingFolder = std::static_pointer_cast<ProjectFolder>(binClip->parent()); 0207 if (containingFolder) { 0208 folder = containingFolder->clipId(); 0209 } 0210 if (url.isEmpty()) { 0211 QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, Q_ARG(QString, i18n("No producer for this clip.")), 0212 Q_ARG(int, int(KMessageWidget::Warning))); 0213 m_errorMessage.append(i18n("No producer for this clip.")); 0214 return; 0215 } 0216 if (QFileInfo(m_destination).absoluteFilePath() == QFileInfo(url).absoluteFilePath()) { 0217 QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, Q_ARG(QString, i18n("You cannot overwrite original clip.")), 0218 Q_ARG(int, int(KMessageWidget::Warning))); 0219 m_errorMessage.append(i18n("You cannot overwrite original clip.")); 0220 return; 0221 } 0222 QStringList params = {QStringLiteral("-y"), 0223 QStringLiteral("-stats"), 0224 QStringLiteral("-v"), 0225 QStringLiteral("error"), 0226 QStringLiteral("-noaccurate_seek"), 0227 QStringLiteral("-ss"), 0228 QString::number(m_inPoint.seconds()), 0229 QStringLiteral("-i"), 0230 url, 0231 QStringLiteral("-t"), 0232 QString::number((m_outPoint - m_inPoint).seconds()), 0233 QStringLiteral("-avoid_negative_ts"), 0234 QStringLiteral("make_zero"), 0235 QStringLiteral("-sn"), 0236 QStringLiteral("-dn"), 0237 QStringLiteral("-map"), 0238 QStringLiteral("0")}; 0239 params << m_encodingParams << m_destination; 0240 m_jobProcess = std::make_unique<QProcess>(new QProcess); 0241 connect(m_jobProcess.get(), &QProcess::readyReadStandardError, this, &CutTask::processLogInfo); 0242 connect(this, &CutTask::jobCanceled, m_jobProcess.get(), &QProcess::kill, Qt::DirectConnection); 0243 qDebug() << "=== STARTING CUT JOB: " << params; 0244 m_jobProcess->start(KdenliveSettings::ffmpegpath(), params, QIODevice::ReadOnly); 0245 m_jobProcess->waitForFinished(-1); 0246 bool result = m_jobProcess->exitStatus() == QProcess::NormalExit; 0247 // remove temporary playlist if it exists 0248 if (result && !m_isCanceled) { 0249 if (QFileInfo(m_destination).size() == 0) { 0250 QFile::remove(m_destination); 0251 // File was not created 0252 QMetaObject::invokeMethod(pCore.get(), "displayBinLogMessage", Qt::QueuedConnection, Q_ARG(QString, i18n("Failed to create file.")), 0253 Q_ARG(int, int(KMessageWidget::Warning)), Q_ARG(QString, m_logDetails)); 0254 } else { 0255 // all ok, add clip 0256 if (m_addToProject) { 0257 QMetaObject::invokeMethod(pCore->window(), "addProjectClip", Qt::QueuedConnection, Q_ARG(QString, m_destination), Q_ARG(QString, folder)); 0258 } 0259 } 0260 } else { 0261 // transcode task crashed 0262 QFile::remove(m_destination); 0263 QMetaObject::invokeMethod(pCore.get(), "displayBinLogMessage", Qt::QueuedConnection, Q_ARG(QString, i18n("Cut job failed.")), 0264 Q_ARG(int, int(KMessageWidget::Warning)), Q_ARG(QString, m_logDetails)); 0265 } 0266 } 0267 } 0268 0269 void CutTask::processLogInfo() 0270 { 0271 const QString buffer = QString::fromUtf8(m_jobProcess->readAllStandardError()); 0272 m_logDetails.append(buffer); 0273 int progress = 0; 0274 // Parse FFmpeg output 0275 if (m_jobDuration == 0) { 0276 if (buffer.contains(QLatin1String("Duration:"))) { 0277 QString data = buffer.section(QStringLiteral("Duration:"), 1, 1).section(QLatin1Char(','), 0, 0).simplified(); 0278 if (!data.isEmpty()) { 0279 QStringList numbers = data.split(QLatin1Char(':')); 0280 if (numbers.size() < 3) { 0281 return; 0282 } 0283 m_jobDuration = numbers.at(0).toInt() * 3600 + numbers.at(1).toInt() * 60 + numbers.at(2).toInt(); 0284 } 0285 } 0286 } else if (buffer.contains(QLatin1String("time="))) { 0287 QString time = buffer.section(QStringLiteral("time="), 1, 1).simplified().section(QLatin1Char(' '), 0, 0); 0288 if (!time.isEmpty()) { 0289 QStringList numbers = time.split(QLatin1Char(':')); 0290 if (numbers.size() < 3) { 0291 progress = time.toInt(); 0292 if (progress == 0) { 0293 return; 0294 } 0295 } else { 0296 progress = numbers.at(0).toInt() * 3600 + numbers.at(1).toInt() * 60 + qRound(numbers.at(2).toDouble()); 0297 } 0298 } 0299 m_progress = 100 * progress / m_jobDuration; 0300 QMetaObject::invokeMethod(m_object, "updateJobProgress"); 0301 } 0302 }