clipcreator.cpp 15.7 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/***************************************************************************
 *   Copyright (C) 2017 by Nicolas Carion                                  *
 *   This file is part of Kdenlive. See www.kdenlive.org.                  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) version 3 or any later version accepted by the       *
 *   membership of KDE e.V. (or its successor approved  by the membership  *
 *   of KDE e.V.), which shall act as a proxy defined in Section 14 of     *
 *   version 3 of the license.                                             *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 ***************************************************************************/

#include "clipcreator.hpp"
23
#include "bin/bin.h"
24
#include "core.h"
25
26
27
28
#include "doc/kdenlivedoc.h"
#include "kdenlivesettings.h"
#include "klocalizedstring.h"
#include "macros.hpp"
29
#include "mainwindow.h"
30
#include "projectitemmodel.h"
31
32
#include "titler/titledocument.h"
#include "utils/devices.hpp"
33
#include "xml/xml.hpp"
34
35
#include <KMessageBox>
#include <QApplication>
36
#include <QDomDocument>
37
#include <QMimeDatabase>
38
#include <QProgressDialog>
Nicolas Carion's avatar
Nicolas Carion committed
39
#include <utility>
40

41
namespace {
42
QDomElement createProducer(QDomDocument &xml, ClipType::ProducerType type, const QString &resource, const QString &name, int duration, const QString &service)
43
44
45
{
    QDomElement prod = xml.createElement(QStringLiteral("producer"));
    xml.appendChild(prod);
46
    prod.setAttribute(QStringLiteral("type"), (int)type);
47
48
    prod.setAttribute(QStringLiteral("in"), QStringLiteral("0"));
    prod.setAttribute(QStringLiteral("length"), duration);
49
50
51
52
53
54
55
56
    std::unordered_map<QString, QString> properties;
    properties[QStringLiteral("resource")] = resource;
    if (!name.isEmpty()) {
        properties[QStringLiteral("kdenlive:clipname")] = name;
    }
    if (!service.isEmpty()) {
        properties[QStringLiteral("mlt_service")] = service;
    }
57
    Xml::addXmlProperties(prod, properties);
58
59
60
61
62
    return prod;
}

} // namespace

63
QString ClipCreator::createTitleClip(const std::unordered_map<QString, QString> &properties, int duration, const QString &name, const QString &parentFolder,
Nicolas Carion's avatar
Nicolas Carion committed
64
                                     const std::shared_ptr<ProjectItemModel> &model)
65
66
67
68
69
70
71
72
73
74
{
    QDomDocument xml;
    auto prod = createProducer(xml, ClipType::Text, QString(), name, duration, QStringLiteral("kdenlivetitle"));
    Xml::addXmlProperties(prod, properties);

    QString id;
    bool res = model->requestAddBinClip(id, xml.documentElement(), parentFolder, i18n("Create title clip"));
    return res ? id : QStringLiteral("-1");
}

75
QString ClipCreator::createColorClip(const QString &color, int duration, const QString &name, const QString &parentFolder,
Nicolas Carion's avatar
Nicolas Carion committed
76
                                     const std::shared_ptr<ProjectItemModel> &model)
77
78
79
{
    QDomDocument xml;

80
    auto prod = createProducer(xml, ClipType::Color, color, name, duration, QStringLiteral("color"));
81
82

    QString id;
83
84
    bool res = model->requestAddBinClip(id, xml.documentElement(), parentFolder, i18n("Create color clip"));
    return res ? id : QStringLiteral("-1");
85
86
}

87
QDomDocument ClipCreator::getXmlFromUrl(const QString &path)
88
89
90
{
    QDomDocument xml;
    QMimeDatabase db;
91
    QMimeType type = db.mimeTypeForUrl(QUrl::fromLocalFile(path));
92
93

    QDomElement prod;
94
    qDebug()<<"=== GOT DROPPPED MIME: "<<type.name();
95
96
    if (type.name().startsWith(QLatin1String("image/"))) {
        int duration = pCore->currentDoc()->getFramePos(KdenliveSettings::image_duration());
97
        prod = createProducer(xml, ClipType::Image, path, QString(), duration, QString());
98
99
100
    } else if (type.inherits(QStringLiteral("application/x-kdenlivetitle"))) {
        // opening a title file
        QDomDocument txtdoc(QStringLiteral("titledocument"));
101
        QFile txtfile(path);
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
        if (txtfile.open(QIODevice::ReadOnly) && txtdoc.setContent(&txtfile)) {
            txtfile.close();
            // extract embedded images
            QDomNodeList items = txtdoc.elementsByTagName(QStringLiteral("content"));
            for (int j = 0; j < items.count(); ++j) {
                QDomElement content = items.item(j).toElement();
                if (content.hasAttribute(QStringLiteral("base64"))) {
                    QString titlesFolder = pCore->currentDoc()->projectDataFolder() + QStringLiteral("/titles/");
                    QString imgPath = TitleDocument::extractBase64Image(titlesFolder, content.attribute(QStringLiteral("base64")));
                    if (!imgPath.isEmpty()) {
                        content.setAttribute(QStringLiteral("url"), imgPath);
                        content.removeAttribute(QStringLiteral("base64"));
                    }
                }
            }
117
            prod = createProducer(xml, ClipType::Text, path, QString(), -1, QString());
118
119
120
121
            QString titleData = txtdoc.toString();
            prod.setAttribute(QStringLiteral("xmldata"), titleData);
        } else {
            txtfile.close();
122
            return QDomDocument();
123
124
125
126
127
128
129
130
131
        }
    } else {
        // it is a "normal" file, just use a producer
        prod = xml.createElement(QStringLiteral("producer"));
        xml.appendChild(prod);
        QMap<QString, QString> properties;
        properties.insert(QStringLiteral("resource"), path);
        Xml::addXmlProperties(prod, properties);
    }
132
133
134
    if (pCore->bin()->isEmpty() && (KdenliveSettings::default_profile().isEmpty() || KdenliveSettings::checkfirstprojectclip())) {
        prod.setAttribute(QStringLiteral("_checkProfile"), 1);
    }
135
136
    return xml;
}
137

138
139
140
QString ClipCreator::createClipFromFile(const QString &path, const QString &parentFolder, const std::shared_ptr<ProjectItemModel> &model, Fun &undo, Fun &redo,
                                        const std::function<void(const QString &)> &readyCallBack)
{
141
    qDebug() << "/////////// createClipFromFile" << path << parentFolder;
142
143
144
145
    QDomDocument xml = getXmlFromUrl(path);
    if (xml.isNull()) {
        return QStringLiteral("-1");
    }
Nicolas Carion's avatar
Nicolas Carion committed
146
    qDebug() << "/////////// final xml" << xml.toString();
147
    QString id;
148
    bool res = model->requestAddBinClip(id, xml.documentElement(), parentFolder, undo, redo, readyCallBack);
149
150
151
152
153
154
155
    return res ? id : QStringLiteral("-1");
}

bool ClipCreator::createClipFromFile(const QString &path, const QString &parentFolder, std::shared_ptr<ProjectItemModel> model)
{
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };
Nicolas Carion's avatar
Nicolas Carion committed
156
    auto id = ClipCreator::createClipFromFile(path, parentFolder, std::move(model), undo, redo);
157
158
159
160
161
162
163
164
    bool ok = (id != QStringLiteral("-1"));
    if (ok) {
        pCore->pushUndo(undo, redo, i18n("Add clip"));
    }
    return ok;
}

QString ClipCreator::createSlideshowClip(const QString &path, int duration, const QString &name, const QString &parentFolder,
Nicolas Carion's avatar
Nicolas Carion committed
165
                                         const std::unordered_map<QString, QString> &properties, const std::shared_ptr<ProjectItemModel> &model)
166
167
168
{
    QDomDocument xml;

169
    auto prod = createProducer(xml, ClipType::SlideShow, path, name, duration, QString());
170
171
172
173
174
175
176
177
    Xml::addXmlProperties(prod, properties);

    QString id;
    bool res = model->requestAddBinClip(id, xml.documentElement(), parentFolder, i18n("Create slideshow clip"));
    return res ? id : QStringLiteral("-1");
}

QString ClipCreator::createTitleTemplate(const QString &path, const QString &text, const QString &name, const QString &parentFolder,
Nicolas Carion's avatar
Nicolas Carion committed
178
                                         const std::shared_ptr<ProjectItemModel> &model)
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
{
    QDomDocument xml;

    // We try to retrieve duration for template
    int duration = 0;
    QDomDocument titledoc;
    QFile txtfile(path);
    if (txtfile.open(QIODevice::ReadOnly) && titledoc.setContent(&txtfile)) {
        if (titledoc.documentElement().hasAttribute(QStringLiteral("duration"))) {
            duration = titledoc.documentElement().attribute(QStringLiteral("duration")).toInt();
        } else {
            // keep some time for backwards compatibility - 26/12/12
            duration = titledoc.documentElement().attribute(QStringLiteral("out")).toInt();
        }
    }
    txtfile.close();

    // Duration not found, we fall-back to defaults
    if (duration == 0) {
        duration = pCore->currentDoc()->getFramePos(KdenliveSettings::title_duration());
    }
200
    auto prod = createProducer(xml, ClipType::TextTemplate, path, name, duration, QString());
201
202
203
204
205
206
207
208
209
    if (!text.isEmpty()) {
        prod.setAttribute(QStringLiteral("templatetext"), text);
    }

    QString id;
    bool res = model->requestAddBinClip(id, xml.documentElement(), parentFolder, i18n("Create title template"));
    return res ? id : QStringLiteral("-1");
}

210
const QString ClipCreator::createClipsFromList(const QList<QUrl> &list, bool checkRemovable, const QString &parentFolder, const std::shared_ptr<ProjectItemModel> &model,
211
                                      Fun &undo, Fun &redo, bool topLevel)
212
{
213
    QString createdItem;
214
215
216
217
218
219
220
221
222
223
224
    QScopedPointer<QProgressDialog> progressDialog;
    if (topLevel) {
        progressDialog.reset(new QProgressDialog(pCore->window()));
        progressDialog->setWindowTitle(i18n("Loading clips"));
        progressDialog->setCancelButton(nullptr);
        progressDialog->setLabelText(i18n("Importing bin clips..."));
        progressDialog->setMaximum(0);
        progressDialog->show();
        progressDialog->repaint();
        qApp->processEvents();
    }
Nicolas Carion's avatar
Nicolas Carion committed
225
    qDebug() << "/////////// creatclipsfromlist" << list << checkRemovable << parentFolder;
226
227
228
    bool created = false;
    QMimeDatabase db;
    for (const QUrl &file : list) {
229
230
231
        if (!QFile::exists(file.toLocalFile())) {
            continue;
        }
232
233
        QMimeType mType = db.mimeTypeForUrl(file);
        if (mType.inherits(QLatin1String("inode/directory"))) {
234
235
            // user dropped a folder, import its files
            QDir dir(file.path());
236
237
238
            QString folderId;
            Fun local_undo = []() { return true; };
            Fun local_redo = []() { return true; };
239
            QStringList result = dir.entryList(QDir::Files);
240
            QStringList subfolders = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
241
242
            QList<QUrl> folderFiles;
            for (const QString &path : result) {
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
                QUrl url = QUrl::fromLocalFile(dir.absoluteFilePath(path));
                // Check file is of a supported type
                mType = db.mimeTypeForUrl(url);
                QString mimeAliases = mType.name();
                bool isValid = mimeAliases.contains(QLatin1String("video/"));
                if (!isValid) {
                    isValid = mimeAliases.contains(QLatin1String("audio/"));
                }
                if (!isValid) {
                    isValid = mimeAliases.contains(QLatin1String("image/"));
                }
                if (!isValid && (mType.inherits(QLatin1String("video/mlt-playlist")) || mType.inherits(QLatin1String("application/x-kdenlivetitle")))) {
                    isValid = true;
                }
                if (isValid) {
                    folderFiles.append(url);
                }
260
            }
261
262
            if (folderFiles.isEmpty()) {
                QList<QUrl> sublist;
263
                for (const QString &sub : subfolders) {
264
265
266
267
268
269
                    QUrl url = QUrl::fromLocalFile(dir.absoluteFilePath(sub));
                    if (!list.contains(url)) {
                        sublist << url;
                    }
                }
                if (!sublist.isEmpty()) {
270
271
272
273
274
275
                    // Create main folder
                    bool folderCreated = pCore->projectItemModel()->requestAddFolder(folderId, dir.dirName(), parentFolder, local_undo, local_redo);
                    if (!folderCreated) {
                        continue;
                    }
                    createdItem = folderId;
276
                    // load subfolders
277
278
279
280
                    const QString clipId = createClipsFromList(sublist, checkRemovable, folderId, model, undo, redo, false);
                    if (createdItem.isEmpty() && clipId != QLatin1String("-1")) {
                        createdItem = clipId;
                    }
281
282
                }
            } else {
283
284
285
286
287
288
                // Create main folder
                bool folderCreated = pCore->projectItemModel()->requestAddFolder(folderId, dir.dirName(), parentFolder, local_undo, local_redo);
                if (!folderCreated) {
                    continue;
                }
                createdItem = folderId;
289
290
                const QString clipId = createClipsFromList(folderFiles, checkRemovable, folderId, model, local_undo, local_redo, false);
                if (clipId.isEmpty() || clipId == QLatin1String("-1")) {
291
292
                    local_undo();
                } else {
293
294
295
                    if (createdItem.isEmpty()) {
                        createdItem = clipId;
                    }
296
297
298
299
300
301
302
303
                    UPDATE_UNDO_REDO_NOLOCK(local_redo, local_undo, undo, redo)
                }
                // Check subfolders
                QList<QUrl> sublist;
                for (const QString &sub : subfolders) {
                    QUrl url = QUrl::fromLocalFile(dir.absoluteFilePath(sub));
                    if (!list.contains(url)) {
                        sublist << url;
304
                    }
305
                }
306
307
308
309
                if (!sublist.isEmpty()) {
                    // load subfolders
                    createClipsFromList(sublist, checkRemovable, folderId, model, undo, redo, false);
                }
310
            }
311
312
313
314
315
316
317
318
319
        } else {
            // file is not a directory
            if (checkRemovable && isOnRemovableDevice(file) && !isOnRemovableDevice(pCore->currentDoc()->projectDataFolder())) {
                int answer = KMessageBox::warningContinueCancel(
                    QApplication::activeWindow(),
                    i18n("Clip <b>%1</b><br /> is on a removable device, will not be available when device is unplugged or mounted at a different position. You "
                         "may want to copy it first to your hard-drive. Would you like to add it anyways?",
                         file.path()),
                    i18n("Removable device"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("confirm_removable_device"));
320

321
322
                if (answer == KMessageBox::Cancel) continue;
            }
323
324
325
326
            const QString clipId = ClipCreator::createClipFromFile(file.toLocalFile(), parentFolder, model, undo, redo);
            if (createdItem.isEmpty() && clipId != QLatin1String("-1")) {
                createdItem = clipId;
            }
327
328
        }
    }
329
    qDebug() << "/////////// creatclipsfromlist return" << created;
330
    return createdItem == QLatin1String("-1") ? QString() : createdItem;
331
332
}

333
const QString ClipCreator::createClipsFromList(const QList<QUrl> &list, bool checkRemovable, const QString &parentFolder, std::shared_ptr<ProjectItemModel> model)
334
335
336
{
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };
337
338
    const QString id = ClipCreator::createClipsFromList(list, checkRemovable, parentFolder, std::move(model), undo, redo);
    if (!id.isEmpty()) {
339
340
        pCore->pushUndo(undo, redo, i18np("Add clip", "Add clips", list.size()));
    }
341
    return id;
342
}