File indexing completed on 2024-05-19 04:56:07

0001 /**
0002  * \file scriptinterface.cpp
0003  * D-Bus script adaptor.
0004  *
0005  * \b Project: Kid3
0006  * \author Urs Fleisch
0007  * \date 20 Dec 2007
0008  *
0009  * Copyright (C) 2007-2024  Urs Fleisch
0010  *
0011  * This file is part of Kid3.
0012  *
0013  * Kid3 is free software; you can redistribute it and/or modify
0014  * it under the terms of the GNU General Public License as published by
0015  * the Free Software Foundation; either version 2 of the License, or
0016  * (at your option) any later version.
0017  *
0018  * Kid3 is distributed in the hope that it will be useful,
0019  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0020  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0021  * GNU General Public License for more details.
0022  *
0023  * You should have received a copy of the GNU General Public License
0024  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0025  */
0026 
0027 #include "scriptinterface.h"
0028 #ifdef HAVE_QTDBUS
0029 #include <QDBusMessage>
0030 #include <QDBusConnection>
0031 #include <QFileInfo>
0032 #include <QCoreApplication>
0033 #include <QItemSelectionModel>
0034 #include "kid3application.h"
0035 #include "taggedfile.h"
0036 #include "frametablemodel.h"
0037 #include "filefilter.h"
0038 #include "pictureframe.h"
0039 #include "fileproxymodel.h"
0040 #include "modeliterator.h"
0041 #include "batchimportconfig.h"
0042 #include "batchimportprofile.h"
0043 #include "fileconfig.h"
0044 
0045 /**
0046  * Constructor.
0047  *
0048  * @param app parent application
0049  */
0050 ScriptInterface::ScriptInterface(Kid3Application* app)
0051   : QDBusAbstractAdaptor(app), m_app(app)
0052 {
0053   setObjectName(QLatin1String("ScriptInterface"));
0054   setAutoRelaySignals(true);
0055 }
0056 
0057 /**
0058  * Open file or directory.
0059  *
0060  * @param path path to file or directory
0061  *
0062  * @return true if ok.
0063  */
0064 bool ScriptInterface::openDirectory(const QString& path)
0065 {
0066   return m_app->openDirectory({path}, true);
0067 }
0068 
0069 /**
0070  * Unload all tags.
0071  * The tags of all files which are not modified or selected are freed to
0072  * reclaim their memory.
0073  */
0074 void ScriptInterface::unloadAllTags()
0075 {
0076   m_app->unloadAllTags();
0077 }
0078 
0079 /**
0080  * Save all modified files.
0081  *
0082  * @return true if ok,
0083  *         else the error message is available using getErrorMessage().
0084  */
0085 bool ScriptInterface::save()
0086 {
0087   if (QStringList errorFiles = m_app->saveDirectory(); errorFiles.isEmpty()) {
0088     m_errorMsg.clear();
0089     return true;
0090   } else {
0091     m_errorMsg = QLatin1String("Error while writing file:\n") +
0092         errorFiles.join(QLatin1String("\n"));
0093     return false;
0094   }
0095 }
0096 
0097 /**
0098  * Get a detailed error message provided by some methods.
0099  *
0100  * @return detailed error message.
0101  */
0102 QString ScriptInterface::getErrorMessage() const
0103 {
0104   return m_errorMsg;
0105 }
0106 
0107 /**
0108  * Revert changes in the selected files.
0109  */
0110 void ScriptInterface::revert()
0111 {
0112   m_app->revertFileModifications();
0113 }
0114 
0115 /**
0116  * Import tags from a file.
0117  *
0118  * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
0119  * @param path    path of file, "clipboard" for import from clipboard
0120  * @param fmtIdx  index of format
0121  *
0122  * @return true if ok.
0123  */
0124 bool ScriptInterface::importFromFile(int tagMask, const QString& path,
0125                                      int fmtIdx)
0126 {
0127   return m_app->importTags(Frame::tagVersionCast(tagMask), path, fmtIdx);
0128 }
0129 
0130 /**
0131  * Import from tags.
0132  *
0133  * @param tagMask tag mask
0134  * @param source format to get source text from tags
0135  * @param extraction regular expression with frame names and captures to
0136  * extract from source text
0137  */
0138 void ScriptInterface::importFromTags(int tagMask,
0139                                      const QString& source,
0140                                      const QString& extraction)
0141 {
0142   m_app->importFromTags(Frame::tagVersionCast(tagMask), source, extraction);
0143 }
0144 
0145 /**
0146  * Import from tags on selected files.
0147  *
0148  * @param tagMask tag mask
0149  * @param source format to get source text from tags
0150  * @param extraction regular expression with frame names and captures to
0151  * extract from source text
0152  *
0153  * @return extracted values for "%{__return}(.+)", empty if not used.
0154  */
0155 QStringList ScriptInterface::importFromTagsToSelection(int tagMask,
0156                                                       const QString& source,
0157                                                       const QString& extraction)
0158 {
0159   return m_app->importFromTagsToSelection(Frame::tagVersionCast(tagMask),
0160                                           source, extraction);
0161 }
0162 
0163 /**
0164  * Start an automatic batch import.
0165  *
0166  * @param tagMask tag mask (bit 0 for tag 1, bit 1 for tag 2)
0167  * @param profileName name of batch import profile to use
0168  *
0169  * @return true if profile found.
0170  */
0171 bool ScriptInterface::batchImport(int tagMask, const QString& profileName)
0172 {
0173   return m_app->batchImport(profileName, Frame::tagVersionCast(tagMask));
0174 }
0175 
0176 /**
0177  * Download album cover art into the picture frame of the selected files.
0178  *
0179  * @param url           URL of picture file or album art resource
0180  * @param allFilesInDir true to add the image to all files in the directory
0181  */
0182 void ScriptInterface::downloadAlbumArt(const QString& url, bool allFilesInDir)
0183 {
0184   m_app->downloadImage(url, allFilesInDir);
0185 }
0186 
0187 /**
0188  * Export tags to a file.
0189  *
0190  * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
0191  * @param path    path of file, "clipboard" for export to clipboard
0192  * @param fmtIdx  index of format
0193  *
0194  * @return true if ok.
0195  */
0196 bool ScriptInterface::exportToFile(int tagMask, const QString& path, int fmtIdx)
0197 {
0198   return m_app->exportTags(Frame::tagVersionCast(tagMask), path, fmtIdx);
0199 }
0200 
0201 /**
0202  * Create a playlist.
0203  *
0204  * @return true if ok.
0205  */
0206 bool ScriptInterface::createPlaylist()
0207 {
0208   return m_app->writePlaylist();
0209 }
0210 
0211 /**
0212  * Get items of a playlist.
0213  * @param path path to playlist file
0214  * @return list of absolute paths to playlist items.
0215  */
0216 QStringList ScriptInterface::getPlaylistItems(const QString& path)
0217 {
0218   return m_app->getPlaylistItems(path);
0219 }
0220 
0221 /**
0222  * Set items of a playlist.
0223  * @param path path to playlist file
0224  * @param items list of absolute paths to playlist items
0225  * @return true if ok, false if not all @a items were found and added or
0226  *         saving failed.
0227  */
0228 bool ScriptInterface::setPlaylistItems(const QString& path,
0229                                        const QStringList& items)
0230 {
0231   return m_app->setPlaylistItems(path, items);
0232 }
0233 
0234 /**
0235  * Quit the application.
0236  */
0237 void ScriptInterface::quit()
0238 {
0239   selectAll();
0240   revert();
0241   QCoreApplication::quit();
0242 }
0243 
0244 /**
0245  * Select all files.
0246  */
0247 void ScriptInterface::selectAll()
0248 {
0249   m_app->selectAllFiles();
0250 }
0251 
0252 /**
0253  * Deselect all files.
0254  */
0255 void ScriptInterface::deselectAll()
0256 {
0257   m_app->deselectAllFiles();
0258 }
0259 
0260 /**
0261  * Set the first file as the current file.
0262  *
0263  * @return true if there is a first file.
0264  */
0265 bool ScriptInterface::firstFile()
0266 {
0267   return m_app->firstFile(false);
0268 }
0269 
0270 /**
0271  * Set the previous file as the current file.
0272  *
0273  * @return true if there is a previous file.
0274  */
0275 bool ScriptInterface::previousFile()
0276 {
0277   return m_app->previousFile(false);
0278 }
0279 
0280 /**
0281  * Set the next file as the current file.
0282  *
0283  * @return true if there is a next file.
0284  */
0285 bool ScriptInterface::nextFile()
0286 {
0287   return m_app->nextFile(false);
0288 }
0289 
0290 /**
0291  * Select the first file.
0292  *
0293  * @return true if there is a first file.
0294  */
0295 bool ScriptInterface::selectFirstFile()
0296 {
0297   return m_app->firstFile(true);
0298 }
0299 
0300 /**
0301  * Select the previous file.
0302  *
0303  * @return true if there is a previous file.
0304  */
0305 bool ScriptInterface::selectPreviousFile()
0306 {
0307   return m_app->previousFile(true);
0308 }
0309 
0310 /**
0311  * Select the next file.
0312  *
0313  * @return true if there is a next file.
0314  */
0315 bool ScriptInterface::selectNextFile()
0316 {
0317   return m_app->nextFile(true);
0318 }
0319 
0320 /**
0321  * Select the current file.
0322  *
0323  * @return true if there is a current file.
0324  */
0325 bool ScriptInterface::selectCurrentFile()
0326 {
0327  return m_app->selectCurrentFile(true);
0328 }
0329 
0330 /**
0331  * Expand the current file item if it is a directory.
0332  * A file list item is a directory if getFileName() returns a name with
0333  * '/' as the last character.
0334  * The directory is fetched but not expanded in the GUI. To expand it in the
0335  * GUI, call nextFile() or selectNextFile() after expandDirectory().
0336  *
0337  * @return true if current file item is a directory.
0338  */
0339 bool ScriptInterface::expandDirectory()
0340 {
0341   if (QModelIndex index(m_app->getFileSelectionModel()->currentIndex());
0342       !FileProxyModel::getPathIfIndexOfDir(index).isNull()) {
0343     m_app->expandDirectory(index);
0344     return true;
0345   }
0346   return false;
0347 }
0348 
0349 /**
0350  * Expand the file list.
0351  */
0352 void ScriptInterface::expandFileList()
0353 {
0354   m_app->requestExpandFileList();
0355 }
0356 
0357 /**
0358  * Apply the file name format.
0359  */
0360 void ScriptInterface::applyFilenameFormat()
0361 {
0362   m_app->applyFilenameFormat();
0363 }
0364 
0365 /**
0366  * Apply the tag format.
0367  */
0368 void ScriptInterface::applyTagFormat()
0369 {
0370   m_app->applyTagFormat();
0371 }
0372 
0373 /**
0374  * Apply text encoding.
0375  */
0376 void ScriptInterface::applyTextEncoding()
0377 {
0378   m_app->applyTextEncoding();
0379 }
0380 
0381 /**
0382  * Set the directory name from the tags.
0383  *
0384  * @param tagMask tag mask (bit 0 for tag 1, bit 1 for tag 2)
0385  * @param format  directory name format
0386  * @param create  true to create, false to rename
0387  *
0388  * @return true if ok,
0389  *         else the error message is available using getErrorMessage().
0390  */
0391 bool ScriptInterface::setDirNameFromTag(int tagMask, const QString& format,
0392                                         bool create)
0393 {
0394   connect(m_app, &Kid3Application::renameActionsScheduled,
0395           this, &ScriptInterface::onRenameActionsScheduled);
0396   if (m_app->renameDirectory(Frame::tagVersionCast(tagMask), format,
0397                              create)) {
0398     return true;
0399   }
0400   disconnect(m_app, &Kid3Application::renameActionsScheduled,
0401              this, &ScriptInterface::onRenameActionsScheduled);
0402   return false;
0403 }
0404 
0405 void ScriptInterface::onRenameActionsScheduled()
0406 {
0407   disconnect(m_app, &Kid3Application::renameActionsScheduled,
0408              this, &ScriptInterface::onRenameActionsScheduled);
0409   m_errorMsg = m_app->performRenameActions();
0410   if (!m_errorMsg.isEmpty()) {
0411     m_errorMsg = QLatin1String("Error while renaming:\n") + m_errorMsg;
0412   }
0413 }
0414 
0415 /**
0416  * Set subsequent track numbers in the selected files.
0417  *
0418  * @param tagMask      tag mask (bit 0 for tag 1, bit 1 for tag 2)
0419  * @param firstTrackNr number to use for first file
0420  */
0421 void ScriptInterface::numberTracks(int tagMask, int firstTrackNr)
0422 {
0423   m_app->numberTracks(firstTrackNr, 0, Frame::tagVersionCast(tagMask));
0424 }
0425 
0426 /**
0427  * Filter the files.
0428  *
0429  * @param expression filter expression
0430  */
0431 void ScriptInterface::filter(const QString& expression)
0432 {
0433   m_app->applyFilter(expression);
0434 }
0435 
0436 /**
0437  * Convert ID3v2.3 tags to ID3v2.4.
0438  */
0439 void ScriptInterface::convertToId3v24()
0440 {
0441   m_app->convertToId3v24();
0442 }
0443 
0444 /**
0445  * Convert ID3v2.4 tags to ID3v2.3.
0446  */
0447 void ScriptInterface::convertToId3v23()
0448 {
0449   m_app->convertToId3v23();
0450 }
0451 
0452 /**
0453  * Get path of directory.
0454  *
0455  * @return absolute path of directory.
0456  */
0457 QString ScriptInterface::getDirectoryName()
0458 {
0459   return m_app->getDirPath();
0460 }
0461 
0462 /**
0463  * Get name of current file.
0464  *
0465  * @return absolute file name, ends with "/" if it is a directory.
0466  */
0467 QString ScriptInterface::getFileName()
0468 {
0469   return m_app->getFileNameOfSelectedFile();
0470 }
0471 
0472 /**
0473  * Set name of selected file.
0474  * The file will be renamed when the directory is saved.
0475  *
0476  * @param name file name.
0477  */
0478 void ScriptInterface::setFileName(const QString& name)
0479 {
0480   m_app->setFileNameOfSelectedFile(name);
0481 }
0482 
0483 /**
0484  * Set format to use when setting the filename from the tags.
0485  *
0486  * @param format file name format
0487  * @see setFileNameFromTag()
0488  */
0489 void ScriptInterface::setFileNameFormat(const QString& format)
0490 {
0491   FileConfig::instance().setToFilenameFormat(format);
0492 }
0493 
0494 /**
0495  * Set the file names of the selected files from the tags.
0496  *
0497  * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
0498  * @see setFileNameFormat()
0499  */
0500 void ScriptInterface::setFileNameFromTag(int tagMask)
0501 {
0502   m_app->getFilenameFromTags(Frame::tagVersionCast(tagMask));
0503 }
0504 
0505 /**
0506  * Get value of frame.
0507  * To get binary data like a picture, the name of a file to write can be
0508  * added after the @a name, e.g. "Picture:/path/to/file".
0509  *
0510  * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
0511  * @param name    name of frame (e.g. "Artist")
0512  */
0513 QString ScriptInterface::getFrame(int tagMask, const QString& name)
0514 {
0515   return m_app->getFrame(Frame::tagVersionCast(tagMask), name);
0516 }
0517 
0518 /**
0519  * Set value of frame.
0520  * For tag 2 (@a tagMask 2), if no frame with @a name exists, a new frame
0521  * is added, if @a value is empty, the frame is deleted.
0522  * To add binary data like a picture, a file can be added after the
0523  * @a name, e.g. "Picture:/path/to/file".
0524  *
0525  * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
0526  * @param name    name of frame (e.g. "Artist")
0527  * @param value   value of frame
0528  */
0529 bool ScriptInterface::setFrame(int tagMask, const QString& name,
0530                            const QString& value)
0531 {
0532   return m_app->setFrame(Frame::tagVersionCast(tagMask), name, value);
0533 }
0534 
0535 /**
0536  * Get all frames of a tag.
0537  *
0538  * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
0539  *
0540  * @return list with alternating frame names and values.
0541  */
0542 QStringList ScriptInterface::getTag(int tagMask)
0543 {
0544   Frame::TagNumber tagNr =
0545       Frame::tagNumberFromMask(Frame::tagVersionCast(tagMask));
0546   if (tagNr >= Frame::Tag_NumValues)
0547     return QStringList();
0548 
0549   QStringList lst;
0550   FrameTableModel* ft = m_app->frameModel(tagNr);
0551   for (auto it = ft->frames().cbegin(); it != ft->frames().cend(); ++it) {
0552     lst << it->getName();
0553     lst << it->getValue();
0554   }
0555   return lst;
0556 }
0557 
0558 /**
0559  * Get technical information about file.
0560  * Properties are Format, Bitrate, Samplerate, Channels, Duration,
0561  * Channel Mode, VBR, Tag 1, Tag 2.
0562  * Properties which are not available are omitted.
0563  *
0564  * @return list with alternating property names and values.
0565  */
0566 QStringList ScriptInterface::getInformation()
0567 {
0568   QStringList lst;
0569   QModelIndex index = m_app->getFileSelectionModel()->currentIndex();
0570   if (TaggedFile* taggedFile = FileProxyModel::getTaggedFileOfIndex(index)) {
0571     TaggedFile::DetailInfo info;
0572     taggedFile->getDetailInfo(info);
0573     if (info.valid) {
0574       lst << QLatin1String("Format") << info.format;
0575       if (info.bitrate > 0 && info.bitrate < 16384) {
0576         lst << QLatin1String("Bitrate") << QString::number(info.bitrate);
0577       }
0578       if (info.sampleRate > 0) {
0579         lst << QLatin1String("Samplerate") << QString::number(info.sampleRate);
0580       }
0581       if (info.channels > 0) {
0582         lst << QLatin1String("Channels") << QString::number(info.channels);
0583       }
0584       if (info.duration > 0) {
0585         lst << QLatin1String("Duration") << QString::number(info.duration);
0586       }
0587       if (info.channelMode == TaggedFile::DetailInfo::CM_Stereo ||
0588           info.channelMode == TaggedFile::DetailInfo::CM_JointStereo) {
0589         lst << QLatin1String("Channel Mode") <<
0590           (info.channelMode == TaggedFile::DetailInfo::CM_Stereo ?
0591            QLatin1String("Stereo") : QLatin1String("Joint Stereo"));
0592       }
0593       if (info.vbr) {
0594         lst << QLatin1String("VBR") << QLatin1String("1");
0595       }
0596     }
0597     FOR_ALL_TAGS(tagNr) {
0598       if (QString tag = taggedFile->getTagFormat(tagNr); !tag.isEmpty()) {
0599         lst << QLatin1String("Tag ") + Frame::tagNumberToString(tagNr) << tag; // clazy:exclude=reserve-candidates
0600       }
0601     }
0602   }
0603   return lst;
0604 }
0605 
0606 /**
0607  * Set tag from file name.
0608  *
0609  * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
0610  */
0611 void ScriptInterface::setTagFromFileName(int tagMask)
0612 {
0613   m_app->getTagsFromFilename(Frame::tagVersionCast(tagMask));
0614 }
0615 
0616 /**
0617  * Set tag from other tag.
0618  *
0619  * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
0620  */
0621 void ScriptInterface::setTagFromOtherTag(int tagMask)
0622 {
0623   m_app->copyToOtherTag(Frame::tagVersionCast(tagMask));
0624 }
0625 
0626 /**
0627  * Copy tag.
0628  *
0629  * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
0630  */
0631 void ScriptInterface::copyTag(int tagMask)
0632 {
0633   m_app->copyTags(Frame::tagVersionCast(tagMask));
0634 }
0635 
0636 /**
0637  * Paste tag.
0638  *
0639  * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
0640  */
0641 void ScriptInterface::pasteTag(int tagMask)
0642 {
0643   m_app->pasteTags(Frame::tagVersionCast(tagMask));
0644 }
0645 
0646 /**
0647  * Remove tag.
0648  *
0649  * @param tagMask tag bit (1 for tag 1, 2 for tag 2)
0650  */
0651 void ScriptInterface::removeTag(int tagMask)
0652 {
0653   m_app->removeTags(Frame::tagVersionCast(tagMask));
0654 }
0655 
0656 /**
0657  * Reparse the configuration.
0658  * Automated configuration changes are possible by modifying
0659  * the configuration file and then reparsing the configuration.
0660  */
0661 void ScriptInterface::reparseConfiguration()
0662 {
0663   m_app->readConfig();
0664 }
0665 
0666 /**
0667  * Play selected audio files.
0668  */
0669 void ScriptInterface::playAudio()
0670 {
0671   m_app->playAudio();
0672 }
0673 
0674 #endif // HAVE_QTDBUS