1 /*
2  * Copyright (c) 2014-2021 Meltytech, LLC
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "shotcut_mlt_properties.h"
19 #include "filtercontroller.h"
20 #include <QQmlEngine>
21 #include <QDir>
22 #include <Logger.h>
23 #include <QQmlComponent>
24 #include <QTimerEvent>
25 #include "mltcontroller.h"
26 #include "settings.h"
27 #include "qmltypes/qmlmetadata.h"
28 #include "qmltypes/qmlutilities.h"
29 #include "qmltypes/qmlfilter.h"
30 
FilterController(QObject * parent)31 FilterController::FilterController(QObject* parent) : QObject(parent),
32  m_mltFilter(0),
33  m_metadataModel(this),
34  m_attachedModel(this),
35  m_currentFilterIndex(QmlFilter::NoCurrentFilter)
36 {
37     startTimer(0);
38     connect(&m_attachedModel, SIGNAL(changed()), this, SLOT(handleAttachedModelChange()));
39     connect(&m_attachedModel, SIGNAL(modelAboutToBeReset()), this, SLOT(handleAttachedModelAboutToReset()));
40     connect(&m_attachedModel, SIGNAL(rowsRemoved(const QModelIndex&,int,int)), this, SLOT(handleAttachedRowsRemoved(const QModelIndex&,int,int)));
41     connect(&m_attachedModel, SIGNAL(rowsInserted(const QModelIndex&,int,int)), this, SLOT(handleAttachedRowsInserted(const QModelIndex&,int,int)));
42     connect(&m_attachedModel, SIGNAL(duplicateAddFailed(int)), this, SLOT(handleAttachDuplicateFailed(int)));
43 }
44 
loadFilterMetadata()45 void FilterController::loadFilterMetadata() {
46     QScopedPointer<Mlt::Properties> mltFilters(MLT.repository()->filters());
47     QDir dir = QmlUtilities::qmlDir();
48     dir.cd("filters");
49     foreach (QString dirName, dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Executable)) {
50         QDir subdir = dir;
51         subdir.cd(dirName);
52         subdir.setFilter(QDir::Files | QDir::NoDotAndDotDot | QDir::Readable);
53         subdir.setNameFilters(QStringList("meta*.qml"));
54         foreach (QString fileName, subdir.entryList()) {
55             LOG_DEBUG() << "reading filter metadata" << dirName << fileName;
56             QQmlComponent component(QmlUtilities::sharedEngine(), subdir.absoluteFilePath(fileName));
57             QmlMetadata *meta = qobject_cast<QmlMetadata*>(component.create());
58             if (meta) {
59                 QScopedPointer<Mlt::Properties> mltMetadata(MLT.repository()->metadata(filter_type, meta->mlt_service().toLatin1().constData()));
60                 QString version;
61                 if (mltMetadata && mltMetadata->is_valid() && mltMetadata->get("version")) {
62                     version = QString::fromLatin1(mltMetadata->get("version"));
63                     if (version.startsWith("lavfi"))
64                         version.remove(0, 5);
65                 }
66 
67                     // Check if mlt_service is available.
68                 if (mltFilters->get_data(meta->mlt_service().toLatin1().constData()) &&
69                         (version.isEmpty() || meta->isMltVersion(version))) {
70                     LOG_DEBUG() << "added filter" << meta->name();
71                     meta->loadSettings();
72                     meta->setPath(subdir);
73                     meta->setParent(0);
74                     addMetadata(meta);
75 
76                     // Check if a keyframes minimum version is required.
77                     if (!version.isEmpty() && meta->keyframes()) {
78                         meta->setProperty("version", version);
79                         meta->keyframes()->checkVersion(version);
80                     }
81 
82                     if (meta->isDeprecated())
83                         meta->setName(meta->name() + " " + tr("(DEPRECATED)"));
84                 }
85             } else if (!meta) {
86                 LOG_WARNING() << component.errorString();
87             }
88         }
89     };
90 }
91 
metadataForService(Mlt::Service * service)92 QmlMetadata *FilterController::metadataForService(Mlt::Service *service)
93 {
94     QmlMetadata* meta = 0;
95     int rowCount = m_metadataModel.rowCount();
96     QString uniqueId = service->get(kShotcutFilterProperty);
97 
98     // Fallback to mlt_service for legacy filters
99     if (uniqueId.isEmpty()) {
100         uniqueId = service->get("mlt_service");
101     }
102 
103     for (int i = 0; i < rowCount; i++) {
104         QmlMetadata* tmpMeta = m_metadataModel.get(i);
105         if (tmpMeta->uniqueId() == uniqueId) {
106             meta = tmpMeta;
107             break;
108         }
109     }
110 
111     return meta;
112 }
113 
timerEvent(QTimerEvent * event)114 void FilterController::timerEvent(QTimerEvent* event)
115 {
116     loadFilterMetadata();
117     killTimer(event->timerId());
118 }
119 
metadataModel()120 MetadataModel* FilterController::metadataModel()
121 {
122     return &m_metadataModel;
123 }
124 
attachedModel()125 AttachedFiltersModel* FilterController::attachedModel()
126 {
127     return &m_attachedModel;
128 }
129 
setProducer(Mlt::Producer * producer)130 void FilterController::setProducer(Mlt::Producer *producer)
131 {
132     m_attachedModel.setProducer(producer);
133     if (producer && producer->is_valid()) {
134         mlt_service_type service_type = producer->type();
135         m_metadataModel.setIsClipProducer(service_type != playlist_type &&
136             (service_type != tractor_type || !producer->get_int(kShotcutXmlProperty)));
137     }
138 }
139 
setCurrentFilter(int attachedIndex,bool isNew)140 void FilterController::setCurrentFilter(int attachedIndex, bool isNew)
141 {
142     if (attachedIndex == m_currentFilterIndex) {
143         return;
144     }
145     m_currentFilterIndex = attachedIndex;
146 
147     // VUIs may instruct MLT filters to not render if they are doing the rendering
148     // theirself, for example, Text: Rich. Component.onDestruction is not firing.
149     if (m_mltFilter) {
150         if (m_mltFilter->get_int("_hide")) {
151             m_mltFilter->clear("_hide");
152             MLT.refreshConsumer();
153         }
154     }
155 
156     QmlMetadata* meta = m_attachedModel.getMetadata(m_currentFilterIndex);
157     QmlFilter* filter = 0;
158     if (meta) {
159         emit currentFilterChanged(nullptr, nullptr, QmlFilter::NoCurrentFilter);
160         m_mltFilter = m_attachedModel.getFilter(m_currentFilterIndex);
161         filter = new QmlFilter(*m_mltFilter, meta);
162         filter->setIsNew(isNew);
163         connect(filter, SIGNAL(changed()), SLOT(onQmlFilterChanged()));
164         connect(filter, SIGNAL(changed(QString)), SLOT(onQmlFilterChanged(const QString&)));
165     }
166 
167     emit currentFilterChanged(filter, meta, m_currentFilterIndex);
168     m_currentFilter.reset(filter);
169 }
170 
onFadeInChanged()171 void FilterController::onFadeInChanged()
172 {
173     if (m_currentFilter) {
174         emit m_currentFilter->changed();
175         emit m_currentFilter->animateInChanged();
176     }
177 }
178 
onFadeOutChanged()179 void FilterController::onFadeOutChanged()
180 {
181     if (m_currentFilter) {
182         emit m_currentFilter->changed();
183         emit m_currentFilter->animateOutChanged();
184     }
185 }
186 
onFilterInChanged(int delta,Mlt::Filter * filter)187 void FilterController::onFilterInChanged(int delta, Mlt::Filter* filter)
188 {
189     if (delta && m_currentFilter && (!filter || m_currentFilter->filter().get_filter() == filter->get_filter())) {
190         emit m_currentFilter->inChanged(delta);
191     }
192 }
193 
onFilterOutChanged(int delta,Mlt::Filter * filter)194 void FilterController::onFilterOutChanged(int delta, Mlt::Filter* filter)
195 {
196     if (delta && m_currentFilter && (!filter || m_currentFilter->filter().get_filter() == filter->get_filter())) {
197         emit m_currentFilter->outChanged(delta);
198     }
199 }
200 
handleAttachedModelChange()201 void FilterController::handleAttachedModelChange()
202 {
203     if (m_currentFilter) {
204         emit m_currentFilter->changed("disable");
205     }
206 }
207 
handleAttachedModelAboutToReset()208 void FilterController::handleAttachedModelAboutToReset()
209 {
210     setCurrentFilter(QmlFilter::NoCurrentFilter);
211 }
212 
handleAttachedRowsRemoved(const QModelIndex &,int first,int)213 void FilterController::handleAttachedRowsRemoved(const QModelIndex&, int first, int)
214 {
215     m_currentFilterIndex = QmlFilter::DeselectCurrentFilter; // Force update
216     setCurrentFilter(qBound(0, first, m_attachedModel.rowCount() - 1));
217 }
218 
handleAttachedRowsInserted(const QModelIndex &,int first,int)219 void FilterController::handleAttachedRowsInserted(const QModelIndex&, int first, int)
220 {
221     m_currentFilterIndex = QmlFilter::DeselectCurrentFilter; // Force update
222     setCurrentFilter(qBound(0, first, m_attachedModel.rowCount() - 1), true);
223 }
224 
handleAttachDuplicateFailed(int index)225 void FilterController::handleAttachDuplicateFailed(int index)
226 {
227     const QmlMetadata* meta = m_attachedModel.getMetadata(index);
228     emit statusChanged(tr("Only one %1 filter is allowed.").arg(meta->name()));
229     setCurrentFilter(index);
230 }
231 
onQmlFilterChanged()232 void FilterController::onQmlFilterChanged()
233 {
234     emit filterChanged(m_mltFilter);
235 }
236 
onQmlFilterChanged(const QString & name)237 void FilterController::onQmlFilterChanged(const QString &name)
238 {
239     if (name == "disable") {
240         QModelIndex index = m_attachedModel.index(m_currentFilterIndex);
241         emit m_attachedModel.dataChanged(index, index, QVector<int>() << Qt::CheckStateRole);
242     }
243 }
244 
removeCurrent()245 void FilterController::removeCurrent()
246 {
247     if (m_currentFilterIndex > QmlFilter::NoCurrentFilter)
248         m_attachedModel.remove(m_currentFilterIndex);
249 }
250 
onProducerChanged()251 void FilterController::onProducerChanged()
252 {
253     emit m_attachedModel.trackTitleChanged();
254 }
255 
addMetadata(QmlMetadata * meta)256 void FilterController::addMetadata(QmlMetadata* meta)
257 {
258     m_metadataModel.add(meta);
259 }
260