File indexing completed on 2024-04-14 04:46:17
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 "clipcreationdialog.h" 0009 #include "bin/bin.h" 0010 #include "bin/bincommands.h" 0011 #include "bin/clipcreator.hpp" 0012 #include "bin/projectclip.h" 0013 #include "bin/projectitemmodel.h" 0014 #include "core.h" 0015 #include "doc/docundostack.hpp" 0016 #include "doc/kdenlivedoc.h" 0017 #include "glaxnimatelauncher.h" 0018 #include "kdenlive_debug.h" 0019 #include "kdenlivesettings.h" 0020 #include "project/dialogs/slideshowclip.h" 0021 #include "titler/titlewidget.h" 0022 #include "titletemplatedialog.h" 0023 #include "ui_colorclip_ui.h" 0024 #include "ui_qtextclip_ui.h" 0025 #include "utils/devices.hpp" 0026 #include "utils/qcolorutils.h" 0027 #include "widgets/timecodedisplay.h" 0028 #include "xml/xml.hpp" 0029 0030 #include <KDirOperator> 0031 #include <KFileWidget> 0032 #include <KIO/RenameDialog> 0033 #include <KLocalizedString> 0034 #include <KMessageBox> 0035 #include <KRecentDirs> 0036 #include <KWindowConfig> 0037 0038 #include <QDialog> 0039 #include <QDir> 0040 #include <QMimeDatabase> 0041 #include <QPointer> 0042 #include <QProcess> 0043 #include <QPushButton> 0044 #include <QStandardPaths> 0045 #include <QUndoCommand> 0046 #include <QWindow> 0047 0048 #include <unordered_map> 0049 #include <utility> 0050 0051 // static 0052 QStringList ClipCreationDialog::getExtensions() 0053 { 0054 // Build list of MIME types 0055 QStringList mimeTypes = QStringList() << QStringLiteral("application/x-kdenlivetitle") << QStringLiteral("video/mlt-playlist") 0056 << QStringLiteral("text/plain") << QStringLiteral("application/x-kdenlive"); 0057 0058 // Video MIMEs 0059 mimeTypes << QStringLiteral("video/x-flv") << QStringLiteral("application/vnd.rn-realmedia") << QStringLiteral("video/x-dv") << QStringLiteral("video/dv") 0060 << QStringLiteral("video/x-msvideo") << QStringLiteral("video/x-matroska") << QStringLiteral("video/mpeg") << QStringLiteral("video/ogg") 0061 << QStringLiteral("video/x-ms-wmv") << QStringLiteral("video/mp4") << QStringLiteral("video/quicktime") << QStringLiteral("video/webm") 0062 << QStringLiteral("video/3gpp") << QStringLiteral("video/mp2t"); 0063 0064 // Audio MIMEs 0065 mimeTypes << QStringLiteral("audio/AMR") << QStringLiteral("audio/x-flac") << QStringLiteral("audio/x-matroska") << QStringLiteral("audio/mp4") 0066 << QStringLiteral("audio/mpeg") << QStringLiteral("audio/x-mp3") << QStringLiteral("audio/ogg") << QStringLiteral("audio/x-wav") 0067 << QStringLiteral("audio/x-aiff") << QStringLiteral("audio/aiff") << QStringLiteral("application/ogg") << QStringLiteral("application/mxf") 0068 << QStringLiteral("application/x-shockwave-flash") << QStringLiteral("audio/ac3") << QStringLiteral("audio/aac"); 0069 0070 // Image MIMEs 0071 mimeTypes << QStringLiteral("image/gif") << QStringLiteral("image/jpeg") << QStringLiteral("image/png") << QStringLiteral("image/x-tga") 0072 << QStringLiteral("image/x-bmp") << QStringLiteral("image/svg+xml") << QStringLiteral("image/tiff") << QStringLiteral("image/x-xcf") 0073 << QStringLiteral("image/x-xcf-gimp") << QStringLiteral("image/x-vnd.adobe.photoshop") << QStringLiteral("image/x-pcx") 0074 << QStringLiteral("image/x-exr") << QStringLiteral("image/x-portable-pixmap") << QStringLiteral("application/x-krita") 0075 << QStringLiteral("image/webp") << QStringLiteral("image/jp2") << QStringLiteral("image/avif") << QStringLiteral("image/heif") 0076 << QStringLiteral("image/jxl"); 0077 0078 // Lottie animations 0079 bool allowLottie = KdenliveSettings::producerslist().contains(QLatin1String("glaxnimate")); 0080 if (allowLottie) { 0081 mimeTypes << QStringLiteral("application/json"); 0082 } 0083 0084 // Some newer mimetypes might not be registered on some older Operating Systems, so register manually 0085 QMap<QString, QString> manualMap; 0086 manualMap.insert(QStringLiteral("image/avif"), QStringLiteral("*.avif")); 0087 manualMap.insert(QStringLiteral("image/heif"), QStringLiteral("*.heif")); 0088 manualMap.insert(QStringLiteral("image/x-exr"), QStringLiteral("*.exr")); 0089 manualMap.insert(QStringLiteral("image/jp2"), QStringLiteral("*.jp2")); 0090 manualMap.insert(QStringLiteral("image/jxl"), QStringLiteral("*.jxl")); 0091 0092 QMimeDatabase db; 0093 QStringList allExtensions; 0094 for (const QString &mimeType : qAsConst(mimeTypes)) { 0095 QMimeType mime = db.mimeTypeForName(mimeType); 0096 if (mime.isValid()) { 0097 allExtensions.append(mime.globPatterns()); 0098 } else if (manualMap.contains(mimeType)) { 0099 allExtensions.append(manualMap.value(mimeType)); 0100 } 0101 } 0102 if (allowLottie) { 0103 allExtensions.append(QStringLiteral("*.rawr")); 0104 } 0105 // process custom user extensions 0106 const QStringList customs = KdenliveSettings::addedExtensions().split(' ', Qt::SkipEmptyParts); 0107 if (!customs.isEmpty()) { 0108 for (const QString &ext : customs) { 0109 if (ext.startsWith(QLatin1String("*."))) { 0110 allExtensions << ext; 0111 } else if (ext.startsWith(QLatin1String("."))) { 0112 allExtensions << QStringLiteral("*") + ext; 0113 } else if (!ext.contains(QLatin1Char('.'))) { 0114 allExtensions << QStringLiteral("*.") + ext; 0115 } else { 0116 // Unrecognized format 0117 qCDebug(KDENLIVE_LOG) << "Unrecognized custom format: " << ext; 0118 } 0119 } 0120 } 0121 allExtensions.removeDuplicates(); 0122 return allExtensions; 0123 } 0124 0125 QString ClipCreationDialog::getExtensionsFilter(const QStringList &additionalFilters) 0126 { 0127 const QString allExtensions = ClipCreationDialog::getExtensions().join(QLatin1Char(' ')); 0128 QString filter = i18n("All Supported Files") + " (" + allExtensions + ')'; 0129 if (!additionalFilters.isEmpty()) { 0130 filter += ";;"; 0131 filter.append(additionalFilters.join(";;")); 0132 } 0133 return filter; 0134 } 0135 0136 // static 0137 void ClipCreationDialog::createColorClip(KdenliveDoc *doc, const QString &parentFolder, std::shared_ptr<ProjectItemModel> model) 0138 { 0139 QScopedPointer<QDialog> dia(new QDialog(qApp->activeWindow())); 0140 Ui::ColorClip_UI dia_ui; 0141 dia_ui.setupUi(dia.data()); 0142 dia->setAccessibleName(i18n("Color Clip Dialog")); 0143 dia->setWindowTitle(i18nc("@title:window", "Color Clip")); 0144 dia_ui.clip_name->setText(i18n("Color Clip")); 0145 dia_ui.clip_name->selectAll(); 0146 dia_ui.clip_name->setAccessibleName(i18n("Clip Name")); 0147 0148 dia_ui.clip_duration->setValue(KdenliveSettings::color_duration()); 0149 dia_ui.clip_color->setColor(KdenliveSettings::colorclipcolor()); 0150 dia_ui.buttonBox->button(QDialogButtonBox::Ok)->setAccessibleName(i18n("Create Color Clip")); 0151 0152 if (dia->exec() == QDialog::Accepted) { 0153 QString color = dia_ui.clip_color->color().name(); 0154 KdenliveSettings::setColorclipcolor(color); 0155 color = color.replace(0, 1, QStringLiteral("0x")) + "ff"; 0156 int duration = doc->getFramePos(doc->timecode().getTimecode(dia_ui.clip_duration->gentime())); 0157 QString name = dia_ui.clip_name->text(); 0158 0159 ClipCreator::createColorClip(color, duration, name, parentFolder, std::move(model)); 0160 } 0161 } 0162 0163 void ClipCreationDialog::createAnimationClip(KdenliveDoc *doc, const QString &parentId) 0164 { 0165 if (!GlaxnimateLauncher::instance().checkInstalled()) { 0166 return; 0167 } 0168 QDir dir; 0169 QString path = KRecentDirs::dir(QStringLiteral(":KdenliveAnimationFolder")); 0170 if (path.isEmpty()) { 0171 dir = QDir(doc->projectDataFolder()); 0172 if (!dir.cd("animations")) { 0173 dir.mkpath("animations"); 0174 dir.cd("animations"); 0175 } 0176 } else { 0177 dir = QDir(path); 0178 } 0179 QString fileName("animation-0001.rawr"); 0180 int ix = 2; 0181 while (dir.exists(fileName) && ix < 9999) { 0182 QString number = QString::number(ix).rightJustified(4, '0'); 0183 number.prepend(QStringLiteral("animation-")); 0184 number.append(QStringLiteral(".rawr")); 0185 fileName = number; 0186 ix++; 0187 } 0188 QDialog d(QApplication::activeWindow()); 0189 d.setWindowTitle(i18n("Create animation")); 0190 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); 0191 auto *l = new QVBoxLayout; 0192 d.setLayout(l); 0193 KUrlRequester fileUrl(&d); 0194 fileUrl.setAcceptMode(QFileDialog::AcceptSave); 0195 fileUrl.setMode(KFile::File); 0196 fileUrl.setUrl(QUrl::fromLocalFile(dir.absoluteFilePath(fileName))); 0197 l->addWidget(new QLabel(i18n("Save animation as"), &d)); 0198 l->addWidget(&fileUrl); 0199 QHBoxLayout *lay = new QHBoxLayout; 0200 lay->addWidget(new QLabel(i18n("Animation duration"), &d)); 0201 TimecodeDisplay tCode(&d); 0202 tCode.setValue(QStringLiteral("00:00:05:00")); 0203 lay->addWidget(&tCode); 0204 l->addLayout(lay); 0205 l->addWidget(buttonBox); 0206 d.connect(buttonBox, &QDialogButtonBox::rejected, &d, &QDialog::reject); 0207 d.connect(buttonBox, &QDialogButtonBox::accepted, &d, &QDialog::accept); 0208 if (d.exec() != QDialog::Accepted) { 0209 return; 0210 } 0211 fileName = fileUrl.url().toLocalFile(); 0212 if (QFile::exists(fileName)) { 0213 KIO::RenameDialog renameDialog(QApplication::activeWindow(), i18n("File already exists"), fileUrl.url(), fileUrl.url(), 0214 KIO::RenameDialog_Option::RenameDialog_Overwrite); 0215 if (renameDialog.exec() != QDialog::Rejected) { 0216 QUrl final = renameDialog.newDestUrl(); 0217 if (final.isValid()) { 0218 fileName = final.toLocalFile(); 0219 } 0220 } else { 0221 return; 0222 } 0223 } 0224 KRecentDirs::add(QStringLiteral(":KdenliveAnimationFolder"), QFileInfo(fileName).absolutePath()); 0225 int frameLength = tCode.getValue() - 1; 0226 // Params: duration, framerate, width, height 0227 const QString templateRawr = 0228 QString( 0229 "{ \"animation\": { \"__type__\": \"MainComposition\", \"animation\": { \"__type__\": \"AnimationContainer\", \"first_frame\": 0, \"last_frame\": " 0230 "%1 }, \"fps\": %2, \"group_color\": \"#00000000\", \"height\": %4, \"locked\": false, \"name\": \"Animation\", \"shapes\": [ { \"__type__\": " 0231 "\"Layer\", \"animation\": { \"__type__\": \"AnimationContainer\", \"first_frame\": 0, \"last_frame\": %1 }, \"group_color\": \"#00000000\", " 0232 "\"locked\": false, \"mask\": { \"__type__\": \"MaskSettings\", \"inverted\": false, \"mask\": \"NoMask\" }, \"name\": \"Layer\", \"opacity\": { " 0233 "\"value\": 1 }, \"parent\": null, \"render\": true, \"shapes\": [ ], \"transform\": { \"__type__\": \"Transform\", \"anchor_point\": { \"value\": " 0234 "{ \"x\": 160, \"y\": 160 } }, \"position\": { \"value\": { \"x\": 160, \"y\": 160 } }, \"rotation\": { \"value\": 0 }, \"scale\": { \"value\": { " 0235 "\"x\": 1, \"y\": 1 } } }, \"uuid\": \"39cd3d4c-2a87-4af9-b52c-704f2c320aa3\", \"visible\": true } ], \"uuid\": " 0236 "\"b85ac6be-7935-45b9-9a62-7347d1cdf972\", \"visible\": true, \"width\": %3 }, \"assets\": { \"__type__\": \"Assets\", \"colors\": { \"__type__\": " 0237 "\"NamedColorList\", \"name\": \"\", \"uuid\": \"af0a82b1-daff-404d-9c91-aecdf1a7b1ba\", \"values\": [ ] }, \"fonts\": { \"__type__\": " 0238 "\"FontList\", \"name\": \"\", \"uuid\": \"af90956d-af34-4a81-9ab5-fa6d0520f96c\", \"values\": [ ] }, \"gradient_colors\": { \"__type__\": " 0239 "\"GradientColorsList\", \"name\": \"\", \"uuid\": \"eb84adf9-92de-44c5-a7c6-52e9390473c1\", \"values\": [ ] }, \"gradients\": { \"__type__\": " 0240 "\"GradientList\", \"name\": \"\", \"uuid\": \"096ffcf6-60a0-46e5-964e-752cb64a7607\", \"values\": [ ] }, \"images\": { \"__type__\": " 0241 "\"BitmapList\", \"name\": \"\", \"uuid\": \"333b9e0c-4825-4108-9de4-2a64f2dbb523\", \"values\": [ ] }, \"name\": \"\", \"precompositions\": { " 0242 "\"__type__\": \"PrecompositionList\", \"name\": \"\", \"uuid\": \"cca2c63d-5295-4ab2-ae32-72e58af6f3e0\", \"values\": [ ] }, \"uuid\": " 0243 "\"d7ac670a-6dc6-4ad9-8e3a-26348903a7e1\" }, \"format\": { \"format_version\": 7, \"generator\": \"Glaxnimate\", \"generator_version\": " 0244 "\"0.5.3-51-g110a1d77\" }, \"info\": { \"author\": \"\", \"description\": \"\", \"keywords\": [ ] }, \"metadata\": { } }") 0245 .arg(frameLength) 0246 .arg(QString::number(doc->timecode().fps())) 0247 .arg(pCore->getCurrentFrameSize().width()) 0248 .arg(pCore->getCurrentFrameSize().height()); 0249 /*const QString templateJson = 0250 QString("{\"v\":\"5.7.1\",\"ip\":0,\"op\":%1,\"nm\":\"Animation\",\"mn\":\"{c9eac49f-b1f0-482f-a8d8-302293bd1e46}\",\"fr\":%2,\"w\":%3,\"h\":%4," 0251 "\"assets\":[],\"layers\":[{\"ddd\":0,\"ty\":3,\"ind\":0,\"st\":0,\"ip\":0,\"op\":90,\"nm\":\"Layer\",\"mn\":\"{4d7c9721-b5ef-4075-a89c-" 0252 "c4c5629423db}\",\"ks\":{\"a\":{\"a\":0,\"k\":[960,540]},\"p\":{\"a\":0,\"k\":[960,540]},\"s\":{\"a\":0,\"k\":[100,100]},\"r\":{\"a\":0,\"k\":" 0253 "0},\"o\":{\"a\":0,\"k\":100}}}],\"meta\":{\"g\":\"Glaxnimate 0.5.0-52-g36d6269d\"}}") 0254 .arg(frameLength) 0255 .arg(QString::number(doc->timecode().fps())) 0256 .arg(pCore->getCurrentFrameSize().width()) 0257 .arg(pCore->getCurrentFrameSize().height());*/ 0258 QFile file(fileName); 0259 file.open(QIODevice::WriteOnly | QIODevice::Text); 0260 QTextStream out(&file); 0261 out << templateRawr; 0262 file.close(); 0263 GlaxnimateLauncher::instance().openFile(fileName); 0264 // Add clip to project 0265 QDomDocument xml; 0266 QDomElement prod = xml.createElement(QStringLiteral("producer")); 0267 xml.appendChild(prod); 0268 prod.setAttribute(QStringLiteral("type"), int(ClipType::Animation)); 0269 int id = pCore->projectItemModel()->getFreeClipId(); 0270 prod.setAttribute(QStringLiteral("id"), QString::number(id)); 0271 QMap<QString, QString> properties; 0272 if (!parentId.isEmpty()) { 0273 properties.insert(QStringLiteral("kdenlive:folderid"), parentId); 0274 } 0275 properties.insert(QStringLiteral("mlt_service"), QStringLiteral("glaxnimate")); 0276 properties.insert(QStringLiteral("resource"), fileName); 0277 Xml::addXmlProperties(prod, properties); 0278 QString clipId = QString::number(id); 0279 pCore->projectItemModel()->requestAddBinClip(clipId, xml.documentElement(), parentId, i18n("Create Animation clip")); 0280 } 0281 0282 const QString ClipCreationDialog::createPlaylistClip(const QString &name, std::pair<int, int> tracks, const QString &parentFolder, 0283 std::shared_ptr<ProjectItemModel> model) 0284 { 0285 return ClipCreator::createPlaylistClip(name, tracks, parentFolder, model); 0286 } 0287 0288 void ClipCreationDialog::createQTextClip(const QString &parentId, Bin *bin, ProjectClip *clip) 0289 { 0290 KSharedConfigPtr config = KSharedConfig::openConfig(); 0291 KConfigGroup titleConfig(config, "TitleWidget"); 0292 QScopedPointer<QDialog> dia(new QDialog(bin)); 0293 Ui::QTextClip_UI dia_ui; 0294 dia_ui.setupUi(dia.data()); 0295 dia->setWindowTitle(i18nc("@title:window", "Text Clip")); 0296 dia_ui.fgColor->setAlphaChannelEnabled(true); 0297 dia_ui.lineColor->setAlphaChannelEnabled(true); 0298 dia_ui.bgColor->setAlphaChannelEnabled(true); 0299 if (clip) { 0300 dia_ui.name->setText(clip->clipName()); 0301 dia_ui.text->setPlainText(clip->getProducerProperty(QStringLiteral("text"))); 0302 dia_ui.fgColor->setColor(QColorUtils::stringToColor(clip->getProducerProperty(QStringLiteral("fgcolour")))); 0303 dia_ui.bgColor->setColor(QColorUtils::stringToColor(clip->getProducerProperty(QStringLiteral("bgcolour")))); 0304 dia_ui.pad->setValue(clip->getProducerProperty(QStringLiteral("pad")).toInt()); 0305 dia_ui.lineColor->setColor(QColorUtils::stringToColor(clip->getProducerProperty(QStringLiteral("olcolour")))); 0306 dia_ui.lineWidth->setValue(clip->getProducerProperty(QStringLiteral("outline")).toInt()); 0307 dia_ui.font->setCurrentFont(QFont(clip->getProducerProperty(QStringLiteral("family")))); 0308 dia_ui.fontSize->setValue(clip->getProducerProperty(QStringLiteral("size")).toInt()); 0309 dia_ui.weight->setValue(clip->getProducerProperty(QStringLiteral("weight")).toInt()); 0310 dia_ui.italic->setChecked(clip->getProducerProperty(QStringLiteral("style")) == QStringLiteral("italic")); 0311 dia_ui.duration->setValue(clip->frameDuration()); 0312 } else { 0313 dia_ui.name->setText(i18n("Text Clip")); 0314 dia_ui.fgColor->setColor(titleConfig.readEntry(QStringLiteral("font_color"))); 0315 dia_ui.bgColor->setColor(titleConfig.readEntry(QStringLiteral("background_color"))); 0316 dia_ui.lineColor->setColor(titleConfig.readEntry(QStringLiteral("font_outline_color"))); 0317 dia_ui.lineWidth->setValue(titleConfig.readEntry(QStringLiteral("font_outline")).toInt()); 0318 dia_ui.font->setCurrentFont(QFont(titleConfig.readEntry(QStringLiteral("font_family")))); 0319 dia_ui.fontSize->setValue(titleConfig.readEntry(QStringLiteral("font_pixel_size")).toInt()); 0320 dia_ui.weight->setValue(titleConfig.readEntry(QStringLiteral("font_weight")).toInt()); 0321 dia_ui.italic->setChecked(titleConfig.readEntry(QStringLiteral("font_italic")).toInt() != 0); 0322 } 0323 if (dia->exec() == QDialog::Accepted) { 0324 // KdenliveSettings::setColorclipcolor(color); 0325 QMap<QString, QString> properties; 0326 properties.insert(QStringLiteral("kdenlive:clipname"), dia_ui.name->text()); 0327 if (!parentId.isEmpty()) { 0328 properties.insert(QStringLiteral("kdenlive:folderid"), parentId); 0329 } 0330 int newDuration = dia_ui.duration->getValue(); 0331 if (clip && (newDuration != clip->getFramePlaytime())) { 0332 // duration changed, we need to update duration 0333 properties.insert(QStringLiteral("out"), clip->framesToTime(newDuration - 1)); 0334 int currentLength = clip->getProducerDuration(); 0335 if (currentLength != newDuration) { 0336 properties.insert(QStringLiteral("kdenlive:duration"), clip->framesToTime(newDuration)); 0337 properties.insert(QStringLiteral("length"), QString::number(newDuration)); 0338 } 0339 } 0340 0341 properties.insert(QStringLiteral("mlt_service"), QStringLiteral("qtext")); 0342 // properties.insert(QStringLiteral("scale"), QStringLiteral("off")); 0343 // properties.insert(QStringLiteral("fill"), QStringLiteral("0")); 0344 properties.insert(QStringLiteral("text"), dia_ui.text->document()->toPlainText()); 0345 properties.insert(QStringLiteral("fgcolour"), dia_ui.fgColor->color().name(QColor::HexArgb)); 0346 properties.insert(QStringLiteral("bgcolour"), dia_ui.bgColor->color().name(QColor::HexArgb)); 0347 properties.insert(QStringLiteral("olcolour"), dia_ui.lineColor->color().name(QColor::HexArgb)); 0348 properties.insert(QStringLiteral("outline"), QString::number(dia_ui.lineWidth->value())); 0349 properties.insert(QStringLiteral("pad"), QString::number(dia_ui.pad->value())); 0350 properties.insert(QStringLiteral("family"), dia_ui.font->currentFont().family()); 0351 properties.insert(QStringLiteral("size"), QString::number(dia_ui.fontSize->value())); 0352 properties.insert(QStringLiteral("style"), dia_ui.italic->isChecked() ? QStringLiteral("italic") : QStringLiteral("normal")); 0353 properties.insert(QStringLiteral("weight"), QString::number(dia_ui.weight->value())); 0354 if (clip) { 0355 bin->slotEditClipCommand(clip->AbstractProjectItem::clipId(), clip->currentProperties(properties), properties); 0356 } else { 0357 QDomDocument xml; 0358 QDomElement prod = xml.createElement(QStringLiteral("producer")); 0359 xml.appendChild(prod); 0360 prod.setAttribute(QStringLiteral("type"), int(ClipType::QText)); 0361 int id = pCore->projectItemModel()->getFreeClipId(); 0362 prod.setAttribute(QStringLiteral("id"), QString::number(id)); 0363 0364 prod.setAttribute(QStringLiteral("in"), QStringLiteral("0")); 0365 prod.setAttribute(QStringLiteral("out"), newDuration); 0366 Xml::addXmlProperties(prod, properties); 0367 QString clipId = QString::number(id); 0368 pCore->projectItemModel()->requestAddBinClip(clipId, xml.documentElement(), parentId, i18n("Create Text clip")); 0369 } 0370 } 0371 } 0372 0373 // static 0374 void ClipCreationDialog::createSlideshowClip(KdenliveDoc *doc, const QString &parentId, std::shared_ptr<ProjectItemModel> model) 0375 { 0376 QScopedPointer<SlideshowClip> dia( 0377 new SlideshowClip(doc->timecode(), KRecentDirs::dir(QStringLiteral(":KdenliveSlideShowFolder")), nullptr, QApplication::activeWindow())); 0378 0379 if (dia->exec() == QDialog::Accepted) { 0380 // Ready, create xml 0381 KRecentDirs::add(QStringLiteral(":KdenliveSlideShowFolder"), QUrl::fromLocalFile(dia->selectedPath()).adjusted(QUrl::RemoveFilename).toLocalFile()); 0382 KdenliveSettings::setSlideshowmimeextension(dia->extension()); 0383 std::unordered_map<QString, QString> properties; 0384 properties[QStringLiteral("ttl")] = QString::number(doc->getFramePos(dia->clipDuration())); 0385 properties[QStringLiteral("loop")] = QString::number(static_cast<int>(dia->loop())); 0386 properties[QStringLiteral("crop")] = QString::number(static_cast<int>(dia->crop())); 0387 properties[QStringLiteral("fade")] = QString::number(static_cast<int>(dia->fade())); 0388 properties[QStringLiteral("luma_duration")] = QString::number(doc->getFramePos(dia->lumaDuration())); 0389 properties[QStringLiteral("luma_file")] = dia->lumaFile(); 0390 properties[QStringLiteral("softness")] = QString::number(dia->softness()); 0391 properties[QStringLiteral("animation")] = dia->animation(); 0392 properties[QStringLiteral("low-pass")] = QString::number(dia->lowPass()); 0393 int duration = doc->getFramePos(dia->clipDuration()) * dia->imageCount(); 0394 ClipCreator::createSlideshowClip(dia->selectedPath(), duration, dia->clipName(), parentId, properties, std::move(model)); 0395 } 0396 } 0397 0398 void ClipCreationDialog::createTitleClip(KdenliveDoc *doc, const QString &parentFolder, const QString &templatePath, std::shared_ptr<ProjectItemModel> model) 0399 { 0400 // Make sure the titles folder exists 0401 QDir dir(doc->projectDataFolder() + QStringLiteral("/titles")); 0402 dir.mkpath(QStringLiteral(".")); 0403 QPointer<TitleWidget> dia_ui = 0404 new TitleWidget(QUrl::fromLocalFile(templatePath), dir.absolutePath(), pCore->getMonitor(Kdenlive::ProjectMonitor), pCore->bin()); 0405 if (dia_ui->exec() == QDialog::Accepted) { 0406 // Ready, create clip xml 0407 std::unordered_map<QString, QString> properties; 0408 properties[QStringLiteral("xmldata")] = dia_ui->xml().toString(); 0409 QString titleSuggestion = dia_ui->titleSuggest(); 0410 0411 ClipCreator::createTitleClip(properties, dia_ui->duration(), titleSuggestion.isEmpty() ? i18n("Title clip") : titleSuggestion, parentFolder, 0412 std::move(model)); 0413 } 0414 delete dia_ui; 0415 } 0416 0417 void ClipCreationDialog::createTitleTemplateClip(KdenliveDoc *doc, const QString &parentFolder, std::shared_ptr<ProjectItemModel> model) 0418 { 0419 0420 QScopedPointer<TitleTemplateDialog> dia(new TitleTemplateDialog(doc->projectDataFolder(), QApplication::activeWindow())); 0421 0422 if (dia->exec() == QDialog::Accepted) { 0423 ClipCreator::createTitleTemplate(dia->selectedTemplate(), dia->selectedText(), i18n("Template title clip"), parentFolder, std::move(model)); 0424 } 0425 } 0426 0427 // void ClipCreationDialog::createClipsCommand(KdenliveDoc *doc, const QList<QUrl> &urls, const QStringList &groupInfo, Bin *bin, 0428 // const QMap<QString, QString> &data) 0429 // { 0430 // auto *addClips = new QUndoCommand(); 0431 0432 // TODO: check files on removable volume 0433 /*listRemovableVolumes(); 0434 for (const QUrl &file : urls) { 0435 if (QFile::exists(file.path())) { 0436 //TODO check for duplicates 0437 if (!data.contains("bypassDuplicate") && !getClipByResource(file.path()).empty()) { 0438 if (KMessageBox::warningContinueCancel(QApplication::activeWindow(), i18n("Clip <b>%1</b><br />already exists in project, what do you want to 0439 do?", file.path()), i18n("Clip already exists")) == KMessageBox::Cancel) 0440 continue; 0441 } 0442 if (isOnRemovableDevice(file) && !isOnRemovableDevice(m_doc->projectFolder())) { 0443 int answer = KMessageBox::warningYesNoCancel(QApplication::activeWindow(), i18n("Clip <b>%1</b><br /> is on a removable device, will not be 0444 available when device is unplugged", file.path()), i18n("File on a Removable Device"), KGuiItem(i18n("Copy file to project folder")), 0445 KGuiItem(i18n("Continue")), KStandardGuiItem::cancel(), QString("copyFilesToProjectFolder")); 0446 0447 if (answer == KMessageBox::Cancel) continue; 0448 else if (answer == KMessageBox::Yes) { 0449 // Copy files to project folder 0450 QDir sourcesFolder(m_doc->projectFolder().toLocalFile()); 0451 sourcesFolder.cd("clips"); 0452 KIO::MkdirJob *mkdirJob = KIO::mkdir(QUrl::fromLocalFile(sourcesFolder.absolutePath())); 0453 KJobWidgets::setWindow(mkdirJob, QApplication::activeWindow()); 0454 if (!mkdirJob->exec()) { 0455 KMessageBox::error(QApplication::activeWindow(), i18n("Cannot create directory %1", sourcesFolder.absolutePath())); 0456 continue; 0457 } 0458 //KIO::filesize_t m_requestedSize; 0459 KIO::CopyJob *copyjob = KIO::copy(file, QUrl::fromLocalFile(sourcesFolder.absolutePath())); 0460 //TODO: for some reason, passing metadata does not work... 0461 copyjob->addMetaData("group", data.value("group")); 0462 copyjob->addMetaData("groupId", data.value("groupId")); 0463 copyjob->addMetaData("comment", data.value("comment")); 0464 KJobWidgets::setWindow(copyjob, QApplication::activeWindow()); 0465 connect(copyjob, &KIO::CopyJob::copyingDone, this, &ClipManager::slotAddCopiedClip); 0466 continue; 0467 } 0468 }*/ 0469 0470 // TODO check folders 0471 /*QList< QList<QUrl> > foldersList; 0472 QMimeDatabase db; 0473 for (const QUrl & file : list) { 0474 // Check there is no folder here 0475 QMimeType type = db.mimeTypeForUrl(file); 0476 if (type.inherits("inode/directory")) { 0477 // user dropped a folder, import its files 0478 list.removeAll(file); 0479 QDir dir(file.path()); 0480 QStringList result = dir.entryList(QDir::Files); 0481 QList<QUrl> folderFiles; 0482 folderFiles << file; 0483 for (const QString & path : result) { 0484 // TODO: create folder command 0485 folderFiles.append(QUrl::fromLocalFile(dir.absoluteFilePath(path))); 0486 } 0487 if (folderFiles.count() > 1) foldersList.append(folderFiles); 0488 } 0489 }*/ 0490 //} 0491 0492 void ClipCreationDialog::createClipsCommand(KdenliveDoc *doc, const QString &parentFolder, const std::shared_ptr<ProjectItemModel> &model) 0493 { 0494 qDebug() << "/////////// starting to add bin clips"; 0495 QList<QUrl> list; 0496 QCheckBox *b = new QCheckBox(i18n("Import image sequence")); 0497 b->setToolTip(i18n("Try to import an image sequence")); 0498 b->setWhatsThis( 0499 xi18nc("@info:whatsthis", "When enabled, Kdenlive will look for other images with the same name pattern and import them as an image sequence.")); 0500 b->setChecked(KdenliveSettings::autoimagesequence()); 0501 QCheckBox *bf = new QCheckBox(i18n("Ignore subfolder structure")); 0502 bf->setChecked(KdenliveSettings::ignoresubdirstructure()); 0503 bf->setToolTip(i18n("Do not create subfolders in Project Bin")); 0504 bf->setWhatsThis( 0505 xi18nc("@info:whatsthis", 0506 "When enabled, Kdenlive will import all clips contained in the folder and its subfolders without creating the subfolders in Project Bin.")); 0507 QFrame *f = new QFrame(); 0508 f->setFrameShape(QFrame::NoFrame); 0509 auto *l = new QHBoxLayout; 0510 l->addWidget(b); 0511 l->addWidget(bf); 0512 l->addStretch(5); 0513 f->setLayout(l); 0514 QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder")); 0515 QScopedPointer<QDialog> dlg(new QDialog(static_cast<QWidget *>(doc->parent()))); 0516 QScopedPointer<KFileWidget> fileWidget(new KFileWidget(QUrl::fromLocalFile(clipFolder), dlg.data())); 0517 auto *layout = new QVBoxLayout; 0518 layout->addWidget(fileWidget.data()); 0519 fileWidget->setCustomWidget(f); 0520 fileWidget->okButton()->show(); 0521 fileWidget->cancelButton()->show(); 0522 QObject::connect(fileWidget->okButton(), &QPushButton::clicked, fileWidget.data(), &KFileWidget::slotOk); 0523 QObject::connect(fileWidget.data(), &KFileWidget::accepted, fileWidget.data(), &KFileWidget::accept); 0524 QObject::connect(fileWidget.data(), &KFileWidget::accepted, dlg.data(), &QDialog::accept); 0525 QObject::connect(fileWidget->cancelButton(), &QPushButton::clicked, dlg.data(), &QDialog::reject); 0526 dlg->setLayout(layout); 0527 0528 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0529 QString allExtensions = getExtensions().join(QLatin1Char(' ')); 0530 QString dialogFilter = allExtensions + QLatin1Char('|') + i18n("All Supported Files") + QStringLiteral("\n*|") + i18n("All Files"); 0531 fileWidget->setFilter(dialogFilter); 0532 #else 0533 const QStringList allExtensions = getExtensions(); 0534 const QList<KFileFilter> filters{ 0535 KFileFilter(i18n("All Supported Files"), allExtensions, {}), 0536 KFileFilter(i18n("All Files"), {QStringLiteral("*")}, {}), 0537 }; 0538 fileWidget->setFilters(filters); 0539 #endif 0540 0541 fileWidget->setMode(KFile::Files | KFile::ExistingOnly | KFile::LocalOnly | KFile::Directory); 0542 KSharedConfig::Ptr conf = KSharedConfig::openConfig(); 0543 QWindow *handle = dlg->windowHandle(); 0544 if ((handle != nullptr) && conf->hasGroup("FileDialogSize")) { 0545 KWindowConfig::restoreWindowSize(handle, conf->group("FileDialogSize")); 0546 dlg->resize(handle->size()); 0547 } 0548 int result = dlg->exec(); 0549 if (result == QDialog::Accepted) { 0550 KdenliveSettings::setAutoimagesequence(b->isChecked()); 0551 KdenliveSettings::setIgnoresubdirstructure(bf->isChecked()); 0552 list = fileWidget->selectedUrls(); 0553 if (!list.isEmpty()) { 0554 KRecentDirs::add(QStringLiteral(":KdenliveClipFolder"), list.constFirst().adjusted(QUrl::RemoveFilename).toLocalFile()); 0555 } 0556 if (KdenliveSettings::autoimagesequence() && list.count() >= 1) { 0557 // Check for image sequence 0558 const QUrl &url = list.at(0); 0559 QString fileName = url.fileName().section(QLatin1Char('.'), 0, -2); 0560 if (!fileName.isEmpty() && fileName.at(fileName.size() - 1).isDigit()) { 0561 KFileItem item(url); 0562 if (item.mimetype().startsWith(QLatin1String("image"))) { 0563 // import as sequence if we found more than one image in the sequence 0564 QStringList patternlist; 0565 QString pattern = SlideshowClip::selectedPath(url, false, QString(), &patternlist); 0566 qCDebug(KDENLIVE_LOG) << " / // IMPORT PATTERN: " << pattern << " COUNT: " << patternlist.count(); 0567 int count = patternlist.count(); 0568 if (count > 1) { 0569 // get image sequence base name 0570 while (fileName.size() > 0 && fileName.at(fileName.size() - 1).isDigit()) { 0571 fileName.chop(1); 0572 } 0573 0574 QString duration = doc->timecode().reformatSeparators(KdenliveSettings::sequence_duration()); 0575 std::unordered_map<QString, QString> properties; 0576 properties[QStringLiteral("ttl")] = QString::number(doc->getFramePos(duration)); 0577 properties[QStringLiteral("loop")] = QString::number(0); 0578 properties[QStringLiteral("crop")] = QString::number(0); 0579 properties[QStringLiteral("fade")] = QString::number(0); 0580 properties[QStringLiteral("luma_duration")] = 0581 QString::number(doc->getFramePos(doc->timecode().getTimecodeFromFrames(int(ceil(doc->timecode().fps()))))); 0582 int frame_duration = doc->getFramePos(duration) * count; 0583 ClipCreator::createSlideshowClip(pattern, frame_duration, fileName, parentFolder, properties, model); 0584 return; 0585 } 0586 } 0587 } 0588 } 0589 } 0590 qDebug() << "/////////// found list" << list; 0591 KConfigGroup group = conf->group("FileDialogSize"); 0592 if (handle) { 0593 KWindowConfig::saveWindowSize(handle, group); 0594 } 0595 Fun undo = []() { return true; }; 0596 Fun redo = []() { return true; }; 0597 const QString id = ClipCreator::createClipsFromList(list, true, parentFolder, model, undo, redo); 0598 0599 // We reset the state of the "don't ask again" for the question about removable devices 0600 KMessageBox::enableMessage(QStringLiteral("removable")); 0601 if (!id.isEmpty()) { 0602 pCore->pushUndo(undo, redo, i18np("Add clip", "Add clips", list.size())); 0603 } 0604 }