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