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,0,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 popupStack->AddScreen(menu);
673 else
674 {
675 delete menu;
676 return;
677 }
678
679 menu->SetReturnEvent(this, "menu");
680 menu->AddButton(tr("Select Where To Save Tracks"), &Ripper::chooseBackend);
681 menu->AddButton(tr("Edit Track Metadata"),
682 qOverload<>(&Ripper::showEditMetadataDialog));
683}
684
686{
688}
689
690void Ripper::chooseBackend(void) const
691{
692 QStringList hostList;
693
694 // get a list of hosts with a directory defined for the 'Music' storage group
696 QString sql = "SELECT DISTINCT hostname "
697 "FROM storagegroup "
698 "WHERE groupname = 'Music'";
699 if (!query.exec(sql) || !query.isActive())
700 MythDB::DBError("Ripper::chooseBackend get host list", query);
701 else
702 {
703 while(query.next())
704 {
705 hostList.append(query.value(0).toString());
706 }
707 }
708
709 if (hostList.isEmpty())
710 {
711 LOG(VB_GENERAL, LOG_ERR, "Ripper::chooseBackend: No backends found");
712 return;
713 }
714
715 QString msg = tr("Select where to save tracks");
716
717 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
718 auto *searchDlg = new MythUISearchDialog(popupStack, msg, hostList, false, "");
719
720 if (!searchDlg->Create())
721 {
722 delete searchDlg;
723 return;
724 }
725
726 connect(searchDlg, &MythUISearchDialog::haveResult, this, &Ripper::setSaveHost);
727
728 popupStack->AddScreen(searchDlg);
729}
730
731void Ripper::setSaveHost(const QString& host)
732{
733 gCoreContext->SaveSetting("MythMusicLastRipHost", host);
734
735 QStringList dirs = StorageGroup::getGroupDirs("Music", host);
736 if (dirs.count() > 0)
737 m_musicStorageDir = StorageGroup::getGroupDirs("Music", host).at(0);
738}
739
741{
742 if (m_scanThread)
743 return;
744
745 QString message = tr("Scanning CD. Please Wait ...");
746 OpenBusyPopup(message);
747
748 m_scanThread = new CDScannerThread(this);
749 connect(m_scanThread->qthread(), &QThread::finished, this, &Ripper::ScanFinished);
751}
752
754{
755 delete m_scanThread;
756 m_scanThread = nullptr;
757
758 m_tracks->clear();
759
760 if (m_decoder)
761 {
762 bool isCompilation = false;
763
764 m_artistName.clear();
765 m_albumName.clear();
766 m_genreName.clear();
767 m_year.clear();
768
769 int max_tracks = m_decoder->getNumTracks();
770 for (int trackno = 0; trackno < max_tracks; trackno++)
771 {
772 auto *ripTrack = new RipTrack;
773
774 MusicMetadata *metadata = m_decoder->getMetadata(trackno + 1);
775 if (metadata)
776 {
777 ripTrack->metadata = metadata;
778 ripTrack->length = metadata->Length();
779
780 if (metadata->Compilation())
781 {
782 isCompilation = true;
783 m_artistName = metadata->CompilationArtist();
784 }
785 else if (m_artistName.isEmpty())
786 {
787 m_artistName = metadata->Artist();
788 }
789
790 if (m_albumName.isEmpty())
791 m_albumName = metadata->Album();
792
793 if (m_genreName.isEmpty() && !metadata->Genre().isEmpty())
794 m_genreName = metadata->Genre();
795
796 if (m_year.isEmpty() && metadata->Year() > 0)
797 m_year = QString::number(metadata->Year());
798
799 QString title = metadata->Title();
800 ripTrack->isNew = isNewTune(m_artistName, m_albumName, title);
801
802 ripTrack->active = ripTrack->isNew;
803
804 m_tracks->push_back(ripTrack);
805
806 }
807 else
808 {
809 delete ripTrack;
810 }
811 }
812
817 m_compilationCheck->SetCheckState(isCompilation);
818
819 if (!isCompilation)
821 else
823 }
824
827
829}
830
832{
833#ifdef HAVE_CDIO
834 {
835 LOG(VB_MEDIA, LOG_INFO, QString("Ripper::%1 CD='%2'").
836 arg(__func__, m_cdDevice));
837 (void)cdio_close_tray(m_cdDevice.toLatin1().constData(), nullptr);
838 }
839#endif // HAVE_CDIO
840
841 delete m_decoder;
842 m_decoder = new CdDecoder("cda", nullptr, nullptr);
843 if (m_decoder)
845}
846
848{
849 // NOLINTNEXTLINE(readability-qualified-auto) // qt6
850 for (auto it = m_tracks->begin(); it < m_tracks->end(); ++it)
851 {
852 RipTrack *track = (*it);
853 if (track && !track->isNew)
854 {
855 if (deleteExistingTrack(track))
856 {
857 track->isNew = true;
858 toggleTrackActive(track);
859 }
860 }
861 }
862}
863
865{
866 if (!track)
867 return false;
868
869 MusicMetadata *metadata = track->metadata;
870
871 if (!metadata)
872 return false;
873
874 QString artist = metadata->Artist();
875 QString album = metadata->Album();
876 QString title = metadata->Title();
877
879 QString queryString("SELECT song_id, "
880 "CONCAT_WS('/', music_directories.path, music_songs.filename) AS filename "
881 "FROM music_songs "
882 "LEFT JOIN music_artists"
883 " ON music_songs.artist_id=music_artists.artist_id "
884 "LEFT JOIN music_albums"
885 " ON music_songs.album_id=music_albums.album_id "
886 "LEFT JOIN music_directories "
887 " ON music_songs.directory_id=music_directories.directory_id "
888 "WHERE artist_name REGEXP \'");
889 QString token = artist;
890 static const QRegularExpression punctuation
891 { R"((/|\\|:|'|\,|\!|\‍(|\)|"|\?|\|))" };
892 token.replace(punctuation, QString("."));
893 queryString += token + "\' AND " + "album_name REGEXP \'";
894 token = album;
895 token.replace(punctuation, QString("."));
896 queryString += token + "\' AND " + "name REGEXP \'";
897 token = title;
898 token.replace(punctuation, QString("."));
899 queryString += token + "\' ORDER BY artist_name, album_name,"
900 " name, song_id, filename LIMIT 1";
901 query.prepare(queryString);
902
903 if (!query.exec() || !query.isActive())
904 {
905 MythDB::DBError("Search music database", query);
906 return false;
907 }
908
909 if (query.next())
910 {
911 int trackID = query.value(0).toInt();
912 QString filename = query.value(1).toString();
913 QUrl url(m_musicStorageDir);
914 filename = MythCoreContext::GenMythURL(url.host(), 0, filename, "Music");
915
916 // delete file
917 // FIXME: RemoteFile::DeleteFile will only work with files on the master BE
919 {
920 LOG(VB_GENERAL, LOG_NOTICE, QString("Ripper::deleteExistingTrack() "
921 "Could not delete %1")
922 .arg(filename));
923 return false;
924 }
925
926 // remove database entry
927 MSqlQuery deleteQuery(MSqlQuery::InitCon());
928 deleteQuery.prepare("DELETE FROM music_songs"
929 " WHERE song_id = :SONG_ID");
930 deleteQuery.bindValue(":SONG_ID", trackID);
931 if (!deleteQuery.exec())
932 {
933 MythDB::DBError("Delete Track", deleteQuery);
934 return false;
935 }
936 return true;
937 }
938
939 return false;
940}
941
943{
945}
946
948{
949 QString newartist = m_artistEdit->GetText();
950
951 if (!m_tracks->empty())
952 {
953 for (const auto *track : std::as_const(*m_tracks))
954 {
955 MusicMetadata *data = track->metadata;
956 if (data)
957 {
959 {
960 data->setCompilationArtist(newartist);
961 }
962 else
963 {
964 data->setArtist(newartist);
965 data->setCompilationArtist("");
966 }
967 }
968 }
969
971 }
972
973 m_artistName = newartist;
974}
975
977{
978 QString newalbum = m_albumEdit->GetText();
979
980 if (!m_tracks->empty())
981 {
982 for (const auto *track : std::as_const(*m_tracks))
983 {
984 MusicMetadata *data = track->metadata;
985 if (data)
986 data->setAlbum(newalbum);
987 }
988 }
989
990 m_albumName = newalbum;
991}
992
994{
995 QString newgenre = m_genreEdit->GetText();
996
997 if (!m_tracks->empty())
998 {
999 for (const auto *track : std::as_const(*m_tracks))
1000 {
1001 MusicMetadata *data = track->metadata;
1002 if (data)
1003 data->setGenre(newgenre);
1004 }
1005 }
1006
1007 m_genreName = newgenre;
1008}
1009
1011{
1012 QString newyear = m_yearEdit->GetText();
1013
1014 if (!m_tracks->empty())
1015 {
1016 for (const auto *track : std::as_const(*m_tracks))
1017 {
1018 MusicMetadata *data = track->metadata;
1019 if (data)
1020 data->setYear(newyear.toInt());
1021 }
1022 }
1023
1024 m_year = newyear;
1025}
1026
1028{
1029 if (!state)
1030 {
1031 if (!m_tracks->empty())
1032 {
1033 // Update artist MetaData of each track on the ablum...
1034 for (const auto *track : std::as_const(*m_tracks))
1035 {
1036 MusicMetadata *data = track->metadata;
1037 if (data)
1038 {
1039 data->setCompilationArtist("");
1040 data->setArtist(m_artistName);
1041 data->setCompilation(false);
1042 }
1043 }
1044 }
1045
1047 }
1048 else
1049 {
1050 if (!m_tracks->empty())
1051 {
1052 // Update artist MetaData of each track on the album...
1053 for (const auto *track : std::as_const(*m_tracks))
1054 {
1055 MusicMetadata *data = track->metadata;
1056
1057 if (data)
1058 {
1060 data->setCompilation(true);
1061 }
1062 }
1063 }
1064
1066 }
1067
1070}
1071
1073{
1075 return;
1076
1077 // Switch title and artist for each track
1078 QString tmp;
1079 if (!m_tracks->empty())
1080 {
1081 for (const auto *track : std::as_const(*m_tracks))
1082 {
1083 MusicMetadata *data = track->metadata;
1084
1085 if (data)
1086 {
1087 tmp = data->Artist();
1088 data->setArtist(data->Title());
1089 data->setTitle(tmp);
1090 }
1091 }
1092
1094 }
1095}
1096
1098{
1099 if (m_tracks->isEmpty())
1100 {
1101 ShowOkPopup(tr("There are no tracks to rip?"));
1102 return;
1103 }
1104
1106
1107 int quality = m_qualityList->GetItemCurrent()->GetData().toInt();
1108
1109 auto *statusDialog = new RipStatus(mainStack, m_cdDevice, m_tracks, quality);
1110
1111 if (statusDialog->Create())
1112 {
1113 connect(statusDialog, &RipStatus::Result, this, &Ripper::RipComplete);
1114 mainStack->AddScreen(statusDialog);
1115 }
1116 else
1117 {
1118 delete statusDialog;
1119 }
1120}
1121
1122void Ripper::RipComplete(bool result)
1123{
1124 if (result)
1125 {
1126 bool EjectCD = gCoreContext->GetBoolSetting("EjectCDAfterRipping", true);
1127 if (EjectCD)
1128 startEjectCD();
1129
1130 ShowOkPopup(tr("Rip completed successfully."));
1131
1132 m_somethingwasripped = true;
1133 }
1134
1135 if (LCD *lcd = LCD::Get())
1136 lcd->switchToTime();
1137}
1138
1139
1141{
1142 if (m_ejectThread)
1143 return;
1144
1145 QString message = tr("Ejecting CD. Please Wait ...");
1146
1147 OpenBusyPopup(message);
1148
1149 m_ejectThread = new CDEjectorThread(this);
1150 connect(m_ejectThread->qthread(),
1151 &QThread::finished, this, &Ripper::EjectFinished);
1153}
1154
1156{
1157 delete m_ejectThread;
1158 m_ejectThread = nullptr;
1159
1161}
1162
1164{
1165 LOG(VB_MEDIA, LOG_INFO, __PRETTY_FUNCTION__);
1166 bool bEjectCD = gCoreContext->GetBoolSetting("EjectCDAfterRipping",true);
1167 if (bEjectCD)
1168 {
1169#ifdef HAVE_CDIO
1170 LOG(VB_MEDIA, LOG_INFO, QString("Ripper::%1 '%2'").
1171 arg(__func__, m_cdDevice));
1172 (void)cdio_eject_media_drive(m_cdDevice.toLatin1().constData());
1173#else
1175 if (mon)
1176 {
1177 QByteArray devname = m_cdDevice.toLatin1();
1178 MythMediaDevice *pMedia = mon->GetMedia(devname.constData());
1179 if (pMedia && mon->ValidateAndLock(pMedia))
1180 {
1181 pMedia->eject();
1182 mon->Unlock(pMedia);
1183 }
1184 }
1185#endif // HAVE_CDIO
1186 }
1187}
1188
1190{
1191 if (m_tracks->isEmpty())
1192 return;
1193
1194 if (m_trackList)
1195 {
1196 m_trackList->Reset();
1197
1198 for (int i = 0; i < m_tracks->size(); i++)
1199 {
1200 if (i >= m_tracks->size())
1201 break;
1202
1203 RipTrack *track = m_tracks->at(i);
1204 MusicMetadata *metadata = track->metadata;
1205
1206 auto *item = new MythUIButtonListItem(m_trackList,"");
1207
1208 item->setCheckable(true);
1209
1210 item->SetData(QVariant::fromValue(track));
1211
1212 if (track->isNew)
1213 item->DisplayState("new", "yes");
1214 else
1215 item->DisplayState("new", "no");
1216
1217 if (track->active)
1218 item->setChecked(MythUIButtonListItem::FullChecked);
1219 else
1220 item->setChecked(MythUIButtonListItem::NotChecked);
1221
1222 item->SetText(QString::number(metadata->Track()), "track");
1223 item->SetText(metadata->Title(), "title");
1224 item->SetText(metadata->Artist(), "artist");
1225
1226 if (track->length >= 1s)
1227 {
1228 item->SetText(MythDate::formatTime(track->length, "mm:ss"), "length");
1229 }
1230 else
1231 {
1232 item->SetText("", "length");
1233 }
1234
1235// if (i == m_currentTrack)
1236// m_trackList->SetItemCurrent(i);
1237 }
1238 }
1239}
1240
1242{
1243 QString msg = tr("Select an Artist");
1244 QStringList searchList = MusicMetadata::fillFieldList("artist");
1245
1246 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1247 auto *searchDlg = new MythUISearchDialog(popupStack, msg, searchList, false, "");
1248
1249 if (!searchDlg->Create())
1250 {
1251 delete searchDlg;
1252 return;
1253 }
1254
1255 connect(searchDlg, &MythUISearchDialog::haveResult, this, &Ripper::setArtist);
1256
1257 popupStack->AddScreen(searchDlg);
1258}
1259
1260void Ripper::setArtist(const QString& artist)
1261{
1262 m_artistEdit->SetText(artist);
1263}
1264
1266{
1267 QString msg = tr("Select an Album");
1268 QStringList searchList = MusicMetadata::fillFieldList("album");
1269
1270 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1271 auto *searchDlg = new MythUISearchDialog(popupStack, msg, searchList, false, "");
1272
1273 if (!searchDlg->Create())
1274 {
1275 delete searchDlg;
1276 return;
1277 }
1278
1279 connect(searchDlg, &MythUISearchDialog::haveResult, this, &Ripper::setAlbum);
1280
1281 popupStack->AddScreen(searchDlg);
1282}
1283
1284void Ripper::setAlbum(const QString& album)
1285{
1286 m_albumEdit->SetText(album);
1287}
1288
1290{
1291 QString msg = tr("Select a Genre");
1292 QStringList searchList = MusicMetadata::fillFieldList("genre");
1293 // load genre list
1294 m_searchList.clear();
1295 for (const auto & genre : genre_table)
1296 m_searchList.push_back(QString::fromStdString(genre));
1297 m_searchList.sort();
1298
1299 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1300 auto *searchDlg = new MythUISearchDialog(popupStack, msg, searchList, false, "");
1301
1302 if (!searchDlg->Create())
1303 {
1304 delete searchDlg;
1305 return;
1306 }
1307
1308 connect(searchDlg, &MythUISearchDialog::haveResult, this, &Ripper::setGenre);
1309
1310 popupStack->AddScreen(searchDlg);
1311}
1312
1313void Ripper::setGenre(const QString& genre)
1314{
1315 m_genreEdit->SetText(genre);
1316}
1317
1319{
1320 if (!item || m_tracks->isEmpty())
1321 return;
1322
1323 auto *track = item->GetData().value<RipTrack *>();
1324
1325 if (!track)
1326 return;
1327
1328 MusicMetadata *editMeta = track->metadata;
1329
1331
1332 auto *editDialog = new EditMetadataDialog(mainStack, editMeta);
1333 editDialog->setSaveMetadataOnly();
1334
1335 if (!editDialog->Create())
1336 {
1337 delete editDialog;
1338 return;
1339 }
1340
1342
1343 mainStack->AddScreen(editDialog);
1344}
1345
1347{
1349}
1350
1352{
1353 QVariant data = QVariant::fromValue(track);
1355 if (item)
1356 {
1357 toggleTrackActive(item);
1358 }
1359}
1360
1362{
1363 if (m_tracks->isEmpty() || !item)
1364 return;
1365
1366 int pos = m_trackList->GetItemPos(item);
1367
1368 // sanity check item position
1369 if (pos < 0 || pos > m_tracks->count() - 1)
1370 return;
1371
1372 RipTrack *track = m_tracks->at(pos);
1373
1374 if (!track->active && !track->isNew)
1375 {
1376 ShowConflictMenu(track);
1377 return;
1378 }
1379
1380 track->active = !track->active;
1381
1382 if (track->active)
1384 else
1386
1388}
1389
1391{
1392 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1393
1394 QString msg = tr("This track has been disabled because it is already "
1395 "present in the database.\n"
1396 "Do you want to permanently delete the existing "
1397 "file(s)?");
1398 auto *menu = new MythDialogBox(msg, popupStack, "conflictmenu", true);
1399
1400 if (menu->Create())
1401 popupStack->AddScreen(menu);
1402 else
1403 {
1404 delete menu;
1405 return;
1406 }
1407
1408 menu->SetReturnEvent(this, "conflictmenu");
1409 menu->AddButton(tr("No, Cancel"));
1410 menu->AddButtonV(tr("Yes, Delete"), QVariant::fromValue(track));
1411 menu->AddButton(tr("Yes, Delete All"));
1412}
1413
1415{
1416 std::chrono::milliseconds length = 0ms;
1417
1418 // NOLINTNEXTLINE(readability-qualified-auto) // qt6
1419 for (auto it = m_tracks->end() - 1; it == m_tracks->begin(); --it)
1420 {
1421 RipTrack *track = *it;
1422 if (track->active)
1423 {
1424 track->length = length + track->metadata->Length();
1425 length = 0ms;
1426 }
1427 else
1428 {
1429 track->length = 0ms;
1430 length += track->metadata->Length();
1431 }
1432 }
1433}
1434
1435void Ripper::customEvent(QEvent* event)
1436{
1437 if (event->type() == DialogCompletionEvent::kEventType)
1438 {
1439 auto *dce = dynamic_cast<DialogCompletionEvent *>(event);
1440 if (dce == nullptr)
1441 return;
1442 if (dce->GetId() == "conflictmenu")
1443 {
1444 int buttonNum = dce->GetResult();
1445 auto *track = dce->GetData().value<RipTrack *>();
1446
1447 switch (buttonNum)
1448 {
1449 case 0:
1450 // Do nothing
1451 break;
1452 case 1:
1453 if (deleteExistingTrack(track))
1454 {
1455 track->isNew = true;
1456 toggleTrackActive(track);
1457 }
1458 break;
1459 case 2:
1461 break;
1462 default:
1463 break;
1464 }
1465 }
1466
1467 return;
1468 }
1469
1471}
1472
1473
1475
1477{
1478 delete m_ripperThread;
1479 if (LCD *lcd = LCD::Get())
1480 lcd->switchToTime();
1481}
1482
1484{
1485 if (!LoadWindowFromXML("music-ui.xml", "ripstatus", this))
1486 return false;
1487
1488 m_overallText = dynamic_cast<MythUIText *>(GetChild("overall"));
1489 m_trackText = dynamic_cast<MythUIText *>(GetChild("track"));
1490 m_statusText = dynamic_cast<MythUIText *>(GetChild("status"));
1491 m_trackPctText = dynamic_cast<MythUIText *>(GetChild("trackpct"));
1492 m_overallPctText = dynamic_cast<MythUIText *>(GetChild("overallpct"));
1493
1494 m_overallProgress = dynamic_cast<MythUIProgressBar *>(GetChild("overall_progress"));
1495 m_trackProgress = dynamic_cast<MythUIProgressBar *>(GetChild("track_progress"));
1496
1498
1499 startRip();
1500
1501 return true;
1502}
1503
1504bool RipStatus::keyPressEvent(QKeyEvent *event)
1505{
1506 if (GetFocusWidget() && GetFocusWidget()->keyPressEvent(event))
1507 return true;
1508
1509 QStringList actions;
1510 bool handled = GetMythMainWindow()->TranslateKeyPress("Global", event, actions);
1511
1512 for (int i = 0; i < actions.size() && !handled; i++)
1513 {
1514 const QString& action = actions[i];
1515 handled = true;
1516
1517
1518 if (action == "ESCAPE" &&
1520 {
1521 MythConfirmationDialog *dialog =
1522 ShowOkPopup(tr("Cancel ripping the CD?"), true);
1523 if (dialog)
1524 dialog->SetReturnEvent(this, "stop_ripping");
1525 }
1526 else
1527 {
1528 handled = false;
1529 }
1530 }
1531
1532 if (!handled && MythScreenType::keyPressEvent(event))
1533 handled = true;
1534
1535 return handled;
1536}
1537
1538void RipStatus::customEvent(QEvent *event)
1539{
1540 if (event->type() == DialogCompletionEvent::kEventType)
1541 {
1542 auto *dce = dynamic_cast<DialogCompletionEvent *>(event);
1543 if (dce == nullptr)
1544 return;
1545 if ((dce->GetId() == "stop_ripping") && (dce->GetResult() != 0))
1546 {
1549 Close();
1550 }
1551
1552 return;
1553 }
1554
1555 auto *rse = dynamic_cast<RipStatusEvent *> (event);
1556 if (!rse)
1557 return;
1558
1559 if (event->type() == RipStatusEvent::kTrackTextEvent)
1560 {
1561 if (m_trackText)
1562 m_trackText->SetText(rse->m_text);
1563 }
1564 else if (event->type() == RipStatusEvent::kOverallTextEvent)
1565 {
1566 if (m_overallText)
1567 m_overallText->SetText(rse->m_text);
1568 }
1569 else if (event->type() == RipStatusEvent::kStatusTextEvent)
1570 {
1571 if (m_statusText)
1572 m_statusText->SetText(rse->m_text);
1573 }
1574 else if (event->type() == RipStatusEvent::kTrackProgressEvent)
1575 {
1576 if (m_trackProgress)
1577 m_trackProgress->SetUsed(rse->m_value);
1578 }
1579 else if (event->type() == RipStatusEvent::kTrackPercentEvent)
1580 {
1581 if (m_trackPctText)
1582 m_trackPctText->SetText(QString("%1%").arg(rse->m_value));
1583 }
1584 else if (event->type() == RipStatusEvent::kTrackStartEvent)
1585 {
1586 if (m_trackProgress)
1587 m_trackProgress->SetTotal(rse->m_value);
1588 }
1589 else if (event->type() == RipStatusEvent::kCopyStartEvent)
1590 {
1591 if (m_trackPctText)
1592 m_trackPctText->SetText(tr("Copying Track ..."));
1593 }
1594 else if (event->type() == RipStatusEvent::kCopyEndEvent)
1595 {
1596 if (m_trackPctText)
1598 }
1599 else if (event->type() == RipStatusEvent::kOverallProgressEvent)
1600 {
1602 m_overallProgress->SetUsed(rse->m_value);
1603 }
1604 else if (event->type() == RipStatusEvent::kOverallStartEvent)
1605 {
1607 m_overallProgress->SetTotal(rse->m_value);
1608 }
1609 else if (event->type() == RipStatusEvent::kOverallPercentEvent)
1610 {
1611 if (m_overallPctText)
1612 m_overallPctText->SetText(QString("%1%").arg(rse->m_value));
1613 }
1614 else if (event->type() == RipStatusEvent::kFinishedEvent)
1615 {
1616 emit Result(true);
1617 Close();
1618 }
1619 else if (event->type() == RipStatusEvent::kEncoderErrorEvent)
1620 {
1621 ShowOkPopup(tr("The encoder failed to create the file.\n"
1622 "Do you have write permissions"
1623 " for the music directory?"));
1624 Close();
1625 }
1626 else
1627 {
1628 LOG(VB_GENERAL, LOG_ERR, "Received an unknown event type!");
1629 }
1630}
1631
1633{
1634 delete m_ripperThread;
1637}
static long int getSectorCount(QString &cddevice, int tracknum)
Definition: cdrip.cpp:119
Ripper * m_parent
Definition: cdrip.h:47
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:83
~CDRipperThread() override
Definition: cdrip.cpp:184
long int m_totalSectors
Definition: cdrip.h:85
bool isCancelled(void) const
Definition: cdrip.cpp:195
QString m_musicStorageDir
Definition: cdrip.h:91
bool m_quit
Definition: cdrip.h:80
long int m_totalSectorsDone
Definition: cdrip.h:86
int m_quality
Definition: cdrip.h:82
int m_lastTrackPct
Definition: cdrip.h:88
int m_lastOverallPct
Definition: cdrip.h:89
QString m_cdDevice
Definition: cdrip.h:81
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:79
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:36
void setDevice(const QString &dev)
Definition: cddecoder.cpp:108
MusicMetadata * getMetadata(void)
Definition: cddecoder.cpp:503
int getNumTracks()
Definition: cddecoder.cpp:450
Event dispatched from MythUI modal dialogs to a listening class containing a result of some form.
Definition: mythdialogbox.h:41
static const Type kEventType
Definition: mythdialogbox.h:56
void metadataChanged(void)
Definition: lcddevice.h:170
static LCD * Get(void)
Definition: lcddevice.cpp:69
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:837
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:618
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:888
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:812
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:550
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
bool isRunning(void) const
Definition: mthread.cpp:263
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:196
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:283
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:209
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:300
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
Definition: mthread.cpp:233
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:55
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)
std::chrono::milliseconds Length() const
static QStringList fillFieldList(const QString &field)
void setLength(T llength)
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:226
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:314
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
virtual void SetVisible(bool visible)
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:138
static bool CopyFile(const QString &src, const QString &dst, bool overwrite=false, bool verify=false)
Definition: remotefile.cpp:591
static bool DeleteFile(const QString &url)
Definition: remotefile.cpp:422
static bool Exists(const QString &url, struct stat *fileinfo)
Definition: remotefile.cpp:465
static const Type kOverallStartEvent
Definition: cdrip.h:207
static const Type kTrackPercentEvent
Definition: cdrip.h:203
static const Type kOverallProgressEvent
Definition: cdrip.h:205
static const Type kCopyEndEvent
Definition: cdrip.h:209
static const Type kTrackTextEvent
Definition: cdrip.h:199
static const Type kOverallPercentEvent
Definition: cdrip.h:206
static const Type kStatusTextEvent
Definition: cdrip.h:201
static const Type kTrackStartEvent
Definition: cdrip.h:204
static const Type kFinishedEvent
Definition: cdrip.h:210
static const Type kOverallTextEvent
Definition: cdrip.h:200
static const Type kCopyStartEvent
Definition: cdrip.h:208
static const Type kTrackProgressEvent
Definition: cdrip.h:202
static const Type kEncoderErrorEvent
Definition: cdrip.h:211
QString m_cdDevice
Definition: cdrip.h:238
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
Definition: cdrip.cpp:1504
MythUIProgressBar * m_overallProgress
Definition: cdrip.h:245
int m_quality
Definition: cdrip.h:237
MythUIText * m_overallText
Definition: cdrip.h:240
void startRip(void)
Definition: cdrip.cpp:1632
bool Create(void) override
Definition: cdrip.cpp:1483
MythUIText * m_statusText
Definition: cdrip.h:242
void Result(bool)
CDRipperThread * m_ripperThread
Definition: cdrip.h:248
MythUIText * m_overallPctText
Definition: cdrip.h:243
void customEvent(QEvent *event) override
Definition: cdrip.cpp:1538
~RipStatus(void) override
Definition: cdrip.cpp:1476
MythUIProgressBar * m_trackProgress
Definition: cdrip.h:246
MythUIText * m_trackPctText
Definition: cdrip.h:244
MythUIText * m_trackText
Definition: cdrip.h:241
QVector< RipTrack * > * m_tracks
Definition: cdrip.h:236
MythUITextEdit * m_yearEdit
Definition: cdrip.h:156
MythUICheckBox * m_compilationCheck
Definition: cdrip.h:158
MythUIButton * m_searchArtistButton
Definition: cdrip.h:166
bool m_somethingwasripped
Definition: cdrip.h:177
MythUIButton * m_switchTitleArtist
Definition: cdrip.h:163
void metadataChanged(void)
Definition: cdrip.cpp:1346
void setArtist(const QString &artist)
Definition: cdrip.cpp:1260
MythUIButton * m_searchAlbumButton
Definition: cdrip.h:167
void chooseBackend(void) const
Definition: cdrip.cpp:690
bool deleteExistingTrack(RipTrack *track)
Definition: cdrip.cpp:864
QString m_musicStorageDir
Definition: cdrip.h:149
void ShowConflictMenu(RipTrack *track)
Definition: cdrip.cpp:1390
QString m_artistName
Definition: cdrip.h:173
void updateTrackLengths(void)
Definition: cdrip.cpp:1414
MythUITextEdit * m_artistEdit
Definition: cdrip.h:153
void artistChanged(void)
Definition: cdrip.cpp:947
void ShowMenu(void) override
Definition: cdrip.cpp:662
void setSaveHost(const QString &host)
Definition: cdrip.cpp:731
void ripFinished(void)
MythUIButtonList * m_trackList
Definition: cdrip.h:160
void switchTitlesAndArtists()
Definition: cdrip.cpp:1072
void searchAlbum(void) const
Definition: cdrip.cpp:1265
MythUIButton * m_searchGenreButton
Definition: cdrip.h:168
CDScannerThread * m_scanThread
Definition: cdrip.h:183
QString m_genreName
Definition: cdrip.h:174
void updateTrackList(void)
Definition: cdrip.cpp:1189
bool m_mediaMonitorActive
Definition: cdrip.h:178
MythUIButton * m_ripButton
Definition: cdrip.h:165
MythUITextEdit * m_genreEdit
Definition: cdrip.h:155
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
Definition: cdrip.cpp:635
void setGenre(const QString &genre)
Definition: cdrip.cpp:1313
void ScanFinished(void)
Definition: cdrip.cpp:753
bool Create(void) override
Definition: cdrip.cpp:573
bool somethingWasRipped() const
Definition: cdrip.cpp:942
void deleteAllExistingTracks(void)
Definition: cdrip.cpp:847
~Ripper(void) override
Definition: cdrip.cpp:551
MythUITextEdit * m_albumEdit
Definition: cdrip.h:154
MythUIButton * m_scanButton
Definition: cdrip.h:164
CDEjectorThread * m_ejectThread
Definition: cdrip.h:182
QStringList m_searchList
Definition: cdrip.h:176
void showEditMetadataDialog(void)
Definition: cdrip.cpp:685
void toggleTrackActive(MythUIButtonListItem *item)
Definition: cdrip.cpp:1361
void genreChanged(void)
Definition: cdrip.cpp:993
MythUIButtonList * m_qualityList
Definition: cdrip.h:161
void setAlbum(const QString &album)
Definition: cdrip.cpp:1284
void searchArtist(void) const
Definition: cdrip.cpp:1241
QString m_cdDevice
Definition: cdrip.h:180
void yearChanged(void)
Definition: cdrip.cpp:1010
void customEvent(QEvent *event) override
Definition: cdrip.cpp:1435
void compilationChanged(bool state)
Definition: cdrip.cpp:1027
void RipComplete(bool result)
Definition: cdrip.cpp:1122
void searchGenre(void)
Definition: cdrip.cpp:1289
void scanCD(void)
Definition: cdrip.cpp:831
void EjectFinished(void)
Definition: cdrip.cpp:1155
QVector< RipTrack * > * m_tracks
Definition: cdrip.h:170
void startRipper(void)
Definition: cdrip.cpp:1097
Ripper(MythScreenStack *parent, QString device)
Definition: cdrip.cpp:521
void startEjectCD(void)
Definition: cdrip.cpp:1140
QString m_albumName
Definition: cdrip.h:172
QString m_year
Definition: cdrip.h:175
void albumChanged(void)
Definition: cdrip.cpp:976
CdDecoder * m_decoder
Definition: cdrip.h:151
void startScanCD(void)
Definition: cdrip.cpp:740
void ejectCD(void)
Definition: cdrip.cpp:1163
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
static uint32_t * tmp
Definition: goom_core.cpp:28
@ 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:285
#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
STL namespace.
Definition: cdrip.h:51
bool isNew
Definition: cdrip.h:55
std::chrono::milliseconds length
Definition: cdrip.h:54
bool active
Definition: cdrip.h:53
MusicMetadata * metadata
Definition: cdrip.h:52