1 /**
2 * \file scriptinterface.cpp
3 * D-Bus script adaptor.
4 *
5 * \b Project: Kid3
6 * \author Urs Fleisch
7 * \date 20 Dec 2007
8 *
9 * Copyright (C) 2007-2018 Urs Fleisch
10 *
11 * This file is part of Kid3.
12 *
13 * Kid3 is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * Kid3 is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 */
26
27 #include "scriptinterface.h"
28 #ifdef HAVE_QTDBUS
29 #include <QDBusMessage>
30 #include <QDBusConnection>
31 #include <QFileInfo>
32 #include <QCoreApplication>
33 #include <QItemSelectionModel>
34 #include "kid3application.h"
35 #include "taggedfile.h"
36 #include "frametablemodel.h"
37 #include "filefilter.h"
38 #include "pictureframe.h"
39 #include "fileproxymodel.h"
40 #include "modeliterator.h"
41 #include "batchimportconfig.h"
42 #include "batchimportprofile.h"
43 #include "fileconfig.h"
44
45 /**
46 * Constructor.
47 *
48 * @param app parent application
49 */
ScriptInterface(Kid3Application * app)50 ScriptInterface::ScriptInterface(Kid3Application* app)
51 : QDBusAbstractAdaptor(app), m_app(app)
52 {
53 setObjectName(QLatin1String("ScriptInterface"));
54 setAutoRelaySignals(true);
55 }
56
57 /**
58 * Open file or directory.
59 *
60 * @param path path to file or directory
61 *
62 * @return true if ok.
63 */
openDirectory(const QString & path)64 bool ScriptInterface::openDirectory(const QString& path)
65 {
66 return m_app->openDirectory({path}, true);
67 }
68
69 /**
70 * Unload all tags.
71 * The tags of all files which are not modified or selected are freed to
72 * reclaim their memory.
73 */
unloadAllTags()74 void ScriptInterface::unloadAllTags()
75 {
76 m_app->unloadAllTags();
77 }
78
79 /**
80 * Save all modified files.
81 *
82 * @return true if ok,
83 * else the error message is available using getErrorMessage().
84 */
save()85 bool ScriptInterface::save()
86 {
87 QStringList errorFiles = m_app->saveDirectory();
88 if (errorFiles.isEmpty()) {
89 m_errorMsg.clear();
90 return true;
91 } else {
92 m_errorMsg = QLatin1String("Error while writing file:\n") +
93 errorFiles.join(QLatin1String("\n"));
94 return false;
95 }
96 }
97
98 /**
99 * Get a detailed error message provided by some methods.
100 *
101 * @return detailed error message.
102 */
getErrorMessage() const103 QString ScriptInterface::getErrorMessage() const
104 {
105 return m_errorMsg;
106 }
107
108 /**
109 * Revert changes in the selected files.
110 */
revert()111 void ScriptInterface::revert()
112 {
113 m_app->revertFileModifications();
114 }
115
116 /**
117 * Import tags from a file.
118 *
119 * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
120 * @param path path of file, "clipboard" for import from clipboard
121 * @param fmtIdx index of format
122 *
123 * @return true if ok.
124 */
importFromFile(int tagMask,const QString & path,int fmtIdx)125 bool ScriptInterface::importFromFile(int tagMask, const QString& path,
126 int fmtIdx)
127 {
128 return m_app->importTags(Frame::tagVersionCast(tagMask), path, fmtIdx);
129 }
130
131 /**
132 * Import from tags.
133 *
134 * @param tagMask tag mask
135 * @param source format to get source text from tags
136 * @param extraction regular expression with frame names and captures to
137 * extract from source text
138 */
importFromTags(int tagMask,const QString & source,const QString & extraction)139 void ScriptInterface::importFromTags(int tagMask,
140 const QString& source,
141 const QString& extraction)
142 {
143 m_app->importFromTags(Frame::tagVersionCast(tagMask), source, extraction);
144 }
145
146 /**
147 * Import from tags on selected files.
148 *
149 * @param tagMask tag mask
150 * @param source format to get source text from tags
151 * @param extraction regular expression with frame names and captures to
152 * extract from source text
153 *
154 * @return extracted values for "%{__return}(.+)", empty if not used.
155 */
importFromTagsToSelection(int tagMask,const QString & source,const QString & extraction)156 QStringList ScriptInterface::importFromTagsToSelection(int tagMask,
157 const QString& source,
158 const QString& extraction)
159 {
160 return m_app->importFromTagsToSelection(Frame::tagVersionCast(tagMask),
161 source, extraction);
162 }
163
164 /**
165 * Start an automatic batch import.
166 *
167 * @param tagMask tag mask (bit 0 for tag 1, bit 1 for tag 2)
168 * @param profileName name of batch import profile to use
169 *
170 * @return true if profile found.
171 */
batchImport(int tagMask,const QString & profileName)172 bool ScriptInterface::batchImport(int tagMask, const QString& profileName)
173 {
174 return m_app->batchImport(profileName, Frame::tagVersionCast(tagMask));
175 }
176
177 /**
178 * Download album cover art into the picture frame of the selected files.
179 *
180 * @param url URL of picture file or album art resource
181 * @param allFilesInDir true to add the image to all files in the directory
182 */
downloadAlbumArt(const QString & url,bool allFilesInDir)183 void ScriptInterface::downloadAlbumArt(const QString& url, bool allFilesInDir)
184 {
185 m_app->downloadImage(url, allFilesInDir);;
186 }
187
188 /**
189 * Export tags to a file.
190 *
191 * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
192 * @param path path of file, "clipboard" for export to clipboard
193 * @param fmtIdx index of format
194 *
195 * @return true if ok.
196 */
exportToFile(int tagMask,const QString & path,int fmtIdx)197 bool ScriptInterface::exportToFile(int tagMask, const QString& path, int fmtIdx)
198 {
199 return m_app->exportTags(Frame::tagVersionCast(tagMask), path, fmtIdx);
200 }
201
202 /**
203 * Create a playlist.
204 *
205 * @return true if ok.
206 */
createPlaylist()207 bool ScriptInterface::createPlaylist()
208 {
209 return m_app->writePlaylist();
210 }
211
212 /**
213 * Get items of a playlist.
214 * @param path path to playlist file
215 * @return list of absolute paths to playlist items.
216 */
getPlaylistItems(const QString & path)217 QStringList ScriptInterface::getPlaylistItems(const QString& path)
218 {
219 return m_app->getPlaylistItems(path);
220 }
221
222 /**
223 * Set items of a playlist.
224 * @param path path to playlist file
225 * @param items list of absolute paths to playlist items
226 * @return true if ok, false if not all @a items were found and added or
227 * saving failed.
228 */
setPlaylistItems(const QString & path,const QStringList & items)229 bool ScriptInterface::setPlaylistItems(const QString& path,
230 const QStringList& items)
231 {
232 return m_app->setPlaylistItems(path, items);
233 }
234
235 /**
236 * Quit the application.
237 */
quit()238 void ScriptInterface::quit()
239 {
240 selectAll();
241 revert();
242 QCoreApplication::quit();
243 }
244
245 /**
246 * Select all files.
247 */
selectAll()248 void ScriptInterface::selectAll()
249 {
250 m_app->selectAllFiles();
251 }
252
253 /**
254 * Deselect all files.
255 */
deselectAll()256 void ScriptInterface::deselectAll()
257 {
258 m_app->deselectAllFiles();
259 }
260
261 /**
262 * Set the first file as the current file.
263 *
264 * @return true if there is a first file.
265 */
firstFile()266 bool ScriptInterface::firstFile()
267 {
268 return m_app->firstFile(false);
269 }
270
271 /**
272 * Set the previous file as the current file.
273 *
274 * @return true if there is a previous file.
275 */
previousFile()276 bool ScriptInterface::previousFile()
277 {
278 return m_app->previousFile(false);
279 }
280
281 /**
282 * Set the next file as the current file.
283 *
284 * @return true if there is a next file.
285 */
nextFile()286 bool ScriptInterface::nextFile()
287 {
288 return m_app->nextFile(false);
289 }
290
291 /**
292 * Select the first file.
293 *
294 * @return true if there is a first file.
295 */
selectFirstFile()296 bool ScriptInterface::selectFirstFile()
297 {
298 return m_app->firstFile(true);
299 }
300
301 /**
302 * Select the previous file.
303 *
304 * @return true if there is a previous file.
305 */
selectPreviousFile()306 bool ScriptInterface::selectPreviousFile()
307 {
308 return m_app->previousFile(true);
309 }
310
311 /**
312 * Select the next file.
313 *
314 * @return true if there is a next file.
315 */
selectNextFile()316 bool ScriptInterface::selectNextFile()
317 {
318 return m_app->nextFile(true);
319 }
320
321 /**
322 * Select the current file.
323 *
324 * @return true if there is a current file.
325 */
selectCurrentFile()326 bool ScriptInterface::selectCurrentFile()
327 {
328 return m_app->selectCurrentFile(true);
329 }
330
331 /**
332 * Expand the current file item if it is a directory.
333 * A file list item is a directory if getFileName() returns a name with
334 * '/' as the last character.
335 * The directory is fetched but not expanded in the GUI. To expand it in the
336 * GUI, call nextFile() or selectNextFile() after expandDirectory().
337 *
338 * @return true if current file item is a directory.
339 */
expandDirectory()340 bool ScriptInterface::expandDirectory()
341 {
342 QModelIndex index(m_app->getFileSelectionModel()->currentIndex());
343 if (!FileProxyModel::getPathIfIndexOfDir(index).isNull()) {
344 m_app->expandDirectory(index);
345 return true;
346 }
347 return false;
348 }
349
350 /**
351 * Expand the file list.
352 */
expandFileList()353 void ScriptInterface::expandFileList()
354 {
355 m_app->requestExpandFileList();
356 }
357
358 /**
359 * Apply the file name format.
360 */
applyFilenameFormat()361 void ScriptInterface::applyFilenameFormat()
362 {
363 m_app->applyFilenameFormat();
364 }
365
366 /**
367 * Apply the tag format.
368 */
applyTagFormat()369 void ScriptInterface::applyTagFormat()
370 {
371 m_app->applyTagFormat();
372 }
373
374 /**
375 * Apply text encoding.
376 */
applyTextEncoding()377 void ScriptInterface::applyTextEncoding()
378 {
379 m_app->applyTextEncoding();
380 }
381
382 /**
383 * Set the directory name from the tags.
384 *
385 * @param tagMask tag mask (bit 0 for tag 1, bit 1 for tag 2)
386 * @param format directory name format
387 * @param create true to create, false to rename
388 *
389 * @return true if ok,
390 * else the error message is available using getErrorMessage().
391 */
setDirNameFromTag(int tagMask,const QString & format,bool create)392 bool ScriptInterface::setDirNameFromTag(int tagMask, const QString& format,
393 bool create)
394 {
395 connect(m_app, &Kid3Application::renameActionsScheduled,
396 this, &ScriptInterface::onRenameActionsScheduled);
397 if (m_app->renameDirectory(Frame::tagVersionCast(tagMask), format,
398 create)) {
399 return true;
400 } else {
401 disconnect(m_app, &Kid3Application::renameActionsScheduled,
402 this, &ScriptInterface::onRenameActionsScheduled);
403 return false;
404 }
405 }
406
onRenameActionsScheduled()407 void ScriptInterface::onRenameActionsScheduled()
408 {
409 disconnect(m_app, &Kid3Application::renameActionsScheduled,
410 this, &ScriptInterface::onRenameActionsScheduled);
411 m_errorMsg = m_app->performRenameActions();
412 if (!m_errorMsg.isEmpty()) {
413 m_errorMsg = QLatin1String("Error while renaming:\n") + m_errorMsg;
414 }
415 }
416
417 /**
418 * Set subsequent track numbers in the selected files.
419 *
420 * @param tagMask tag mask (bit 0 for tag 1, bit 1 for tag 2)
421 * @param firstTrackNr number to use for first file
422 */
numberTracks(int tagMask,int firstTrackNr)423 void ScriptInterface::numberTracks(int tagMask, int firstTrackNr)
424 {
425 m_app->numberTracks(firstTrackNr, 0, Frame::tagVersionCast(tagMask));
426 }
427
428 /**
429 * Filter the files.
430 *
431 * @param expression filter expression
432 */
filter(const QString & expression)433 void ScriptInterface::filter(const QString& expression)
434 {
435 m_app->applyFilter(expression);
436 }
437
438 /**
439 * Convert ID3v2.3 tags to ID3v2.4.
440 */
convertToId3v24()441 void ScriptInterface::convertToId3v24()
442 {
443 m_app->convertToId3v24();
444 }
445
446 /**
447 * Convert ID3v2.4 tags to ID3v2.3.
448 */
convertToId3v23()449 void ScriptInterface::convertToId3v23()
450 {
451 m_app->convertToId3v23();
452 }
453
454 /**
455 * Get path of directory.
456 *
457 * @return absolute path of directory.
458 */
getDirectoryName()459 QString ScriptInterface::getDirectoryName()
460 {
461 return m_app->getDirPath();
462 }
463
464 /**
465 * Get name of current file.
466 *
467 * @return absolute file name, ends with "/" if it is a directory.
468 */
getFileName()469 QString ScriptInterface::getFileName()
470 {
471 return m_app->getFileNameOfSelectedFile();
472 }
473
474 /**
475 * Set name of selected file.
476 * The file will be renamed when the directory is saved.
477 *
478 * @param name file name.
479 */
setFileName(const QString & name)480 void ScriptInterface::setFileName(const QString& name)
481 {
482 m_app->setFileNameOfSelectedFile(name);
483 }
484
485 /**
486 * Set format to use when setting the filename from the tags.
487 *
488 * @param format file name format
489 * @see setFileNameFromTag()
490 */
setFileNameFormat(const QString & format)491 void ScriptInterface::setFileNameFormat(const QString& format)
492 {
493 FileConfig::instance().setToFilenameFormat(format);
494 }
495
496 /**
497 * Set the file names of the selected files from the tags.
498 *
499 * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
500 * @see setFileNameFormat()
501 */
setFileNameFromTag(int tagMask)502 void ScriptInterface::setFileNameFromTag(int tagMask)
503 {
504 m_app->getFilenameFromTags(Frame::tagVersionCast(tagMask));
505 }
506
507 /**
508 * Get value of frame.
509 * To get binary data like a picture, the name of a file to write can be
510 * added after the @a name, e.g. "Picture:/path/to/file".
511 *
512 * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
513 * @param name name of frame (e.g. "Artist")
514 */
getFrame(int tagMask,const QString & name)515 QString ScriptInterface::getFrame(int tagMask, const QString& name)
516 {
517 return m_app->getFrame(Frame::tagVersionCast(tagMask), name);
518 }
519
520 /**
521 * Set value of frame.
522 * For tag 2 (@a tagMask 2), if no frame with @a name exists, a new frame
523 * is added, if @a value is empty, the frame is deleted.
524 * To add binary data like a picture, a file can be added after the
525 * @a name, e.g. "Picture:/path/to/file".
526 *
527 * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
528 * @param name name of frame (e.g. "Artist")
529 * @param value value of frame
530 */
setFrame(int tagMask,const QString & name,const QString & value)531 bool ScriptInterface::setFrame(int tagMask, const QString& name,
532 const QString& value)
533 {
534 return m_app->setFrame(Frame::tagVersionCast(tagMask), name, value);
535 }
536
537 /**
538 * Get all frames of a tag.
539 *
540 * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
541 *
542 * @return list with alternating frame names and values.
543 */
getTag(int tagMask)544 QStringList ScriptInterface::getTag(int tagMask)
545 {
546 Frame::TagNumber tagNr =
547 Frame::tagNumberFromMask(Frame::tagVersionCast(tagMask));
548 if (tagNr >= Frame::Tag_NumValues)
549 return QStringList();
550
551 QStringList lst;
552 FrameTableModel* ft = m_app->frameModel(tagNr);
553 for (auto it = ft->frames().cbegin(); it != ft->frames().cend(); ++it) {
554 lst << it->getName();
555 lst << it->getValue();
556 }
557 return lst;
558 }
559
560 /**
561 * Get technical information about file.
562 * Properties are Format, Bitrate, Samplerate, Channels, Duration,
563 * Channel Mode, VBR, Tag 1, Tag 2.
564 * Properties which are not available are omitted.
565 *
566 * @return list with alternating property names and values.
567 */
getInformation()568 QStringList ScriptInterface::getInformation()
569 {
570 QStringList lst;
571 QModelIndex index = m_app->getFileSelectionModel()->currentIndex();
572 if (TaggedFile* taggedFile = FileProxyModel::getTaggedFileOfIndex(index)) {
573 TaggedFile::DetailInfo info;
574 taggedFile->getDetailInfo(info);
575 if (info.valid) {
576 lst << QLatin1String("Format") << info.format;
577 if (info.bitrate > 0 && info.bitrate < 16384) {
578 lst << QLatin1String("Bitrate") << QString::number(info.bitrate);
579 }
580 if (info.sampleRate > 0) {
581 lst << QLatin1String("Samplerate") << QString::number(info.sampleRate);
582 }
583 if (info.channels > 0) {
584 lst << QLatin1String("Channels") << QString::number(info.channels);
585 }
586 if (info.duration > 0) {
587 lst << QLatin1String("Duration") << QString::number(info.duration);
588 }
589 if (info.channelMode == TaggedFile::DetailInfo::CM_Stereo ||
590 info.channelMode == TaggedFile::DetailInfo::CM_JointStereo) {
591 lst << QLatin1String("Channel Mode") <<
592 (info.channelMode == TaggedFile::DetailInfo::CM_Stereo ?
593 QLatin1String("Stereo") : QLatin1String("Joint Stereo"));
594 }
595 if (info.vbr) {
596 lst << QLatin1String("VBR") << QLatin1String("1");
597 }
598 }
599 FOR_ALL_TAGS(tagNr) {
600 QString tag = taggedFile->getTagFormat(tagNr);
601 if (!tag.isEmpty()) {
602 lst << QLatin1String("Tag ") + Frame::tagNumberToString(tagNr) << tag; // clazy:exclude=reserve-candidates
603 }
604 }
605 }
606 return lst;
607 }
608
609 /**
610 * Set tag from file name.
611 *
612 * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
613 */
setTagFromFileName(int tagMask)614 void ScriptInterface::setTagFromFileName(int tagMask)
615 {
616 m_app->getTagsFromFilename(Frame::tagVersionCast(tagMask));
617 }
618
619 /**
620 * Set tag from other tag.
621 *
622 * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
623 */
setTagFromOtherTag(int tagMask)624 void ScriptInterface::setTagFromOtherTag(int tagMask)
625 {
626 m_app->copyToOtherTag(Frame::tagVersionCast(tagMask));
627 }
628
629 /**
630 * Copy tag.
631 *
632 * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
633 */
copyTag(int tagMask)634 void ScriptInterface::copyTag(int tagMask)
635 {
636 m_app->copyTags(Frame::tagVersionCast(tagMask));
637 }
638
639 /**
640 * Paste tag.
641 *
642 * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
643 */
pasteTag(int tagMask)644 void ScriptInterface::pasteTag(int tagMask)
645 {
646 m_app->pasteTags(Frame::tagVersionCast(tagMask));
647 }
648
649 /**
650 * Remove tag.
651 *
652 * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
653 */
removeTag(int tagMask)654 void ScriptInterface::removeTag(int tagMask)
655 {
656 m_app->removeTags(Frame::tagVersionCast(tagMask));
657 }
658
659 /**
660 * Reparse the configuration.
661 * Automated configuration changes are possible by modifying
662 * the configuration file and then reparsing the configuration.
663 */
reparseConfiguration()664 void ScriptInterface::reparseConfiguration()
665 {
666 m_app->readConfig();
667 }
668
669 /**
670 * Play selected audio files.
671 */
playAudio()672 void ScriptInterface::playAudio()
673 {
674 m_app->playAudio();
675 }
676
677 #endif // HAVE_QTDBUS
678