Krita Source Code Documentation
Loading...
Searching...
No Matches
recorder_export_config.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2020 Dmitrii Utkin <loentar@gmail.com>
3 *
4 * SPDX-License-Identifier: LGPL-2.1-only
5 */
6
8
9#include <kis_config.h>
10
11#include <QString>
12#include <QDir>
13#include <QRegularExpression>
14
15#ifdef Q_OS_ANDROID
16#include <QJsonDocument>
17#endif
18
19namespace
20{
21const QString keyAnimationExport = "ANIMATION_EXPORT";
22#ifndef Q_OS_ANDROID
23const QString keyFfmpegPath = "ffmpeg_path";
24const QString keyVideoDirectory = "recorder_export/videodirectory";
25#endif
26const QString keyInputFps = "recorder_export/inputfps";
27const QString keyFps = "recorder_export/fps";
28const QString keyResultPreview="recorder_export/resultpreview";
29const QString keyFirstFrameSec = "recorder_export/firstframesec";
30const QString keyExtendResult = "recorder_export/extendresult";
31const QString keyLastFrameSec = "recorder_export/lastframesec";
32const QString keyResize = "recorder_export/resize";
33const QString keySize = "recorder_export/size";
34const QString keyLockRatio = "recorder_export/lockratio";
35const QString keyLockFps = "recorder_export/lockfps";
36#ifdef Q_OS_ANDROID
37const QString keySelectedFormat = "recorder_export/selectedformat";
38const QString keyFormatPreferences = "recorder_export/formatpreferences";
39#else
40const QString keyProfileIndex = "recorder_export/profileIndex";
41const QString keyProfiles = "recorder_export/profiles";
42const QString keyEditedProfiles = "recorder_export/editedprofiles";
43
44const QString profilePrefix = "-reinit_filter 0\n-framerate $IN_FPS\n-i \"$INPUT_DIR%07d.$EXT\"\n-framerate $OUT_FPS\n-start_number $FRAMES-1\n-i \"$INPUT_DIR%07d.$EXT\"\n";
45
46const QList<RecorderProfile> defaultProfiles = {
47 { "MP4 x264", "mp4", profilePrefix % "-filter_complex \"\n"
48 /* Filter documentation https://trac.ffmpeg.org/wiki/FilteringGuide
49 * Inside the complex filter command each line is a different filter that is applied to an input stream.
50 * The input stream to transform is specified at the start of each line in the format '[$STREAM_NAME]' (ex. [p1], [v1], [v2]).
51 * Immediately following the input stream name will be the filter to apply (ex: trim, scale, loop)
52 * Depending on the filter it may or may have various options set
53 * After all options for the filter a name can be specified that will correspond to the output stream of the filter. If no name is specified it will be used as the final result for the output file.
54 */
55 " [1]scale='min($WIDTH, iw*($HEIGHT/ih))':'min($HEIGHT, ih*($WIDTH/iw))'[hold1];\n"
56 " [hold1]pad=$WIDTH:$HEIGHT:(ow-iw)/2:(oh-ih)/2[hold2];\n"
57 " [hold2]setsar=1:1[hold3];\n"
58 " [hold3]split=3[preview1][transition1][end1];\n"
59
60 " [preview1]tpad=stop_mode=clone:stop_duration=$FIRST_FRAME_SEC[preview2];\n"
61 " [preview2]setpts=PTS-STARTPTS[preview3];\n"
62
63 " [transition1]tpad=stop_mode=clone:stop_duration=$TRANSITION_LENGTH[transition2];\n"
64 " [transition2]setpts=PTS-STARTPTS[transition3];\n"
65 " [transition3]framerate=$OUT_FPS[transition4];\n"
66
67 " [0]fps=$OUT_FPS[main1];\n"
68 " [main1]scale='min($WIDTH, iw*($HEIGHT/ih))':'min($HEIGHT, ih*($WIDTH/iw))':eval=frame[main2];\n"
69 " [main2]pad=$WIDTH:$HEIGHT:(ow-iw)/2:(oh-ih)/2:eval=frame[main3];\n"
70 " [main3]setsar=1:1[main4];\n"
71 " [transition4][main4]xfade=transition=smoothright:duration=$TRANSITION_LENGTH:offset=0[main5];\n"
72
73 " [end1]tpad=stop_mode=clone:stop_duration=$LAST_FRAME_SEC[end2];\n"
74 " [end2]setpts=PTS-STARTPTS[end3];\n"
75
76 " [preview3][main5][end3]concat=n=3:v=1:a=0[final1];\n"
77 " [final1]setpts=PTS-STARTPTS\n"
78
79 "\"\n-stats\n-loglevel error\n-c:v $H264_ENCODER\n-r $OUT_FPS\n-pix_fmt yuv420p" },
80 { "GIF", "gif", profilePrefix % "-filter_complex \"\n"
81 " [1]scale='min($WIDTH, iw*($HEIGHT/ih))':'min($HEIGHT, ih*($WIDTH/iw))'[hold1];\n"
82 " [hold1]pad=$WIDTH:$HEIGHT:(ow-iw)/2:(oh-ih)/2[hold2];\n"
83 " [hold2]setsar=1:1[hold3];\n"
84 " [hold3]split=3[preview1][transition1][end1];\n"
85
86 " [preview1]tpad=stop_mode=clone:stop_duration=$FIRST_FRAME_SEC[preview2];\n"
87 " [preview2]setpts=PTS-STARTPTS[preview3];\n"
88
89 " [transition1]tpad=stop_mode=clone:stop_duration=$TRANSITION_LENGTH[transition2];\n"
90 " [transition2]setpts=PTS-STARTPTS[transition3];\n"
91 " [transition3]framerate=$OUT_FPS[transition4];\n"
92
93 " [0]fps=$OUT_FPS[main1];\n"
94 " [main1]scale='min($WIDTH, iw*($HEIGHT/ih))':'min($HEIGHT, ih*($WIDTH/iw))':eval=frame[main2];\n"
95 " [main2]pad=$WIDTH:$HEIGHT:(ow-iw)/2:(oh-ih)/2:eval=frame[main3];\n"
96 " [main3]setsar=1:1[main4];\n"
97 " [transition4][main4]xfade=transition=smoothright:duration=$TRANSITION_LENGTH:offset=0[main5];\n"
98
99 " [end1]tpad=stop_mode=clone:stop_duration=$LAST_FRAME_SEC[end2];\n"
100 " [end2]setpts=PTS-STARTPTS[end3];\n"
101
102 " [preview3][main5][end3]concat=n=3:v=1:a=0[final1];\n"
103 " [final1]setpts=PTS-STARTPTS[final2];\n"
104 " [final2]split[final3][final4];\n"
105 " [final3]palettegen[palettegen];\n"
106 " [final4][palettegen]paletteuse\n"
107 "\"\n-stats\n-loglevel error\n-r $OUT_FPS" },
108 { "Matroska", "mkv", profilePrefix % "-filter_complex \"\n"
109 " [1]scale='min($WIDTH, iw*($HEIGHT/ih))':'min($HEIGHT, ih*($WIDTH/iw))'[hold1];\n"
110 " [hold1]pad=$WIDTH:$HEIGHT:(ow-iw)/2:(oh-ih)/2[hold2];\n"
111 " [hold2]setsar=1:1[hold3];\n"
112 " [hold3]split=3[preview1][transition1][end1];\n"
113
114 " [preview1]tpad=stop_mode=clone:stop_duration=$FIRST_FRAME_SEC[preview2];\n"
115 " [preview2]setpts=PTS-STARTPTS[preview3];\n"
116
117 " [transition1]tpad=stop_mode=clone:stop_duration=$TRANSITION_LENGTH[transition2];\n"
118 " [transition2]setpts=PTS-STARTPTS[transition3];\n"
119 " [transition3]framerate=$OUT_FPS[transition4];\n"
120
121 " [0]fps=$OUT_FPS[main1];\n"
122 " [main1]scale='min($WIDTH, iw*($HEIGHT/ih))':'min($HEIGHT, ih*($WIDTH/iw))':eval=frame[main2];\n"
123 " [main2]pad=$WIDTH:$HEIGHT:(ow-iw)/2:(oh-ih)/2:eval=frame[main3];\n"
124 " [main3]setsar=1:1[main4];\n"
125 " [transition4][main4]xfade=transition=smoothright:duration=$TRANSITION_LENGTH:offset=0[main5];\n"
126
127 " [end1]tpad=stop_mode=clone:stop_duration=$LAST_FRAME_SEC[end2];\n"
128 " [end2]setpts=PTS-STARTPTS[end3];\n"
129
130 " [preview3][main5][end3]concat=n=3:v=1:a=0[final1];\n"
131 " [final1]setpts=PTS-STARTPTS\n"
132 "\"\n-stats\n-loglevel error\n-pix_fmt yuv420p\n-r $OUT_FPS" },
133 { "WebM", "webm", profilePrefix % "-filter_complex \"\n"
134 " [1]scale='min($WIDTH, iw*($HEIGHT/ih))':'min($HEIGHT, ih*($WIDTH/iw))'[hold1];\n"
135 " [hold1]pad=$WIDTH:$HEIGHT:(ow-iw)/2:(oh-ih)/2[hold2];\n"
136 " [hold2]setsar=1:1[hold3];\n"
137 " [hold3]split=3[preview1][transition1][end1];\n"
138
139 " [preview1]tpad=stop_mode=clone:stop_duration=$FIRST_FRAME_SEC[preview2];\n"
140 " [preview2]setpts=PTS-STARTPTS[preview3];\n"
141
142 " [transition1]tpad=stop_mode=clone:stop_duration=$TRANSITION_LENGTH[transition2];\n"
143 " [transition2]setpts=PTS-STARTPTS[transition3];\n"
144 " [transition3]framerate=$OUT_FPS[transition4];\n"
145
146 " [0]fps=$OUT_FPS[main1];\n"
147 " [main1]scale='min($WIDTH, iw*($HEIGHT/ih))':'min($HEIGHT, ih*($WIDTH/iw))':eval=frame[main2];\n"
148 " [main2]pad=$WIDTH:$HEIGHT:(ow-iw)/2:(oh-ih)/2:eval=frame[main3];\n"
149 " [main3]setsar=1:1[main4];\n"
150 " [transition4][main4]xfade=transition=smoothright:duration=$TRANSITION_LENGTH:offset=0[main5];\n"
151
152 " [end1]tpad=stop_mode=clone:stop_duration=$LAST_FRAME_SEC[end2];\n"
153 " [end2]setpts=PTS-STARTPTS[end3];\n"
154
155 " [preview3][main5][end3]concat=n=3:v=1:a=0[final1];\n"
156 " [final1]setpts=PTS-STARTPTS\n"
157 "\"\n-stats\n-loglevel error\n-r $OUT_FPS" },
158 { "MP4 x264 (Flash Effect)", "mp4", profilePrefix % "-filter_complex \"\n"
159 " [1]scale='min($WIDTH, iw*($HEIGHT/ih))':'min($HEIGHT, ih*($WIDTH/iw))'[hold1];\n"
160 " [hold1]pad=$WIDTH:$HEIGHT:(ow-iw)/2:(oh-ih)/2[hold2];\n"
161 " [hold2]setsar=1:1[hold3];\n"
162 " [hold3]split=3[preview1][fade1][end1];\n"
163
164 " [preview1]tpad=stop_mode=clone:stop_duration=$FIRST_FRAME_SEC[preview2];\n"
165 " [preview2]setpts=PTS-STARTPTS[preview3];\n"
166
167 " [fade1]tpad=stop_mode=clone:stop_duration=$TRANSITION_LENGTH[fade2];\n"
168 " [fade2]setpts=PTS-STARTPTS[fade3];\n"
169 " [fade3]fade=out:duration=$TRANSITION_LENGTH:color=white[fade4];\n"
170
171 " [0]fps=$OUT_FPS[main1];\n"
172 " [main1]scale='min($WIDTH, iw*($HEIGHT/ih))':'min($HEIGHT, ih*($WIDTH/iw))':eval=frame[main2];\n"
173 " [main2]pad=$WIDTH:$HEIGHT:(ow-iw)/2:(oh-ih)/2:eval=frame[main3];\n"
174 " [main3]setsar=1:1[main4];\n"
175 " [fade4][main4]concat[main5];\n"
176
177 " [end1]tpad=stop_mode=clone:stop_duration=$LAST_FRAME_SEC[end2];\n"
178 " [end2]setpts=PTS-STARTPTS[end3];\n"
179
180 " [preview3][main5][end3]concat=n=3:v=1:a=0[final1];\n"
181 " [final1]setpts=PTS-STARTPTS\n"
182 "\"\n-stats\n-loglevel error\n-c:v $H264_ENCODER\n-r $OUT_FPS\n-pix_fmt yuv420p" },
183 { "Custom1", "editme", profilePrefix % "-filter_complex \"loop=$LAST_FRAME_SEC:size=1:start=$FRAMES,scale=$WIDTH:$HEIGHT\"\n-r $OUT_FPS" },
184 { "Custom2", "editme", profilePrefix % "-filter_complex \"loop=$LAST_FRAME_SEC:size=1:start=$FRAMES,scale=$WIDTH:$HEIGHT\"\n-r $OUT_FPS" },
185 { "Custom3", "editme", profilePrefix % "-filter_complex \"loop=$LAST_FRAME_SEC:size=1:start=$FRAMES,scale=$WIDTH:$HEIGHT\"\n-r $OUT_FPS" },
186 { "Custom4", "editme", profilePrefix % "-filter_complex \"loop=$LAST_FRAME_SEC:size=1:start=$FRAMES,scale=$WIDTH:$HEIGHT\"\n-r $OUT_FPS" }
187};
188#endif
189}
190
192 : config(new KisConfig(readOnly))
193{
194}
195
200
202{
203 settings->inputFps = inputFps();
204 settings->fps = fps();
205 settings->resultPreview = resultPreview();
206 settings->firstFrameSec = firstFrameSec();
207 settings->extendResult = extendResult();
208 settings->lastFrameSec = lastFrameSec();
209 settings->resize = resize();
210 settings->size = size();
211 settings->lockRatio = lockRatio();
212#ifdef Q_OS_ANDROID
213 settings->selectedFormat = selectedFormat();
214 settings->formatPreferences = formatPreferences();
215#else
216 settings->ffmpegPath = ffmpegPath();
217 settings->profiles = profiles();
218 settings->defaultProfiles = defaultProfiles();
219 settings->profileIndex = profileIndex();
220 settings->videoDirectory = videoDirectory();
221#endif
222 if (loadLockFps)
223 settings->lockFps = lockFps();
224}
225
227{
228 return config->readEntry(keyInputFps, 30);
229}
230
232{
233 config->writeEntry(keyInputFps, value);
234}
235
236
238{
239 return config->readEntry(keyFps, 30);
240}
241
243{
244 config->writeEntry(keyFps, value);
245}
246
248{
249 return config->readEntry(keyResultPreview, true);
250}
251
253{
254 config->writeEntry(keyResultPreview, value);
255}
256
258{
259 return config->readEntry(keyFirstFrameSec, 2);
260}
261
263{
264 config->writeEntry(keyFirstFrameSec, value);
265}
266
267
269{
270 return config->readEntry(keyExtendResult, true);
271}
272
274{
275 config->writeEntry(keyExtendResult, value);
276}
277
279{
280 return config->readEntry(keyLastFrameSec, 5);
281}
282
284{
285 config->writeEntry(keyLastFrameSec, value);
286}
287
289{
290 return config->readEntry(keyResize, false);
291}
292
294{
295 config->writeEntry(keyResize, value);
296}
297
298
300{
301 return config->readEntry(keySize, QSize(1024, 1024));
302}
303
305{
306 config->writeEntry(keySize, value);
307}
308
309
311{
312 return config->readEntry(keyLockRatio, false);
313}
314
316{
317 config->writeEntry(keyLockRatio, value);
318}
319
321{
322 return config->readEntry(keyLockFps, false);
323}
324
326{
327 config->writeEntry(keyLockFps, value);
328}
329
330#ifdef Q_OS_ANDROID
331QString RecorderExportConfig::selectedFormat() const
332{
333 return config->readEntry(keySelectedFormat, QString());
334}
335
336void RecorderExportConfig::setSelectedFormat(const QString &value)
337{
338 config->writeEntry(keySelectedFormat, value);
339}
340
341QVariantMap RecorderExportConfig::formatPreferences() const
342{
343 QByteArray bytes = config->readEntry(keyFormatPreferences, QByteArray());
344 if (!bytes.isEmpty()) {
345 QJsonDocument doc = QJsonDocument::fromJson(bytes);
346 if (doc.isObject()) {
347 return doc.toVariant().toMap();
348 }
349 }
350 return QVariantMap();
351}
352
353void RecorderExportConfig::setFormatPreferences(const QVariantMap &value)
354{
355 config->writeEntry(keyFormatPreferences, QJsonDocument::fromVariant(value).toJson(QJsonDocument::Compact));
356}
357#else
359{
360 return config->readEntry(keyProfileIndex, 0);
361}
362
364{
365 config->writeEntry(keyProfileIndex, value);
366}
367
369{
370 const QString &profilesStr = config->readEntry(keyProfiles, QString());
371 if (profilesStr.isEmpty())
372 return ::defaultProfiles;
373
374 const QSet<int> &editedIndexes = editedProfilesIndexes();
376 const QStringList &profilesList = profilesStr.split("\n");
377 int index = 0;
378 for (const QString &profileStr : profilesList) {
379 // take saved profile in case it's edited.. or something bad happen
380 if (editedIndexes.contains(index) || index >= ::defaultProfiles.size()) {
381 const QStringList& profileList = profileStr.split("|");
382 if (profileList.size() == 3) {
383 profiles.append({profileList[0], profileList[1], QString(profileList[2]).replace("\\n", "\n")});
384 }
385 } else {
386 // use default profile to make it possible to upgrade
387 profiles.append(::defaultProfiles[index]);
388 }
389
390 ++index;
391 }
392 return profiles;
393}
394
396{
397 Q_ASSERT(value.size() == ::defaultProfiles.size());
398
399 const QSet<int> &savedEditedProfilesIndexes = editedProfilesIndexes();
400 QSet<int> editedProfilesIndexes = savedEditedProfilesIndexes;
401 QString outValue;
402 const QRegularExpression cleanUp("[\n|]");
403 int index = 0;
404 for (const RecorderProfile &profile : value) {
405 const RecorderProfile &defaultProfile = ::defaultProfiles[index];
406 if (profile != defaultProfile) {
407 editedProfilesIndexes.insert(index);
408 } else {
409 editedProfilesIndexes.remove(index);
410 }
411
412 outValue += QString(profile.name).replace(cleanUp, " ") % "|"
413 % QString(profile.extension).replace(cleanUp, " ") % "|"
414 % QString(profile.arguments).replace("\n", "\\n").replace("|", " ") % "\n";
415
416 ++index;
417 }
418
419 config->writeEntry(keyProfiles, outValue);
420 if (savedEditedProfilesIndexes != editedProfilesIndexes) {
422 }
423}
424
425
427{
428 return ::defaultProfiles;
429}
430
432{
433 const QVariantList &readValue = config->readEntry(keyEditedProfiles, QVariantList());
434 QSet<int> result;
435 for (const QVariant &item : readValue) {
436 result.insert(item.toInt());
437 }
438 return result;
439}
440
442{
443 QVariantList writeValue;
444 for (int index : value) {
445 writeValue.append(index);
446 }
447 config->writeEntry(keyEditedProfiles, writeValue);
448}
449
450
452{
453 return KisConfig(true).ffmpegLocation();
454}
455
457{
458 KisConfig cfg(false);
460}
461
462
464{
465 return config->readEntry(keyVideoDirectory, QDir::homePath());
466}
467
469{
470 config->writeEntry(keyVideoDirectory, value);
471}
472
473bool operator==(const RecorderProfile &left, const RecorderProfile &right)
474{
475 return left.arguments == right.arguments
476 && left.name == right.name
477 && left.extension == right.extension;
478}
479
480bool operator!=(const RecorderProfile &left, const RecorderProfile &right)
481{
482 return !(left == right);
483}
484#endif
float value(const T *src, size_t ch)
void setFFMpegLocation(const QString &value)
void writeEntry(const QString &name, const T &value)
Definition kis_config.h:865
QString ffmpegLocation(bool defaultValue=false) const
T readEntry(const QString &name, const T &defaultValue=T())
Definition kis_config.h:875
void setProfiles(const QList< RecorderProfile > &value)
void setVideoDirectory(const QString &value)
void loadConfiguration(RecorderExportSettings *settings, bool loadLockFps=true) const
void setFfmpegPath(const QString &value)
QList< RecorderProfile > defaultProfiles() const
QSet< int > editedProfilesIndexes() const
QList< RecorderProfile > profiles() const
void setSize(const QSize &value)
void setEditedProfilesIndexes(const QSet< int > &value)
bool operator!=(const RecorderProfile &left, const RecorderProfile &right)
bool operator==(const RecorderProfile &left, const RecorderProfile &right)
QList< RecorderProfile > profiles
QList< RecorderProfile > defaultProfiles