File indexing completed on 2024-12-08 04:27:17
0001 /* 0002 SPDX-FileCopyrightText: 2022 Meltytech, LLC 0003 SPDX-FileCopyrightText: 2022 Julius Künzel <jk.kdedev@smartlab.uber.space> 0004 0005 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 0008 #include "glaxnimatelauncher.h" 0009 #include "bin/projectclip.h" 0010 #include "bin/projectitemmodel.h" 0011 #include "core.h" 0012 #include "dialogs/kdenlivesettingsdialog.h" 0013 #include "doc/kthumb.h" 0014 #include "kdenlivesettings.h" 0015 #include "mainwindow.h" 0016 #include "timeline2/view/timelinewidget.h" 0017 0018 #include <KConfigDialog> 0019 #include <KMessageBox> 0020 #include <KUrlRequesterDialog> 0021 #include <QLocalServer> 0022 #include <QLocalSocket> 0023 #include <QSharedMemory> 0024 0025 bool GlaxnimateLauncher::checkInstalled() 0026 { 0027 if (!KdenliveSettings::glaxnimatePath().isEmpty() && QFile(KdenliveSettings::glaxnimatePath()).exists()) { 0028 return true; 0029 } 0030 QUrl url = KUrlRequesterDialog::getUrl(QUrl(), nullptr, i18n("Enter path to the Glaxnimate application")); 0031 if (url.isEmpty() || !QFile(url.toLocalFile()).exists()) { 0032 KMessageBox::error(QApplication::activeWindow(), i18n("You need enter a valid path to be able to edit Lottie animations.")); 0033 return false; 0034 } 0035 KdenliveSettings::setGlaxnimatePath(url.toLocalFile()); 0036 KdenliveSettingsDialog *d = static_cast<KdenliveSettingsDialog *>(KConfigDialog::exists(QStringLiteral("settings"))); 0037 if (d) { 0038 d->updateExternalApps(); 0039 } 0040 return true; 0041 } 0042 0043 GlaxnimateLauncher &GlaxnimateLauncher::instance() 0044 { 0045 static GlaxnimateLauncher instance; 0046 return instance; 0047 } 0048 0049 void GlaxnimateLauncher::reset() 0050 { 0051 if (m_stream && m_socket && m_stream && QLocalSocket::ConnectedState == m_socket->state()) { 0052 *m_stream << QString("clear"); 0053 m_socket->flush(); 0054 } 0055 m_parent.reset(); 0056 } 0057 0058 void GlaxnimateLauncher::openFile(const QString &filename) 0059 { 0060 QString error = pCore->openExternalApp(KdenliveSettings::glaxnimatePath(), {filename}); 0061 if (!error.isEmpty()) { 0062 KMessageBox::detailedError(QApplication::activeWindow(), i18n("Failed to launch Glaxnimate application"), error); 0063 return; 0064 } 0065 } 0066 0067 void GlaxnimateLauncher::openClip(int clipId) 0068 { 0069 if (!checkInstalled()) { 0070 return; 0071 } 0072 if ((m_server && m_socket && m_stream && QLocalSocket::ConnectedState == m_socket->state()) || (m_parent && m_parent->m_clipId != -1)) { 0073 // There is already an open connection, it is only supported to send the background to one Glaxnimate instance at the time 0074 // Open the clip without sending the background 0075 openFile(pCore->projectItemModel()->getClipByBinID(pCore->window()->getCurrentTimeline()->model()->getClipBinId(clipId))->clipUrl()); 0076 return; 0077 } 0078 m_parent.reset(new ParentResources); 0079 0080 m_parent->m_binClip = pCore->projectItemModel()->getClipByBinID(pCore->window()->getCurrentTimeline()->model()->getClipBinId(clipId)); 0081 if (m_parent->m_binClip->clipType() != ClipType::Animation) { 0082 pCore->displayMessage(i18n("Item is not an animation clip"), ErrorMessage, 500); 0083 return; 0084 } 0085 0086 QString filename = m_parent->m_binClip->clipUrl(); 0087 m_parent->m_clipId = clipId; 0088 m_server.reset(new QLocalServer); 0089 connect(m_server.get(), &QLocalServer::newConnection, this, &GlaxnimateLauncher::onConnect); 0090 QString name = QString("kdenlive-%1").arg(QCoreApplication::applicationPid()); 0091 QStringList args = {"--ipc", name, filename}; 0092 /*QProcess childProcess; 0093 QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); 0094 env.remove("LC_ALL"); 0095 childProcess.setProcessEnvironment(env);*/ 0096 QString error; 0097 if (!m_server->listen(name)) { 0098 qDebug() << "failed to start the IPC server:" << m_server->errorString(); 0099 m_server.reset(); 0100 args.clear(); 0101 args << filename; 0102 qDebug() << "Run without --ipc"; 0103 error = pCore->openExternalApp(KdenliveSettings::glaxnimatePath(), args); 0104 if (error.isEmpty()) { 0105 m_sharedMemory.reset(new QSharedMemory(name)); 0106 return; 0107 } 0108 } else { 0109 if (pCore->openExternalApp(KdenliveSettings::glaxnimatePath(), args).isEmpty()) { 0110 m_sharedMemory.reset(new QSharedMemory(name)); 0111 return; 0112 } else { 0113 // This glaxnimate executable may not support --ipc 0114 // XXX startDetached is not failing in this case, need something better 0115 qDebug() << "Failed to start glaxnimate with the --ipc, trying without now"; 0116 m_server.reset(); 0117 args.clear(); 0118 args << filename; 0119 qDebug() << "Run without --ipc"; 0120 error = pCore->openExternalApp(KdenliveSettings::glaxnimatePath(), args); 0121 } 0122 } 0123 if (!error.isEmpty()) { 0124 KMessageBox::detailedError(QApplication::activeWindow(), i18n("Failed to launch Glaxnimate application"), error); 0125 return; 0126 } 0127 } 0128 0129 void GlaxnimateLauncher::onConnect() 0130 { 0131 m_socket = m_server->nextPendingConnection(); 0132 connect(m_socket, &QLocalSocket::readyRead, this, &GlaxnimateLauncher::onReadyRead); 0133 connect(m_socket, &QLocalSocket::errorOccurred, this, &GlaxnimateLauncher::onSocketError); 0134 m_stream.reset(new QDataStream(m_socket)); 0135 m_stream->setVersion(QDataStream::Qt_5_15); 0136 *m_stream << QString("hello"); 0137 m_socket->flush(); 0138 m_server->close(); 0139 m_isProtocolValid = false; 0140 } 0141 0142 void GlaxnimateLauncher::onReadyRead() 0143 { 0144 if (!m_isProtocolValid) { 0145 QString message; 0146 *m_stream >> message; 0147 qDebug() << message; 0148 if (message.startsWith("version ") && message != "version 1") { 0149 *m_stream << QString("bye"); 0150 m_socket->flush(); 0151 m_server->close(); 0152 } else { 0153 m_isProtocolValid = true; 0154 } 0155 } else { 0156 qreal time = -1.0; 0157 for (int i = 0; i < 1000 && !m_stream->atEnd(); i++) { 0158 *m_stream >> time; 0159 } 0160 0161 // Only if the frame number is different 0162 int frameNum = pCore->window()->getCurrentTimeline()->model()->getClipPosition(m_parent->m_clipId) + time - 0163 pCore->window()->getCurrentTimeline()->model()->getClipIn(m_parent->m_clipId); 0164 if (frameNum != m_parent->m_frameNum) { 0165 qDebug() << "glaxnimate time =" << time << "=> Kdenlive frameNum =" << frameNum; 0166 0167 // Get the image from MLT 0168 pCore->window()->getCurrentTimeline()->model()->producer().get()->seek(frameNum); 0169 QList<int> clips = m_parent->m_binClip->timelineInstances(); 0170 // Temporarily hide this title clip in timeline so that it does not appear when requesting background frame 0171 pCore->temporaryUnplug(clips, true); 0172 std::unique_ptr<Mlt::Frame> frame(pCore->window()->getCurrentTimeline()->model()->producer().get()->get_frame()); 0173 QImage temp = KThumb::getFrame(frame.get(), pCore->getCurrentFrameSize().width(), pCore->getCurrentFrameSize().height()); 0174 pCore->temporaryUnplug(clips, false); 0175 if (copyToShared(temp)) { 0176 m_parent->m_frameNum = frameNum; 0177 } 0178 } 0179 } 0180 } 0181 0182 void GlaxnimateLauncher::onSocketError(QLocalSocket::LocalSocketError socketError) 0183 { 0184 switch (socketError) { 0185 case QLocalSocket::PeerClosedError: 0186 qDebug() << "Glaxnimate closed the connection"; 0187 m_parent->m_clipId = -1; 0188 m_stream.reset(); 0189 m_sharedMemory.reset(); 0190 break; 0191 default: 0192 qDebug() << "Glaxnimate IPC error:" << m_socket->errorString(); 0193 } 0194 } 0195 0196 bool GlaxnimateLauncher::copyToShared(const QImage &image) 0197 { 0198 if (!m_sharedMemory) { 0199 return false; 0200 } 0201 qint32 sizeInBytes = image.sizeInBytes() + 4 * sizeof(qint32); 0202 if (sizeInBytes > m_sharedMemory->size()) { 0203 if (m_sharedMemory->isAttached()) { 0204 m_sharedMemory->lock(); 0205 m_sharedMemory->detach(); 0206 m_sharedMemory->unlock(); 0207 } 0208 // over-allocate to avoid recreating 0209 if (!m_sharedMemory->create(sizeInBytes)) { 0210 qDebug() << m_sharedMemory->errorString(); 0211 return false; 0212 } 0213 } 0214 if (m_sharedMemory->isAttached()) { 0215 m_sharedMemory->lock(); 0216 0217 uchar *to = (uchar *)m_sharedMemory->data(); 0218 // Write the width of the image and move the pointer forward 0219 qint32 width = image.width(); 0220 ::memcpy(to, &width, sizeof(width)); 0221 to += sizeof(width); 0222 0223 // Write the height of the image and move the pointer forward 0224 qint32 height = image.height(); 0225 ::memcpy(to, &height, sizeof(height)); 0226 to += sizeof(height); 0227 0228 // Write the image format of the image and move the pointer forward 0229 qint32 imageFormat = image.format(); 0230 ::memcpy(to, &imageFormat, sizeof(imageFormat)); 0231 to += sizeof(imageFormat); 0232 0233 // Write the bytes per line of the image and move the pointer forward 0234 qint32 bytesPerLine = image.bytesPerLine(); 0235 ::memcpy(to, &bytesPerLine, sizeof(bytesPerLine)); 0236 to += sizeof(bytesPerLine); 0237 0238 // Write the raw data of the image and move the pointer forward 0239 ::memcpy(to, image.constBits(), image.sizeInBytes()); 0240 0241 m_sharedMemory->unlock(); 0242 if (m_stream && m_socket) { 0243 *m_stream << QString("redraw"); 0244 m_socket->flush(); 0245 } 0246 return true; 0247 } 0248 return false; 0249 }