Commit 99bea51a authored by Jean-Baptiste Mardelle's avatar Jean-Baptiste Mardelle
Browse files

Add support for Lottie animations using the glaxnimate producer (Add Clip > Add Animation)

parent 1d391b6d
Pipeline #191425 passed with stage
in 13 minutes and 41 seconds
......@@ -3180,7 +3180,8 @@ void Bin::setupMenu()
QIcon::fromTheme(QStringLiteral("kdenlive-add-text-clip")));
setupAddClipAction(addClipMenu, ClipType::TextTemplate, QStringLiteral("add_text_template_clip"), i18n("Add Template Title…"),
QIcon::fromTheme(QStringLiteral("kdenlive-add-text-clip")));
setupAddClipAction(addClipMenu, ClipType::Animation, QStringLiteral("add_animation_clip"), i18n("Add Animation…"),
QIcon::fromTheme(QStringLiteral("motion_path_animations")));
QAction *downloadResourceAction =
addAction(QStringLiteral("download_resource"), i18n("Online Resources"), QIcon::fromTheme(QStringLiteral("edit-download")));
addClipMenu->addAction(downloadResourceAction);
......@@ -3358,6 +3359,9 @@ void Bin::slotCreateProjectClip()
case ClipType::QText:
ClipCreationDialog::createQTextClip(m_doc, parentFolder, this);
break;
case ClipType::Animation:
ClipCreationDialog::createAnimationClip(m_doc, parentFolder);
break;
default:
break;
}
......
......@@ -1143,8 +1143,9 @@ void ProjectItemModel::setClipInvalid(const QString &binId)
void ProjectItemModel::updateWatcher(const std::shared_ptr<ProjectClip> &clipItem)
{
QWriteLocker locker(&m_lock);
if (clipItem->clipType() == ClipType::AV || clipItem->clipType() == ClipType::Audio || clipItem->clipType() == ClipType::Image ||
clipItem->clipType() == ClipType::Video || clipItem->clipType() == ClipType::Playlist || clipItem->clipType() == ClipType::TextTemplate) {
ClipType::ProducerType type = clipItem->clipType();
if (type == ClipType::AV || type == ClipType::Audio || type == ClipType::Image || type == ClipType::Video || type == ClipType::Playlist ||
type == ClipType::TextTemplate || type == ClipType::Animation) {
m_fileWatcher->removeFile(clipItem->clipId());
QFileInfo check_file(clipItem->clipUrl());
// check if file exists and if yes: Is it really a file and no directory?
......
......@@ -108,7 +108,8 @@ enum ProducerType {
QText = 12,
Composition = 13,
Track = 14,
Qml = 15
Qml = 15,
Animation = 16
};
Q_ENUM_NS(ProducerType)
} // namespace ClipType
......
......@@ -29,6 +29,7 @@ SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "klocalizedstring.h"
#include <KDirOperator>
#include <KFileWidget>
#include <KIO/RenameDialog>
#include <KMessageBox>
#include <KRecentDirs>
#include <KWindowConfig>
......@@ -139,6 +140,99 @@ void ClipCreationDialog::createColorClip(KdenliveDoc *doc, const QString &parent
}
}
void ClipCreationDialog::createAnimationClip(KdenliveDoc *doc, const QString &parentId)
{
QDir dir(doc->projectDataFolder());
QString fileName("animation-0001.json");
if (dir.cd("animations")) {
int ix = 2;
while (dir.exists(fileName) && ix < 9999) {
QString number = QString::number(ix).rightJustified(4, '0');
number.prepend(QStringLiteral("animation-"));
number.append(QStringLiteral(".json"));
fileName = number;
ix++;
}
} else {
dir.mkpath("animations");
dir.cd("animations");
}
QDialog d(QApplication::activeWindow());
d.setWindowTitle(i18n("Create animation"));
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
auto *l = new QVBoxLayout;
d.setLayout(l);
KUrlRequester fileUrl(&d);
;
fileUrl.setMode(KFile::File);
fileUrl.setUrl(QUrl::fromLocalFile(dir.absoluteFilePath(fileName)));
l->addWidget(new QLabel(i18n("Save animation as"), &d));
l->addWidget(&fileUrl);
QHBoxLayout *lay = new QHBoxLayout;
lay->addWidget(new QLabel(i18n("Animation duration"), &d));
TimecodeDisplay tCode(pCore->timecode(), &d);
tCode.setValue(QStringLiteral("00:00:05:00"));
lay->addWidget(&tCode);
l->addLayout(lay);
l->addWidget(buttonBox);
d.connect(buttonBox, &QDialogButtonBox::rejected, &d, &QDialog::reject);
d.connect(buttonBox, &QDialogButtonBox::accepted, &d, &QDialog::accept);
if (d.exec() != QDialog::Accepted) {
return;
}
fileName = fileUrl.url().toLocalFile();
if (QFile::exists(fileName)) {
KIO::RenameDialog renameDialog(QApplication::activeWindow(), i18n("File already exists"), fileUrl.url(), fileUrl.url(),
KIO::RenameDialog_Option::RenameDialog_Overwrite);
if (renameDialog.exec() != QDialog::Rejected) {
QUrl final = renameDialog.newDestUrl();
if (final.isValid()) {
fileName = final.toLocalFile();
}
} else {
return;
}
}
int frameLength = tCode.getValue() - 1;
// Params: duration, framerate, width, height
const QString templateJson =
QString("{\"v\":\"5.7.1\",\"ip\":0,\"op\":%1,\"nm\":\"Animation\",\"mn\":\"{c9eac49f-b1f0-482f-a8d8-302293bd1e46}\",\"fr\":%2,\"w\":%3,\"h\":%4,"
"\"assets\":[],\"layers\":[{\"ddd\":0,\"ty\":3,\"ind\":0,\"st\":0,\"ip\":0,\"op\":90,\"nm\":\"Layer\",\"mn\":\"{4d7c9721-b5ef-4075-a89c-"
"c4c5629423db}\",\"ks\":{\"a\":{\"a\":0,\"k\":[960,540]},\"p\":{\"a\":0,\"k\":[960,540]},\"s\":{\"a\":0,\"k\":[100,100]},\"r\":{\"a\":0,\"k\":"
"0},\"o\":{\"a\":0,\"k\":100}}}],\"meta\":{\"g\":\"Glaxnimate 0.5.0-52-g36d6269d\"}}")
.arg(frameLength)
.arg(QString::number(doc->timecode().fps()))
.arg(pCore->getCurrentFrameSize().width())
.arg(pCore->getCurrentFrameSize().height());
QFile file(fileName);
file.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream out(&file);
out << templateJson;
file.close();
QString glaxBinary = QStandardPaths::findExecutable(QStringLiteral("glaxnimate"));
if (glaxBinary.isEmpty()) {
KMessageBox::sorry(QApplication::activeWindow(), i18n("Cannot find Glaxnimate, please install it on your computer to allow editing animations."));
return;
}
QProcess::startDetached(glaxBinary, {fileName});
// Add clip to project
QDomDocument xml;
QDomElement prod = xml.createElement(QStringLiteral("producer"));
xml.appendChild(prod);
prod.setAttribute(QStringLiteral("type"), int(ClipType::Animation));
int id = pCore->projectItemModel()->getFreeClipId();
prod.setAttribute(QStringLiteral("id"), QString::number(id));
QMap<QString, QString> properties;
if (!parentId.isEmpty()) {
properties.insert(QStringLiteral("kdenlive:folderid"), parentId);
}
properties.insert(QStringLiteral("mlt_service"), QStringLiteral("glaxnimate"));
properties.insert(QStringLiteral("resource"), fileName);
Xml::addXmlProperties(prod, properties);
QString clipId = QString::number(id);
pCore->projectItemModel()->requestAddBinClip(clipId, xml.documentElement(), parentId, i18n("Create Animation clip"));
}
void ClipCreationDialog::createQTextClip(KdenliveDoc *doc, const QString &parentId, Bin *bin, ProjectClip *clip)
{
KSharedConfigPtr config = KSharedConfig::openConfig();
......
......@@ -26,6 +26,7 @@ QStringList getExtensions();
QString getExtensionsFilter(const QStringList& additionalFilters = QStringList());
void createColorClip(KdenliveDoc *doc, const QString &parentFolder, std::shared_ptr<ProjectItemModel> model);
void createQTextClip(KdenliveDoc *doc, const QString &parentId, Bin *bin, ProjectClip *clip = nullptr);
void createAnimationClip(KdenliveDoc *doc, const QString &parentId);
void createSlideshowClip(KdenliveDoc *doc, const QString &parentId, std::shared_ptr<ProjectItemModel> model);
void createTitleClip(KdenliveDoc *doc, const QString &parentFolder, const QString &templatePath, std::shared_ptr<ProjectItemModel> model);
void createTitleTemplateClip(KdenliveDoc *doc, const QString &parentFolder, std::shared_ptr<ProjectItemModel> model);
......
......@@ -251,6 +251,10 @@ void ClipController::getInfoForProducer()
// Mostly used for testing
m_clipType = ClipType::AV;
m_hasLimitedDuration = true;
} else if (m_service == QLatin1String("glaxnimate")) {
// Mostly used for testing
m_clipType = ClipType::Animation;
m_hasLimitedDuration = true;
} else {
m_clipType = ClipType::Unknown;
}
......@@ -696,18 +700,23 @@ void ClipController::checkAudioVideo()
if (m_masterProducer->property_exists("kdenlive:clip_type")) {
int clipType = m_masterProducer->get_int("kdenlive:clip_type");
switch (clipType) {
case 1:
case ClipType::Audio:
m_hasAudio = true;
m_hasVideo = false;
break;
case 2:
case ClipType::Video:
m_hasAudio = false;
m_hasVideo = true;
break;
default:
case ClipType::AV:
case ClipType::Playlist:
m_hasAudio = true;
m_hasVideo = true;
break;
default:
m_hasAudio = false;
m_hasVideo = true;
break;
}
} else {
QScopedPointer<Mlt::Frame> frame(m_masterProducer->get_frame());
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment