MythTV master
cdrip.cpp
Go to the documentation of this file.
1// Unix C includes
2#include <sys/types.h>
3#include <fcntl.h>
4
5#include "config.h"
6
7// C++ includes
8#include <chrono>
9#include <cstdio>
10#include <cstring>
11#include <iostream>
12#include <memory>
13
14// Qt includes
15#include <QtGlobal>
16#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
17#include <QtSystemDetection>
18#endif
19#include <QApplication>
20#include <QDir>
21#include <QEvent>
22#include <QFile>
23#include <QKeyEvent>
24#include <QRegularExpression>
25#include <QTimer>
26#include <QUrl>
27#include <utility>
28
29// MythTV includes
33#include <libmythbase/mythdb.h>
50
51// MythMusic includes
52#include "cdrip.h"
53#ifdef HAVE_CDIO
54#include "cddecoder.h"
55#endif // HAVE_CDIO
56#include "editmetadata.h"
57#include "encoder.h"
58#include "flacencoder.h"
59#include "genres.h"
60#include "lameencoder.h"
61#include "vorbisencoder.h"
62
63#ifdef HAVE_CDIO
64// libparanoia compatibility
65#ifndef cdrom_paranoia
66#define cdrom_paranoia cdrom_paranoia_t
67#endif // cdrom_paranoia
68
69#ifndef CD_FRAMESIZE_RAW
70# define CD_FRAMESIZE_RAW CDIO_CD_FRAMESIZE_RAW
71#endif // CD_FRAMESIZE_RAW
72#endif // HAVE_CDIO
73
74const QEvent::Type RipStatusEvent::kTrackTextEvent =
75 (QEvent::Type) QEvent::registerEventType();
76const QEvent::Type RipStatusEvent::kOverallTextEvent =
77 (QEvent::Type) QEvent::registerEventType();
78const QEvent::Type RipStatusEvent::kStatusTextEvent =
79 (QEvent::Type) QEvent::registerEventType();
80const QEvent::Type RipStatusEvent::kTrackProgressEvent =
81 (QEvent::Type) QEvent::registerEventType();
82const QEvent::Type RipStatusEvent::kTrackPercentEvent =
83 (QEvent::Type) QEvent::registerEventType();
84const QEvent::Type RipStatusEvent::kTrackStartEvent =
85 (QEvent::Type) QEvent::registerEventType();
87 (QEvent::Type) QEvent::registerEventType();
88const QEvent::Type RipStatusEvent::kOverallPercentEvent =
89 (QEvent::Type) QEvent::registerEventType();
90const QEvent::Type RipStatusEvent::kOverallStartEvent =
91 (QEvent::Type) QEvent::registerEventType();
92const QEvent::Type RipStatusEvent::kCopyStartEvent =
93 (QEvent::Type) QEvent::registerEventType();
94const QEvent::Type RipStatusEvent::kCopyEndEvent =
95 (QEvent::Type) QEvent::registerEventType();
96const QEvent::Type RipStatusEvent::kFinishedEvent =
97 (QEvent::Type) QEvent::registerEventType();
98const QEvent::Type RipStatusEvent::kEncoderErrorEvent =
99 (QEvent::Type) QEvent::registerEventType();
100
102{
103 RunProlog();
104 m_parent->scanCD();
105 RunEpilog();
106}
107
109
111{
112 RunProlog();
113 m_parent->ejectCD();
114 RunEpilog();
115}
116
118
119static long int getSectorCount ([[maybe_unused]] QString &cddevice,
120 [[maybe_unused]] int tracknum)
121{
122#ifdef HAVE_CDIO
123 QByteArray devname = cddevice.toLatin1();
124 cdrom_drive *device = cdda_identify(devname.constData(), 0, nullptr);
125
126 if (!device)
127 {
128 LOG(VB_GENERAL, LOG_ERR,
129 QString("Error: %1('%2',track=%3) failed at cdda_identify()").
130 arg(__func__, cddevice, QString::number(tracknum)));
131 return -1;
132 }
133
134 if (cdda_open(device))
135 {
136 LOG(VB_GENERAL, LOG_ERR,
137 QString("Error: %1('%2',track=%3) failed at cdda_open() - cdda not supported").
138 arg(__func__, cddevice, QString::number(tracknum)));
139 cdda_close(device);
140 return -1;
141 }
142
143 // we only care about audio tracks
144 if (cdda_track_audiop (device, tracknum))
145 {
146 cdda_verbose_set(device, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
147 long int start = cdda_track_firstsector(device, tracknum);
148 long int end = cdda_track_lastsector( device, tracknum);
149 cdda_close(device);
150 return end - start + 1;
151 }
152 LOG(VB_GENERAL, LOG_ERR,
153 QString("Error: cdrip - cdda_track_audiop(%1) returned 0").arg(cddevice));
154
155 cdda_close(device);
156#endif // HAVE_CDIO
157 return 0;
158}
159
160#ifdef HAVE_CDIO
161static void paranoia_cb(long /*status*/, paranoia_cb_mode_t /*mode*/)
162{
163}
164#endif // HAVE_CDIO
165
167 QVector<RipTrack*> *tracks, int quality) :
168 MThread("CDRipper"),
169 m_parent(parent),
170 m_cdDevice(std::move(device)), m_quality(quality),
171 m_tracks(tracks)
172{
173#ifdef Q_OS_WINDOWS // libcdio needs the drive letter with no path
174 if (m_cdDevice.endsWith('\\'))
175 m_cdDevice.chop(1);
176#endif // Q_OS_WINDOWS
177
178 QString lastHost = gCoreContext->GetSetting("MythMusicLastRipHost", gCoreContext->GetMasterHostName());
179 QStringList dirs = StorageGroup::getGroupDirs("Music", lastHost);
180 if (dirs.count() > 0)
181 m_musicStorageDir = StorageGroup::getGroupDirs("Music", lastHost).at(0);
182}
183
185{
186 cancel();
187 wait();
188}
189
191{
192 m_quit = true;
193}
194
196{
197 return m_quit;
198}
199
201{
202 RunProlog();
203
204 if (m_tracks->empty())
205 {
206 RunEpilog();
207 return;
208 }
209
210 m_totalSectors = 0;
212 for (int trackno = 0; trackno < m_tracks->size(); trackno++)
213 {
214 if (!m_tracks->at(trackno)->active)
215 continue;
217 }
218
219 if (!m_totalSectors)
220 {
221 RunEpilog();
222 return;
223 }
224
225 MusicMetadata *track = m_tracks->at(0)->metadata;
226 QString tots;
227
228 if (track->Compilation())
229 {
230 tots = track->CompilationArtist() + " ~ " + track->Album();
231 }
232 else
233 {
234 tots = track->Artist() + " ~ " + track->Album();
235 }
236
237 QApplication::postEvent(
238 m_parent,
240 QApplication::postEvent(
241 m_parent,
243 QApplication::postEvent(
244 m_parent,
246
247 QString textstatus;
248 QString encodertype = gCoreContext->GetSetting("EncoderType");
249 bool mp3usevbr = gCoreContext->GetBoolSetting("Mp3UseVBR", false);
250
251 QApplication::postEvent(m_parent,
253
254 if (LCD *lcd = LCD::Get())
255 {
256 QString lcd_tots = tr("Importing %1").arg(tots);
257 QList<LCDTextItem> textItems;
258 textItems.append(LCDTextItem(1, ALIGN_CENTERED,
259 lcd_tots, "Generic", false));
260 lcd->switchToGeneric(textItems);
261 }
262
263 MusicMetadata *titleTrack = nullptr;
264 QString saveDir = GetConfDir() + "/tmp/RipTemp/";
265 QString outfile;
266
267 std::unique_ptr<Encoder> encoder;
268
269 for (int trackno = 0; trackno < m_tracks->size(); trackno++)
270 {
271 if (isCancelled())
272 break;
273
274 QApplication::postEvent(
275 m_parent,
277 QString("Track %1 of %2")
278 .arg(trackno + 1).arg(m_tracks->size())));
279
280 QApplication::postEvent(
281 m_parent,
283
284 track = m_tracks->at(trackno)->metadata;
285
286 if (track)
287 {
288 textstatus = track->Title();
289 QApplication::postEvent(
290 m_parent,
291 new RipStatusEvent(
293 QApplication::postEvent(
294 m_parent,
296 QApplication::postEvent(
297 m_parent,
299
300 // do we need to start a new file?
301 if (!m_tracks->at(trackno)->active)
302 continue;
303
304 titleTrack = track;
305 titleTrack->setLength(m_tracks->at(trackno)->length);
306
307 if (m_quality < 3)
308 {
309 if (encodertype == "mp3")
310 {
311 outfile = QString("track%1.mp3").arg(trackno);
312 encoder = std::make_unique<LameEncoder>(saveDir + outfile, m_quality,
313 titleTrack, mp3usevbr);
314 }
315 else // ogg
316 {
317 outfile = QString("track%1.ogg").arg(trackno);
318 encoder = std::make_unique<VorbisEncoder>(saveDir + outfile, m_quality,
319 titleTrack);
320 }
321 }
322 else
323 {
324 outfile = QString("track%1.flac").arg(trackno);
325 encoder = std::make_unique<FlacEncoder>(saveDir + outfile, m_quality,
326 titleTrack);
327 }
328
329 if (!encoder->isValid())
330 {
331 QApplication::postEvent(
332 m_parent,
333 new RipStatusEvent(
335 "Encoder failed to open file for writing"));
336 LOG(VB_GENERAL, LOG_ERR, "MythMusic: Encoder failed"
337 " to open file for writing");
338
339 RunEpilog();
340 return;
341 }
342
343 if (!encoder)
344 {
345 // This should never happen.
346 QApplication::postEvent(
347 m_parent,
349 "Failed to create encoder"));
350 LOG(VB_GENERAL, LOG_ERR, "MythMusic: No encoder, failing");
351 RunEpilog();
352 return;
353 }
354 ripTrack(m_cdDevice, encoder.get(), trackno + 1);
355
356 if (isCancelled())
357 {
358 RunEpilog();
359 return;
360 }
361
362 QString ext = QFileInfo(outfile).suffix();
363 QString destFile = filenameFromMetadata(titleTrack) + '.' + ext;
364 QUrl url(m_musicStorageDir);
365
366 // save the metadata to the DB
367 titleTrack->setFilename(destFile);
368 titleTrack->setHostname(url.host());
369 titleTrack->setFileSize((quint64)QFileInfo(outfile).size());
370 titleTrack->dumpToDatabase();
371
372 // this will delete the encoder which will write the metadata in it's dtor
373 encoder.reset();
374
375 // copy track to the BE
376 destFile = MythCoreContext::GenMythURL(url.host(), 0, destFile, "Music");
377
378 QApplication::postEvent(m_parent, new RipStatusEvent(RipStatusEvent::kCopyStartEvent, 0));
379 RemoteFile::CopyFile(saveDir + outfile, destFile, true);
380 QApplication::postEvent(m_parent, new RipStatusEvent(RipStatusEvent::kCopyEndEvent, 0));
381 }
382 }
383
384 QString PostRipCDScript = gCoreContext->GetSetting("PostCDRipScript");
385
386 if (!PostRipCDScript.isEmpty())
387 myth_system(PostRipCDScript);
388
389 QApplication::postEvent(
391
392 RunEpilog();
393}
394
395int CDRipperThread::ripTrack([[maybe_unused]] QString &cddevice,
396 [[maybe_unused]] Encoder *encoder,
397 [[maybe_unused]] int tracknum)
398{
399#ifdef HAVE_CDIO
400 QByteArray devname = cddevice.toLatin1();
401 cdrom_drive *device = cdda_identify(devname.constData(), 0, nullptr);
402
403 if (!device)
404 {
405 LOG(VB_GENERAL, LOG_ERR,
406 QString("cdda_identify failed for device '%1', "
407 "CDRipperThread::ripTrack(tracknum = %2) exiting.")
408 .arg(cddevice).arg(tracknum));
409 return -1;
410 }
411
412 if (cdda_open(device))
413 {
414 LOG(VB_MEDIA, LOG_INFO,
415 QString("Error: %1('%2',track=%3) failed at cdda_open() - cdda not supported")
416 .arg(__func__, cddevice, QString::number(tracknum)));
417 cdda_close(device);
418 return -1;
419 }
420
421 cdda_verbose_set(device, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
422 long int start = cdda_track_firstsector(device, tracknum);
423 long int end = cdda_track_lastsector(device, tracknum);
424 LOG(VB_MEDIA, LOG_INFO, QString("%1(%2,track=%3) start=%4 end=%5")
425 .arg(__func__, cddevice).arg(tracknum).arg(start).arg(end));
426
427 cdrom_paranoia *paranoia = paranoia_init(device);
428 if (gCoreContext->GetSetting("ParanoiaLevel") == "full")
429 {
430 paranoia_modeset(paranoia, PARANOIA_MODE_FULL |
431 PARANOIA_MODE_NEVERSKIP);
432 }
433 else
434 {
435 paranoia_modeset(paranoia, PARANOIA_MODE_OVERLAP);
436 }
437
438 paranoia_seek(paranoia, start, SEEK_SET);
439
440 long int curpos = start;
441
442 QApplication::postEvent(
443 m_parent,
445 m_lastTrackPct = -1;
446 m_lastOverallPct = -1;
447
448 int every15 = 15;
449 while (curpos < end)
450 {
451 int16_t *buffer = paranoia_read(paranoia, paranoia_cb);
452
453 if (encoder->addSamples(buffer, CD_FRAMESIZE_RAW))
454 break;
455
456 curpos++;
457
458 every15--;
459
460 if (every15 <= 0)
461 {
462 every15 = 15;
463
464 // updating the UITypes can be slow - only update if we need to:
465 int newOverallPct = (int) (100.0 / ((double) m_totalSectors /
466 (m_totalSectorsDone + curpos - start)));
467 if (newOverallPct != m_lastOverallPct)
468 {
469 m_lastOverallPct = newOverallPct;
470 QApplication::postEvent(
471 m_parent,
473 newOverallPct));
474 QApplication::postEvent(
475 m_parent,
477 m_totalSectorsDone + curpos - start));
478 }
479
480 int newTrackPct = (int) (100.0 / ((double) (end - start + 1) / (curpos - start)));
481 if (newTrackPct != m_lastTrackPct)
482 {
483 m_lastTrackPct = newTrackPct;
484 QApplication::postEvent(
485 m_parent,
487 newTrackPct));
488 QApplication::postEvent(
489 m_parent,
491 curpos - start));
492 }
493
494 if (LCD *lcd = LCD::Get())
495 {
496 float fProgress = (float)(m_totalSectorsDone + (curpos - start))
498 lcd->setGenericProgress(fProgress);
499 }
500 }
501
502 if (isCancelled())
503 {
504 break;
505 }
506 }
507
508 m_totalSectorsDone += end - start + 1;
509
510 paranoia_free(paranoia);
511 cdda_close(device);
512
513 return (curpos - start + 1) * CD_FRAMESIZE_RAW;
514#else
515 return 0;
516#endif // HAVE_CDIO
517}
518
520
521Ripper::Ripper(MythScreenStack *parent, QString device) :
522 MythScreenType(parent, "ripcd"),
523 m_tracks(new QVector<RipTrack*>),
524 m_cdDevice(std::move(device))
525{
526#ifndef Q_OS_WINDOWS
527 // if the MediaMonitor is running stop it
529 if (mon && mon->IsActive())
530 {
532 mon->StopMonitoring();
533 }
534#endif // Q_OS_WINDOWS
535
536 // make sure the directory where we temporarily save the rips is present
537 QDir dir;
538 dir.mkpath(GetConfDir() + "/tmp/RipTemp/");
539
540 // remove any ripped tracks from the temp rip directory
541 QString command = "rm -f " + GetConfDir() + "/tmp/RipTemp/*";
542 myth_system(command);
543
544 // get last host and directory we ripped to
545 QString lastHost = gCoreContext->GetSetting("MythMusicLastRipHost", gCoreContext->GetMasterHostName());
546 QStringList dirs = StorageGroup::getGroupDirs("Music", lastHost);
547 if (dirs.count() > 0)
548 m_musicStorageDir = StorageGroup::getGroupDirs("Music", lastHost).at(0);
549}
550
552{
553 // remove any ripped tracks from the temp rip directory
554 QString command = "rm -f " + GetConfDir() + "/tmp/RipTemp/*";
555 myth_system(command);
556
557 delete m_decoder;
558
559#ifndef Q_OS_WINDOWS
560 // if the MediaMonitor was active when we started then restart it
562 {
564 if (mon)
565 mon->StartMonitoring();
566 }
567#endif // Q_OS_WINDOWS
568
570 emit ripFinished();
571}
572
574{
575 if (!LoadWindowFromXML("music-ui.xml", "cdripper", this))
576 return false;
577
578 m_qualityList = dynamic_cast<MythUIButtonList *>(GetChild("quality"));
579 m_artistEdit = dynamic_cast<MythUITextEdit *>(GetChild("artist"));
580 m_searchArtistButton = dynamic_cast<MythUIButton *>(GetChild("searchartist"));
581 m_albumEdit = dynamic_cast<MythUITextEdit *>(GetChild("album"));
582 m_searchAlbumButton = dynamic_cast<MythUIButton *>(GetChild("searchalbum"));
583 m_genreEdit = dynamic_cast<MythUITextEdit *>(GetChild("genre"));
584 m_yearEdit = dynamic_cast<MythUITextEdit *>(GetChild("year"));
585 m_searchGenreButton = dynamic_cast<MythUIButton *>(GetChild("searchgenre"));
586 m_compilationCheck = dynamic_cast<MythUICheckBox *>(GetChild("compilation"));
587 m_switchTitleArtist = dynamic_cast<MythUIButton *>(GetChild("switch"));
588 m_scanButton = dynamic_cast<MythUIButton *>(GetChild("scan"));
589 m_ripButton = dynamic_cast<MythUIButton *>(GetChild("rip"));
590 m_trackList = dynamic_cast<MythUIButtonList *>(GetChild("tracks"));
591
593
598 {
599 LOG(VB_GENERAL, LOG_ERR,
600 "Missing theme elements for screen 'cdripper'");
601 return false;
602 }
603
605 this, qOverload<MythUIButtonListItem *>(&Ripper::toggleTrackActive));
621
622 // Populate Quality List
623 new MythUIButtonListItem(m_qualityList, tr("Low"), QVariant::fromValue(0));
624 new MythUIButtonListItem(m_qualityList, tr("Medium"), QVariant::fromValue(1));
625 new MythUIButtonListItem(m_qualityList, tr("High"), QVariant::fromValue(2));
626 new MythUIButtonListItem(m_qualityList, tr("Perfect"), QVariant::fromValue(3));
627 m_qualityList->SetValueByData(QVariant::fromValue(
628 gCoreContext->GetNumSetting("DefaultRipQuality", 1)));
629
630 QTimer::singleShot(500ms, this, &Ripper::startScanCD);
631
632 return true;
633}
634
635bool Ripper::keyPressEvent(QKeyEvent *event)
636{
638 return true;
639
640 QStringList actions;
641 bool handled = GetMythMainWindow()->TranslateKeyPress("Global", event, actions);
642
643 for (int i = 0; i < actions.size() && !handled; i++)
644 {
645 const QString& action = actions[i];
646 handled = true;
647
648 if (action == "EDIT" || action == "INFO") // INFO purely for historical reasons
650 else if (action == "MENU")
651 ShowMenu();
652 else
653 handled = false;
654 }
655
656 if (!handled && MythScreenType::keyPressEvent(event))
657 handled = true;
658
659 return handled;
660}
661
663{
664 if (m_tracks->empty())
665 return;
666
667 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
668
669 auto *menu = new MythDialogBox("", popupStack, "ripmusicmenu");
670
671 if (menu->Create())
672 {
673 popupStack->AddScreen(menu);
674 }
675 else
676 {
677 delete menu;
678 return;
679 }
680
681 menu->SetReturnEvent(this, "menu");
682 menu->AddButton(tr("Select Where To Save Tracks"), &Ripper::chooseBackend);
683 menu->AddButton(tr("Edit Track Metadata"),
684 qOverload<>(&Ripper::showEditMetadataDialog));
685}
686
688{
690}
691
692void Ripper::chooseBackend(void) const
693{
694 QStringList hostList;
695
696 // get a list of hosts with a directory defined for the 'Music' storage group
698 QString sql = "SELECT DISTINCT hostname "
699 "FROM storagegroup "
700 "WHERE groupname = 'Music'";
701 if (!query.exec(sql) || !query.isActive())
702 {
703 MythDB::DBError("Ripper::chooseBackend get host list", query);
704 }
705 else
706 {
707 while(query.next())
708 {
709 hostList.append(query.value(0).toString());
710 }
711 }
712
713 if (hostList.isEmpty())
714 {
715 LOG(VB_GENERAL, LOG_ERR, "Ripper::chooseBackend: No backends found");
716 return;
717 }
718
719 QString msg = tr("Select where to save tracks");
720
721 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
722 auto *searchDlg = new MythUISearchDialog(popupStack, msg, hostList, false, "");
723
724 if (!searchDlg->Create())
725 {
726 delete searchDlg;
727 return;
728 }
729
730 connect(searchDlg, &MythUISearchDialog::haveResult, this, &Ripper::setSaveHost);
731
732 popupStack->AddScreen(searchDlg);
733}
734
735void Ripper::setSaveHost(const QString& host)
736{
737 gCoreContext->SaveSetting("MythMusicLastRipHost", host);
738
739 QStringList dirs = StorageGroup::getGroupDirs("Music", host);
740 if (dirs.count() > 0)
741 m_musicStorageDir = StorageGroup::getGroupDirs("Music", host).at(0);
742}
743
745{
746 if (m_scanThread)
747 return;
748
749 QString message = tr("Scanning CD. Please Wait ...");
750 OpenBusyPopup(message);
751
752 m_scanThread = new CDScannerThread(this);
753 connect(m_scanThread->qthread(), &QThread::finished, this, &Ripper::ScanFinished);
755}
756
758{
759 delete m_scanThread;
760 m_scanThread = nullptr;
761
762 m_tracks->clear();
763
764 if (m_decoder)
765 {
766 bool isCompilation = false;
767
768 m_artistName.clear();
769 m_albumName.clear();
770 m_genreName.clear();
771 m_year.clear();
772
773 int max_tracks = m_decoder->getNumTracks();
774 for (int trackno = 0; trackno < max_tracks; trackno++)
775 {
776 auto *ripTrack = new RipTrack;
777
778 MusicMetadata *metadata = m_decoder->getMetadata(trackno + 1);
779 if (metadata)
780 {
781 ripTrack->metadata = metadata;
782 ripTrack->length = metadata->Length();
783
784 if (metadata->Compilation())
785 {
786 isCompilation = true;
787 m_artistName = metadata->CompilationArtist();
788 }
789 else if (m_artistName.isEmpty())
790 {
791 m_artistName = metadata->Artist();
792 }
793
794 if (m_albumName.isEmpty())
795 m_albumName = metadata->Album();
796
797 if (m_genreName.isEmpty() && !metadata->Genre().isEmpty())
798 m_genreName = metadata->Genre();
799
800 if (m_year.isEmpty() && metadata->Year() > 0)
801 m_year = QString::number(metadata->Year());
802
803 QString title = metadata->Title();
804 ripTrack->isNew = isNewTune(m_artistName, m_albumName, title);
805
806 ripTrack->active = ripTrack->isNew;
807
808 m_tracks->push_back(ripTrack);
809
810 }
811 else
812 {
813 delete ripTrack;
814 }
815 }
816
821 m_compilationCheck->SetCheckState(isCompilation);
822
823 if (!isCompilation)
825 else
827 }
828
831
833}
834
836{
837#ifdef HAVE_CDIO
838 {
839 LOG(VB_MEDIA, LOG_INFO, QString("Ripper::%1 CD='%2'").
840 arg(__func__, m_cdDevice));
841 (void)cdio_close_tray(m_cdDevice.toLatin1().constData(), nullptr);
842 }
843#endif // HAVE_CDIO
844
845 delete m_decoder;
846 m_decoder = new CdDecoder("cda", nullptr, nullptr);
847 if (m_decoder)
849}
850
852{
853 // NOLINTNEXTLINE(readability-qualified-auto) // qt6
854 for (auto it = m_tracks->begin(); it < m_tracks->end(); ++it)
855 {
856 RipTrack *track = (*it);
857 if (track && !track->isNew)
858 {
859 if (deleteExistingTrack(track))
860 {
861 track->isNew = true;
862 toggleTrackActive(track);
863 }
864 }
865 }
866}
867
869{
870 if (!track)
871 return false;
872
873 MusicMetadata *metadata = track->metadata;
874
875 if (!metadata)
876 return false;
877
878 QString artist = metadata->Artist();
879 QString album = metadata->Album();
880 QString title = metadata->Title();
881
883 QString queryString("SELECT song_id, "
884 "CONCAT_WS('/', music_directories.path, music_songs.filename) AS filename "
885 "FROM music_songs "
886 "LEFT JOIN music_artists"
887 " ON music_songs.artist_id=music_artists.artist_id "
888 "LEFT JOIN music_albums"
889 " ON music_songs.album_id=music_albums.album_id "
890 "LEFT JOIN music_directories "
891 " ON music_songs.directory_id=music_directories.directory_id "
892 "WHERE artist_name REGEXP \'");
893 QString token = artist;
894 static const QRegularExpression punctuation
895 { R"((/|\\|:|'|\,|\!|\‍(|\)|"|\?|\|))" };
896 token.replace(punctuation, QString("."));
897 queryString += token + "\' AND " + "album_name REGEXP \'";
898 token = album;
899 token.replace(punctuation, QString("."));
900 queryString += token + "\' AND " + "name REGEXP \'";
901 token = title;
902 token.replace(punctuation, QString("."));
903 queryString += token + "\' ORDER BY artist_name, album_name,"
904 " name, song_id, filename LIMIT 1";
905 query.prepare(queryString);
906
907 if (!query.exec() || !query.isActive())
908 {
909 MythDB::DBError("Search music database", query);
910 return false;
911 }
912
913 if (query.next())
914 {
915 int trackID = query.value(0).toInt();
916 QString filename = query.value(1).toString();
917 QUrl url(m_musicStorageDir);
918 filename = MythCoreContext::GenMythURL(url.host(), 0, filename, "Music");
919
920 // delete file
921 // FIXME: RemoteFile::DeleteFile will only work with files on the master BE
923 {
924 LOG(VB_GENERAL, LOG_NOTICE, QString("Ripper::deleteExistingTrack() "
925 "Could not delete %1")
926 .arg(filename));
927 return false;
928 }
929
930 // remove database entry
931 MSqlQuery deleteQuery(MSqlQuery::InitCon());
932 deleteQuery.prepare("DELETE FROM music_songs"
933 " WHERE song_id = :SONG_ID");
934 deleteQuery.bindValue(":SONG_ID", trackID);
935 if (!deleteQuery.exec())
936 {
937 MythDB::DBError("Delete Track", deleteQuery);
938 return false;
939 }
940 return true;
941 }
942
943 return false;
944}
945
947{
949}
950
952{
953 QString newartist = m_artistEdit->GetText();
954
955 if (!m_tracks->empty())
956 {
957 for (const auto *track : std::as_const(*m_tracks))
958 {
959 MusicMetadata *data = track->metadata;
960 if (data)
961 {
963 {
964 data->setCompilationArtist(newartist);
965 }
966 else
967 {
968 data->setArtist(newartist);
969 data->setCompilationArtist("");
970 }
971 }
972 }
973
975 }
976
977 m_artistName = newartist;
978}
979
981{
982 QString newalbum = m_albumEdit->GetText();
983
984 if (!m_tracks->empty())
985 {
986 for (const auto *track : std::as_const(*m_tracks))
987 {
988 MusicMetadata *data = track->metadata;
989 if (data)
990 data->setAlbum(newalbum);
991 }
992 }
993
994 m_albumName = newalbum;
995}
996
998{
999 QString newgenre = m_genreEdit->GetText();
1000
1001 if (!m_tracks->empty())
1002 {
1003 for (const auto *track : std::as_const(*m_tracks))
1004 {
1005 MusicMetadata *data = track->metadata;
1006 if (data)
1007 data->setGenre(newgenre);
1008 }
1009 }
1010
1011 m_genreName = newgenre;
1012}
1013
1015{
1016 QString newyear = m_yearEdit->GetText();
1017
1018 if (!m_tracks->empty())
1019 {
1020 for (const auto *track : std::as_const(*m_tracks))
1021 {
1022 MusicMetadata *data = track->metadata;
1023 if (data)
1024 data->setYear(newyear.toInt());
1025 }
1026 }
1027
1028 m_year = newyear;
1029}
1030
1032{
1033 if (!state)
1034 {
1035 if (!m_tracks->empty())
1036 {
1037 // Update artist MetaData of each track on the ablum...
1038 for (const auto *track : std::as_const(*m_tracks))
1039 {
1040 MusicMetadata *data = track->metadata;
1041 if (data)
1042 {
1043 data->setCompilationArtist("");
1044 data->setArtist(m_artistName);
1045 data->setCompilation(false);
1046 }
1047 }
1048 }
1049
1051 }
1052 else
1053 {
1054 if (!m_tracks->empty())
1055 {
1056 // Update artist MetaData of each track on the album...
1057 for (const auto *track : std::as_const(*m_tracks))
1058 {
1059 MusicMetadata *data = track->metadata;
1060
1061 if (data)
1062 {
1064 data->setCompilation(true);
1065 }
1066 }
1067 }
1068
1070 }
1071
1074}
1075
1077{
1079 return;
1080
1081 // Switch title and artist for each track
1082 QString tmp;
1083 if (!m_tracks->empty())
1084 {
1085 for (const auto *track : std::as_const(*m_tracks))
1086 {
1087 MusicMetadata *data = track->metadata;
1088
1089 if (data)
1090 {
1091 tmp = data->Artist();
1092 data->setArtist(data->Title());
1093 data->setTitle(tmp);
1094 }
1095 }
1096
1098 }
1099}
1100
1102{
1103 if (m_tracks->isEmpty())
1104 {
1105 ShowOkPopup(tr("There are no tracks to rip?"));
1106 return;
1107 }
1108
1110
1111 int quality = m_qualityList->GetItemCurrent()->GetData().toInt();
1112
1113 auto *statusDialog = new RipStatus(mainStack, m_cdDevice, m_tracks, quality);
1114
1115 if (statusDialog->Create())
1116 {
1117 connect(statusDialog, &RipStatus::Result, this, &Ripper::RipComplete);
1118 mainStack->AddScreen(statusDialog);
1119 }
1120 else
1121 {
1122 delete statusDialog;
1123 }
1124}
1125
1126void Ripper::RipComplete(bool result)
1127{
1128 if (result)
1129 {
1130 bool EjectCD = gCoreContext->GetBoolSetting("EjectCDAfterRipping", true);
1131 if (EjectCD)
1132 startEjectCD();
1133
1134 ShowOkPopup(tr("Rip completed successfully."));
1135
1136 m_somethingwasripped = true;
1137 }
1138
1139 if (LCD *lcd = LCD::Get())
1140 lcd->switchToTime();
1141}
1142
1143
1145{
1146 if (m_ejectThread)
1147 return;
1148
1149 QString message = tr("Ejecting CD. Please Wait ...");
1150
1151 OpenBusyPopup(message);
1152
1153 m_ejectThread = new CDEjectorThread(this);
1154 connect(m_ejectThread->qthread(),
1155 &QThread::finished, this, &Ripper::EjectFinished);
1157}
1158
1160{
1161 delete m_ejectThread;
1162 m_ejectThread = nullptr;
1163
1165}
1166
1168{
1169 LOG(VB_MEDIA, LOG_INFO, __PRETTY_FUNCTION__);
1170 bool bEjectCD = gCoreContext->GetBoolSetting("EjectCDAfterRipping",true);
1171 if (bEjectCD)
1172 {
1173#ifdef HAVE_CDIO
1174 LOG(VB_MEDIA, LOG_INFO, QString("Ripper::%1 '%2'").
1175 arg(__func__, m_cdDevice));
1176 (void)cdio_eject_media_drive(m_cdDevice.toLatin1().constData());
1177#else
1179 if (mon)
1180 {
1181 QByteArray devname = m_cdDevice.toLatin1();
1182 MythMediaDevice *pMedia = mon->GetMedia(devname.constData());
1183 if (pMedia && mon->ValidateAndLock(pMedia))
1184 {
1185 pMedia->eject();
1186 mon->Unlock(pMedia);
1187 }
1188 }
1189#endif // HAVE_CDIO
1190 }
1191}
1192
1194{
1195 if (m_tracks->isEmpty())
1196 return;
1197
1198 if (m_trackList)
1199 {
1200 m_trackList->Reset();
1201
1202 for (int i = 0; i < m_tracks->size(); i++)
1203 {
1204 if (i >= m_tracks->size())
1205 break;
1206
1207 RipTrack *track = m_tracks->at(i);
1208 MusicMetadata *metadata = track->metadata;
1209
1210 auto *item = new MythUIButtonListItem(m_trackList,"");
1211
1212 item->setCheckable(true);
1213
1214 item->SetData(QVariant::fromValue(track));
1215
1216 if (track->isNew)
1217 item->DisplayState("new", "yes");
1218 else
1219 item->DisplayState("new", "no");
1220
1221 if (track->active)
1222 item->setChecked(MythUIButtonListItem::FullChecked);
1223 else
1224 item->setChecked(MythUIButtonListItem::NotChecked);
1225
1226 item->SetText(QString::number(metadata->Track()), "track");
1227 item->SetText(metadata->Title(), "title");
1228 item->SetText(metadata->Artist(), "artist");
1229
1230 if (track->length >= 1s)
1231 {
1232 item->SetText(MythDate::formatTime(track->length, "mm:ss"), "length");
1233 }
1234 else
1235 {
1236 item->SetText("", "length");
1237 }
1238
1239// if (i == m_currentTrack)
1240// m_trackList->SetItemCurrent(i);
1241 }
1242 }
1243}
1244
1246{
1247 QString msg = tr("Select an Artist");
1248 QStringList searchList = MusicMetadata::fillFieldList("artist");
1249
1250 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1251 auto *searchDlg = new MythUISearchDialog(popupStack, msg, searchList, false, "");
1252
1253 if (!searchDlg->Create())
1254 {
1255 delete searchDlg;
1256 return;
1257 }
1258
1259 connect(searchDlg, &MythUISearchDialog::haveResult, this, &Ripper::setArtist);
1260
1261 popupStack->AddScreen(searchDlg);
1262}
1263
1264void Ripper::setArtist(const QString& artist)
1265{
1266 m_artistEdit->SetText(artist);
1267}
1268
1270{
1271 QString msg = tr("Select an Album");
1272 QStringList searchList = MusicMetadata::fillFieldList("album");
1273
1274 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1275 auto *searchDlg = new MythUISearchDialog(popupStack, msg, searchList, false, "");
1276
1277 if (!searchDlg->Create())
1278 {
1279 delete searchDlg;
1280 return;
1281 }
1282
1283 connect(searchDlg, &MythUISearchDialog::haveResult, this, &Ripper::setAlbum);
1284
1285 popupStack->AddScreen(searchDlg);
1286}
1287
1288void Ripper::setAlbum(const QString& album)
1289{
1290 m_albumEdit->SetText(album);
1291}
1292
1294{
1295 QString msg = tr("Select a Genre");
1296 QStringList searchList = MusicMetadata::fillFieldList("genre");
1297 // load genre list
1298 m_searchList.clear();
1299 for (const auto & genre : genre_table)
1300 m_searchList.push_back(QString::fromStdString(genre));
1301 m_searchList.sort();
1302
1303 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1304 auto *searchDlg = new MythUISearchDialog(popupStack, msg, searchList, false, "");
1305
1306 if (!searchDlg->Create())
1307 {
1308 delete searchDlg;
1309 return;
1310 }
1311
1312 connect(searchDlg, &MythUISearchDialog::haveResult, this, &Ripper::setGenre);
1313
1314 popupStack->AddScreen(searchDlg);
1315}
1316
1317void Ripper::setGenre(const QString& genre)
1318{
1319 m_genreEdit->SetText(genre);
1320}
1321
1323{
1324 if (!item || m_tracks->isEmpty())
1325 return;
1326
1327 auto *track = item->GetData().value<RipTrack *>();
1328
1329 if (!track)
1330 return;
1331
1332 MusicMetadata *editMeta = track->metadata;
1333
1335
1336 auto *editDialog = new EditMetadataDialog(mainStack, editMeta);
1337 editDialog->setSaveMetadataOnly();
1338
1339 if (!editDialog->Create())
1340 {
1341 delete editDialog;
1342 return;
1343 }
1344
1346
1347 mainStack->AddScreen(editDialog);
1348}
1349
1351{
1353}
1354
1356{
1357 QVariant data = QVariant::fromValue(track);
1359 if (item)
1360 {
1361 toggleTrackActive(item);
1362 }
1363}
1364
1366{
1367 if (m_tracks->isEmpty() || !item)
1368 return;
1369
1370 int pos = m_trackList->GetItemPos(item);
1371
1372 // sanity check item position
1373 if (pos < 0 || pos > m_tracks->count() - 1)
1374 return;
1375
1376 RipTrack *track = m_tracks->at(pos);
1377
1378 if (!track->active && !track->isNew)
1379 {
1380 ShowConflictMenu(track);
1381 return;
1382 }
1383
1384 track->active = !track->active;
1385
1386 if (track->active)
1388 else
1390
1392}
1393
1395{
1396 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1397
1398 QString msg = tr("This track has been disabled because it is already "
1399 "present in the database.\n"
1400 "Do you want to permanently delete the existing "
1401 "file(s)?");
1402 auto *menu = new MythDialogBox(msg, popupStack, "conflictmenu", true);
1403
1404 if (menu->Create())
1405 {
1406 popupStack->AddScreen(menu);
1407 }
1408 else
1409 {
1410 delete menu;
1411 return;
1412 }
1413
1414 menu->SetReturnEvent(this, "conflictmenu");
1415 menu->AddButton(tr("No, Cancel"));
1416 menu->AddButtonV(tr("Yes, Delete"), QVariant::fromValue(track));
1417 menu->AddButton(tr("Yes, Delete All"));
1418}
1419
1421{
1422 std::chrono::milliseconds length = 0ms;
1423
1424 // NOLINTNEXTLINE(readability-qualified-auto) // qt6
1425 for (auto it = m_tracks->end() - 1; it == m_tracks->begin(); --it)
1426 {
1427 RipTrack *track = *it;
1428 if (track->active)
1429 {
1430 track->length = length + track->metadata->Length();
1431 length = 0ms;
1432 }
1433 else
1434 {
1435 track->length = 0ms;
1436 length += track->metadata->Length();
1437 }
1438 }
1439}
1440
1441void Ripper::customEvent(QEvent* event)
1442{
1443 if (event->type() == DialogCompletionEvent::kEventType)
1444 {
1445 auto *dce = dynamic_cast<DialogCompletionEvent *>(event);
1446 if (dce == nullptr)
1447 return;
1448 if (dce->GetId() == "conflictmenu")
1449 {
1450 int buttonNum = dce->GetResult();
1451 auto *track = dce->GetData().value<RipTrack *>();
1452
1453 switch (buttonNum)
1454 {
1455 case 0:
1456 // Do nothing
1457 break;
1458 case 1:
1459 if (deleteExistingTrack(track))
1460 {
1461 track->isNew = true;
1462 toggleTrackActive(track);
1463 }
1464 break;
1465 case 2:
1467 break;
1468 default:
1469 break;
1470 }
1471 }
1472
1473 return;
1474 }
1475
1477}
1478
1479
1481
1483{
1484 delete m_ripperThread;
1485 if (LCD *lcd = LCD::Get())
1486 lcd->switchToTime();
1487}
1488
1490{
1491 if (!LoadWindowFromXML("music-ui.xml", "ripstatus", this))
1492 return false;
1493
1494 m_overallText = dynamic_cast<MythUIText *>(GetChild("overall"));
1495 m_trackText = dynamic_cast<MythUIText *>(GetChild("track"));
1496 m_statusText = dynamic_cast<MythUIText *>(GetChild("status"));
1497 m_trackPctText = dynamic_cast<MythUIText *>(GetChild("trackpct"));
1498 m_overallPctText = dynamic_cast<MythUIText *>(GetChild("overallpct"));
1499
1500 m_overallProgress = dynamic_cast<MythUIProgressBar *>(GetChild("overall_progress"));
1501 m_trackProgress = dynamic_cast<MythUIProgressBar *>(GetChild("track_progress"));
1502
1504
1505 startRip();
1506
1507 return true;
1508}
1509
1510bool RipStatus::keyPressEvent(QKeyEvent *event)
1511{
1512 if (GetFocusWidget() && GetFocusWidget()->keyPressEvent(event))
1513 return true;
1514
1515 QStringList actions;
1516 bool handled = GetMythMainWindow()->TranslateKeyPress("Global", event, actions);
1517
1518 for (int i = 0; i < actions.size() && !handled; i++)
1519 {
1520 const QString& action = actions[i];
1521 handled = true;
1522
1523
1524 if (action == "ESCAPE" &&
1526 {
1527 MythConfirmationDialog *dialog =
1528 ShowOkPopup(tr("Cancel ripping the CD?"), true);
1529 if (dialog)
1530 dialog->SetReturnEvent(this, "stop_ripping");
1531 }
1532 else
1533 {
1534 handled = false;
1535 }
1536 }
1537
1538 if (!handled && MythScreenType::keyPressEvent(event))
1539 handled = true;
1540
1541 return handled;
1542}
1543
1544void RipStatus::customEvent(QEvent *event)
1545{
1546 if (event->type() == DialogCompletionEvent::kEventType)
1547 {
1548 auto *dce = dynamic_cast<DialogCompletionEvent *>(event);
1549 if (dce == nullptr)
1550 return;
1551 if ((dce->GetId() == "stop_ripping") && (dce->GetResult() != 0))
1552 {
1555 Close();
1556 }
1557
1558 return;
1559 }
1560
1561 auto *rse = dynamic_cast<RipStatusEvent *> (event);
1562 if (!rse)
1563 return;
1564
1565 if (event->type() == RipStatusEvent::kTrackTextEvent)
1566 {
1567 if (m_trackText)
1568 m_trackText->SetText(rse->m_text);
1569 }
1570 else if (event->type() == RipStatusEvent::kOverallTextEvent)
1571 {
1572 if (m_overallText)
1573 m_overallText->SetText(rse->m_text);
1574 }
1575 else if (event->type() == RipStatusEvent::kStatusTextEvent)
1576 {
1577 if (m_statusText)
1578 m_statusText->SetText(rse->m_text);
1579 }
1580 else if (event->type() == RipStatusEvent::kTrackProgressEvent)
1581 {
1582 if (m_trackProgress)
1583 m_trackProgress->SetUsed(rse->m_value);
1584 }
1585 else if (event->type() == RipStatusEvent::kTrackPercentEvent)
1586 {
1587 if (m_trackPctText)
1588 m_trackPctText->SetText(QString("%1%").arg(rse->m_value));
1589 }
1590 else if (event->type() == RipStatusEvent::kTrackStartEvent)
1591 {
1592 if (m_trackProgress)
1593 m_trackProgress->SetTotal(rse->m_value);
1594 }
1595 else if (event->type() == RipStatusEvent::kCopyStartEvent)
1596 {
1597 if (m_trackPctText)
1598 m_trackPctText->SetText(tr("Copying Track ..."));
1599 }
1600 else if (event->type() == RipStatusEvent::kCopyEndEvent)
1601 {
1602 if (m_trackPctText)
1604 }
1605 else if (event->type() == RipStatusEvent::kOverallProgressEvent)
1606 {
1608 m_overallProgress->SetUsed(rse->m_value);
1609 }
1610 else if (event->type() == RipStatusEvent::kOverallStartEvent)
1611 {
1613 m_overallProgress->SetTotal(rse->m_value);
1614 }
1615 else if (event->type() == RipStatusEvent::kOverallPercentEvent)
1616 {
1617 if (m_overallPctText)
1618 m_overallPctText->SetText(QString("%1%").arg(rse->m_value));
1619 }
1620 else if (event->type() == RipStatusEvent::kFinishedEvent)
1621 {
1622 emit Result(true);
1623 Close();
1624 }
1625 else if (event->type() == RipStatusEvent::kEncoderErrorEvent)
1626 {
1627 ShowOkPopup(tr("The encoder failed to create the file.\n"
1628 "Do you have write permissions"
1629 " for the music directory?"));
1630 Close();
1631 }
1632 else
1633 {
1634 LOG(VB_GENERAL, LOG_ERR, "Received an unknown event type!");
1635 }
1636}
1637
1639{
1640 delete m_ripperThread;
1643}
1644
1645#include "moc_cdrip.cpp"
static long int getSectorCount(QString &cddevice, int tracknum)
Definition: cdrip.cpp:119
Ripper * m_parent
Definition: cdrip.h:51
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
Definition: cdrip.cpp:110
QVector< RipTrack * > * m_tracks
Definition: cdrip.h:89
~CDRipperThread() override
Definition: cdrip.cpp:184
long int m_totalSectors
Definition: cdrip.h:91
bool isCancelled(void) const
Definition: cdrip.cpp:195
QString m_musicStorageDir
Definition: cdrip.h:97
bool m_quit
Definition: cdrip.h:86
long int m_totalSectorsDone
Definition: cdrip.h:92
int m_quality
Definition: cdrip.h:88
int m_lastTrackPct
Definition: cdrip.h:94
int m_lastOverallPct
Definition: cdrip.h:95
QString m_cdDevice
Definition: cdrip.h:87
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
Definition: cdrip.cpp:200
RipStatus * m_parent
Definition: cdrip.h:85
void cancel(void)
Definition: cdrip.cpp:190
int ripTrack(QString &cddevice, Encoder *encoder, int tracknum)
Definition: cdrip.cpp:395
CDRipperThread(RipStatus *parent, QString device, QVector< RipTrack * > *tracks, int quality)
Definition: cdrip.cpp:166
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
Definition: cdrip.cpp:101
Ripper * m_parent
Definition: cdrip.h:38
void setDevice(const QString &dev)
Definition: cddecoder.cpp:108
MusicMetadata * getMetadata(void)
Definition: cddecoder.cpp:509
int getNumTracks()
Definition: cddecoder.cpp:456
Event dispatched from MythUI modal dialogs to a listening class containing a result of some form.
Definition: mythdialogbox.h:40
static const Type kEventType
Definition: mythdialogbox.h:55
void metadataChanged(void)
Definition: lcddevice.h:170
static LCD * Get(void)
Definition: lcddevice.cpp:68
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:128
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:839
QVariant value(int i) const
Definition: mythdbcon.h:204
bool isActive(void) const
Definition: mythdbcon.h:215
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:620
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:890
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:814
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:552
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
bool isRunning(void) const
Definition: mthread.cpp:247
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:180
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:267
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:193
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:284
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
Definition: mthread.cpp:217
static MediaMonitor * GetMediaMonitor(void)
bool ValidateAndLock(MythMediaDevice *pMedia)
Validates the MythMediaDevice and increments its reference count.
MythMediaDevice * GetMedia(const QString &path)
Get media device by pathname.
void Unlock(MythMediaDevice *pMedia)
decrements the MythMediaDevices reference count
void StopMonitoring(void)
Stop the monitoring thread if needed.
bool IsActive(void) const
Definition: mediamonitor.h:57
virtual void StartMonitoring(void)
Start the monitoring thread if needed.
int Year() const
void setYear(int lyear)
void setHostname(const QString &host)
void setCompilationArtist(const QString &lcompilation_artist, const QString &lcompilation_artist_sort=nullptr)
void setGenre(const QString &lgenre)
QString CompilationArtist() const
void setCompilation(bool state)
void setLength(T llength)
std::chrono::milliseconds Length() const
static QStringList fillFieldList(const QString &field)
QString Title() const
void setTitle(const QString &ltitle, const QString &ltitle_sort=nullptr)
void setFilename(const QString &lfilename)
int Track() const
QString Artist() const
void setAlbum(const QString &lalbum, const QString &lalbum_sort=nullptr)
bool Compilation() const
QString Genre() const
QString Album() const
void setArtist(const QString &lartist, const QString &lartist_sort=nullptr)
void setFileSize(uint64_t lfilesize)
void dumpToDatabase(void)
Dialog asking for user confirmation.
void SetReturnEvent(QObject *retobject, const QString &resultid)
void SaveSetting(const QString &key, int newValue)
QString GetSetting(const QString &key, const QString &defaultval="")
static QString GenMythURL(const QString &host=QString(), int port=0, QString path=QString(), const QString &storageGroup=QString())
int GetNumSetting(const QString &key, int defaultval=0)
QString GetMasterHostName(void)
bool GetBoolSetting(const QString &key, bool defaultval=false)
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:225
Basic menu dialog, message and a list of options.
MythScreenStack * GetMainStack()
bool TranslateKeyPress(const QString &Context, QKeyEvent *Event, QStringList &Actions, bool AllowJumps=true)
Get a list of actions for a keypress in the given context.
MythScreenStack * GetStack(const QString &Stackname)
virtual MythMediaError eject(bool open_close=true)
Definition: mythmedia.cpp:315
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
Screen in which all other widgets are contained and rendered.
void OpenBusyPopup(const QString &message="")
void BuildFocusList(void)
MythUIType * GetFocusWidget(void) const
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
void CloseBusyPopup(void)
virtual void Close()
bool Create(void) override
void setChecked(CheckState state)
List widget, displays list items in a variety of themeable arrangements and can trigger signals when ...
MythUIButtonListItem * GetItemCurrent() const
void Reset() override
Reset the widget to it's original state, should not reset changes made by the theme.
int GetItemPos(MythUIButtonListItem *item) const
MythUIButtonListItem * GetItemByData(const QVariant &data)
void itemClicked(MythUIButtonListItem *item)
void SetValueByData(const QVariant &data)
A single button widget.
Definition: mythuibutton.h:22
void Clicked()
A checkbox widget supporting three check states - on,off,half and two conditions - selected and unsel...
void SetCheckState(MythUIStateType::StateType state)
void toggled(bool)
bool GetBooleanCheckState(void) const
Progress bar widget.
void SetUsed(int value)
void SetTotal(int value)
Provide a dialog to quickly find an entry in a list.
void haveResult(QString)
A text entry and edit widget.
QString GetText(void) const
void SetText(const QString &text, bool moveCursor=true)
void SetMaxLength(int length)
void SetFilter(InputFilter filter)
void valueChanged()
All purpose text widget, displays a text string.
Definition: mythuitext.h:29
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:115
void customEvent(QEvent *event) override
Definition: mythuitype.cpp:989
virtual void SetVisible(bool visible)
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:130
static bool CopyFile(const QString &src, const QString &dst, bool overwrite=false, bool verify=false)
Definition: remotefile.cpp:589
static bool DeleteFile(const QString &url)
Definition: remotefile.cpp:420
static bool Exists(const QString &url, struct stat *fileinfo)
Definition: remotefile.cpp:463
static const Type kOverallStartEvent
Definition: cdrip.h:215
static const Type kTrackPercentEvent
Definition: cdrip.h:211
static const Type kOverallProgressEvent
Definition: cdrip.h:213
static const Type kCopyEndEvent
Definition: cdrip.h:217
static const Type kTrackTextEvent
Definition: cdrip.h:207
static const Type kOverallPercentEvent
Definition: cdrip.h:214
static const Type kStatusTextEvent
Definition: cdrip.h:209
static const Type kTrackStartEvent
Definition: cdrip.h:212
static const Type kFinishedEvent
Definition: cdrip.h:218
static const Type kOverallTextEvent
Definition: cdrip.h:208
static const Type kCopyStartEvent
Definition: cdrip.h:216
static const Type kTrackProgressEvent
Definition: cdrip.h:210
static const Type kEncoderErrorEvent
Definition: cdrip.h:219
QString m_cdDevice
Definition: cdrip.h:247
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
Definition: cdrip.cpp:1510
MythUIProgressBar * m_overallProgress
Definition: cdrip.h:254
int m_quality
Definition: cdrip.h:246
MythUIText * m_overallText
Definition: cdrip.h:249
void startRip(void)
Definition: cdrip.cpp:1638
bool Create(void) override
Definition: cdrip.cpp:1489
MythUIText * m_statusText
Definition: cdrip.h:251
void Result(bool)
CDRipperThread * m_ripperThread
Definition: cdrip.h:257
MythUIText * m_overallPctText
Definition: cdrip.h:252
void customEvent(QEvent *event) override
Definition: cdrip.cpp:1544
~RipStatus(void) override
Definition: cdrip.cpp:1482
MythUIProgressBar * m_trackProgress
Definition: cdrip.h:255
MythUIText * m_trackPctText
Definition: cdrip.h:253
MythUIText * m_trackText
Definition: cdrip.h:250
QVector< RipTrack * > * m_tracks
Definition: cdrip.h:245
MythUITextEdit * m_yearEdit
Definition: cdrip.h:164
MythUICheckBox * m_compilationCheck
Definition: cdrip.h:166
MythUIButton * m_searchArtistButton
Definition: cdrip.h:174
bool m_somethingwasripped
Definition: cdrip.h:185
MythUIButton * m_switchTitleArtist
Definition: cdrip.h:171
void metadataChanged(void)
Definition: cdrip.cpp:1350
void setArtist(const QString &artist)
Definition: cdrip.cpp:1264
MythUIButton * m_searchAlbumButton
Definition: cdrip.h:175
void chooseBackend(void) const
Definition: cdrip.cpp:692
bool deleteExistingTrack(RipTrack *track)
Definition: cdrip.cpp:868
QString m_musicStorageDir
Definition: cdrip.h:157
void ShowConflictMenu(RipTrack *track)
Definition: cdrip.cpp:1394
QString m_artistName
Definition: cdrip.h:181
void updateTrackLengths(void)
Definition: cdrip.cpp:1420
MythUITextEdit * m_artistEdit
Definition: cdrip.h:161
void artistChanged(void)
Definition: cdrip.cpp:951
void ShowMenu(void) override
Definition: cdrip.cpp:662
void setSaveHost(const QString &host)
Definition: cdrip.cpp:735
void ripFinished(void)
MythUIButtonList * m_trackList
Definition: cdrip.h:168
void switchTitlesAndArtists()
Definition: cdrip.cpp:1076
void searchAlbum(void) const
Definition: cdrip.cpp:1269
MythUIButton * m_searchGenreButton
Definition: cdrip.h:176
CDScannerThread * m_scanThread
Definition: cdrip.h:191
QString m_genreName
Definition: cdrip.h:182
void updateTrackList(void)
Definition: cdrip.cpp:1193
bool m_mediaMonitorActive
Definition: cdrip.h:186
MythUIButton * m_ripButton
Definition: cdrip.h:173
MythUITextEdit * m_genreEdit
Definition: cdrip.h:163
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
Definition: cdrip.cpp:635
void setGenre(const QString &genre)
Definition: cdrip.cpp:1317
void ScanFinished(void)
Definition: cdrip.cpp:757
bool Create(void) override
Definition: cdrip.cpp:573
bool somethingWasRipped() const
Definition: cdrip.cpp:946
void deleteAllExistingTracks(void)
Definition: cdrip.cpp:851
~Ripper(void) override
Definition: cdrip.cpp:551
MythUITextEdit * m_albumEdit
Definition: cdrip.h:162
MythUIButton * m_scanButton
Definition: cdrip.h:172
CDEjectorThread * m_ejectThread
Definition: cdrip.h:190
QStringList m_searchList
Definition: cdrip.h:184
void showEditMetadataDialog(void)
Definition: cdrip.cpp:687
void toggleTrackActive(MythUIButtonListItem *item)
Definition: cdrip.cpp:1365
void genreChanged(void)
Definition: cdrip.cpp:997
MythUIButtonList * m_qualityList
Definition: cdrip.h:169
void setAlbum(const QString &album)
Definition: cdrip.cpp:1288
void searchArtist(void) const
Definition: cdrip.cpp:1245
QString m_cdDevice
Definition: cdrip.h:188
void yearChanged(void)
Definition: cdrip.cpp:1014
void customEvent(QEvent *event) override
Definition: cdrip.cpp:1441
void compilationChanged(bool state)
Definition: cdrip.cpp:1031
void RipComplete(bool result)
Definition: cdrip.cpp:1126
void searchGenre(void)
Definition: cdrip.cpp:1293
void scanCD(void)
Definition: cdrip.cpp:835
void EjectFinished(void)
Definition: cdrip.cpp:1159
QVector< RipTrack * > * m_tracks
Definition: cdrip.h:178
void startRipper(void)
Definition: cdrip.cpp:1101
Ripper(MythScreenStack *parent, QString device)
Definition: cdrip.cpp:521
void startEjectCD(void)
Definition: cdrip.cpp:1144
QString m_albumName
Definition: cdrip.h:180
QString m_year
Definition: cdrip.h:183
void albumChanged(void)
Definition: cdrip.cpp:980
CdDecoder * m_decoder
Definition: cdrip.h:159
void startScanCD(void)
Definition: cdrip.cpp:744
void ejectCD(void)
Definition: cdrip.cpp:1167
static QStringList getGroupDirs(const QString &groupname, const QString &host)
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
const genre_table_array genre_table
Definition: genres.cpp:4
@ ALIGN_CENTERED
Definition: lcddevice.h:57
bool isNewTune(const QString &artist, const QString &album, const QString &title)
try to find a track in the db using the given artist, album and title
Definition: musicutils.cpp:126
QString filenameFromMetadata(MusicMetadata *track)
create a filename using the template in the settings and a MusicMetadata object
Definition: musicutils.cpp:78
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythConfirmationDialog * ShowOkPopup(const QString &message, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
QString GetConfDir(void)
Definition: mythdirs.cpp:282
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythMainWindow * GetMythMainWindow(void)
uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
static MythThemedMenu * menu
InputFilter
@ FilterAlpha
@ FilterPunct
@ FilterSymbols
QString formatTime(std::chrono::milliseconds msecs, QString fmt)
Format a milliseconds time value.
Definition: mythdate.cpp:242
Definition: cdrip.h:55
bool isNew
Definition: cdrip.h:59
std::chrono::milliseconds length
Definition: cdrip.h:58
bool active
Definition: cdrip.h:57
MusicMetadata * metadata
Definition: cdrip.h:56