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