MythTV  master
cdrip.cpp
Go to the documentation of this file.
1 // ANSI C includes
2 #include <cstdio>
3 #include <cstring>
4 
5 // Unix C includes
6 #include <sys/types.h>
7 #include <fcntl.h>
8 
9 #include "config.h"
10 
11 // C++ includes
12 #include <iostream>
13 #include <memory>
14 using namespace std;
15 
16 // Qt includes
17 #include <QApplication>
18 #include <QDir>
19 #include <QEvent>
20 #include <QFile>
21 #include <QKeyEvent>
22 #include <QRegExp>
23 #include <QTimer>
24 #include <QUrl>
25 #include <utility>
26 
27 // MythTV plugin includes
28 #include <mythcontext.h>
29 #include <mythdb.h>
30 #include <lcddevice.h>
31 #include <mythmediamonitor.h>
32 #include <mythdirs.h>
33 
34 // MythUI
35 #include <mythdialogbox.h>
36 #include <mythuitext.h>
37 #include <mythuicheckbox.h>
38 #include <mythuitextedit.h>
39 #include <mythuibutton.h>
40 #include <mythuiprogressbar.h>
41 #include <mythuibuttonlist.h>
42 #include <mythsystemlegacy.h>
43 #include <storagegroup.h>
44 #include <remotefile.h>
45 
46 // MythUI headers
47 #include <mythtv/libmythui/mythscreenstack.h>
48 #include <mythtv/libmythui/mythprogressdialog.h>
49 
50 // MythMusic includes
51 #include "cdrip.h"
52 #ifdef HAVE_CDIO
53 #include "cddecoder.h"
54 #endif // HAVE_CDIO
55 #include "encoder.h"
56 #include "vorbisencoder.h"
57 #include "lameencoder.h"
58 #include "flacencoder.h"
59 #include "genres.h"
60 #include "editmetadata.h"
61 #include "mythlogging.h"
62 #include "musicutils.h"
63 
64 #ifdef HAVE_CDIO
65 // libparanoia compatibility
66 #ifndef cdrom_paranoia
67 #define cdrom_paranoia cdrom_paranoia_t
68 #endif // cdrom_paranoia
69 
70 #ifndef CD_FRAMESIZE_RAW
71 # define CD_FRAMESIZE_RAW CDIO_CD_FRAMESIZE_RAW
72 #endif // CD_FRAMESIZE_RAW
73 #endif // HAVE_CDIO
74 
76  (QEvent::Type) QEvent::registerEventType();
78  (QEvent::Type) QEvent::registerEventType();
80  (QEvent::Type) QEvent::registerEventType();
82  (QEvent::Type) QEvent::registerEventType();
84  (QEvent::Type) QEvent::registerEventType();
86  (QEvent::Type) QEvent::registerEventType();
88  (QEvent::Type) QEvent::registerEventType();
90  (QEvent::Type) QEvent::registerEventType();
92  (QEvent::Type) QEvent::registerEventType();
94  (QEvent::Type) QEvent::registerEventType();
95 QEvent::Type RipStatusEvent::kCopyEndEvent =
96  (QEvent::Type) QEvent::registerEventType();
97 QEvent::Type RipStatusEvent::kFinishedEvent =
98  (QEvent::Type) QEvent::registerEventType();
100  (QEvent::Type) QEvent::registerEventType();
101 
103 {
104  RunProlog();
105  m_parent->scanCD();
106  RunEpilog();
107 }
108 
110 
112 {
113  RunProlog();
114  m_parent->ejectCD();
115  RunEpilog();
116 }
117 
119 
120 static long int getSectorCount (QString &cddevice, 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__).arg(cddevice).arg(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__).arg(cddevice).arg(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 #else
157  (void)cddevice; (void)tracknum;
158 #endif // HAVE_CDIO
159  return 0;
160 }
161 
162 #ifdef HAVE_CDIO
163 static void paranoia_cb(long /*status*/, paranoia_cb_mode_t /*mode*/)
164 {
165 }
166 #endif // HAVE_CDIO
167 
169  QVector<RipTrack*> *tracks, int quality) :
170  MThread("CDRipper"),
171  m_parent(parent),
172  m_CDdevice(std::move(device)), m_quality(quality),
173  m_tracks(tracks)
174 {
175 #ifdef WIN32 // libcdio needs the drive letter with no path
176  if (m_CDdevice.endsWith('\\'))
177  m_CDdevice.chop(1);
178 #endif // WIN32
179 
180  QString lastHost = gCoreContext->GetSetting("MythMusicLastRipHost", gCoreContext->GetMasterHostName());
181  QStringList dirs = StorageGroup::getGroupDirs("Music", lastHost);
182  if (dirs.count() > 0)
183  m_musicStorageDir = StorageGroup::getGroupDirs("Music", lastHost).at(0);
184 }
185 
187 {
188  cancel();
189  wait();
190 }
191 
193 {
194  m_quit = true;
195 }
196 
198 {
199  return m_quit;
200 }
201 
203 {
204  RunProlog();
205 
206  if (m_tracks->empty())
207  {
208  RunEpilog();
209  return;
210  }
211 
212  m_totalSectors = 0;
213  m_totalSectorsDone = 0;
214  for (int trackno = 0; trackno < m_tracks->size(); trackno++)
215  {
216  m_totalSectors += getSectorCount(m_CDdevice, trackno + 1);
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(
292  RipStatusEvent::kTrackTextEvent, textstatus));
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  {
303  titleTrack = track;
304  titleTrack->setLength(m_tracks->at(trackno)->length);
305 
306  if (m_quality < 3)
307  {
308  if (encodertype == "mp3")
309  {
310  outfile = QString("track%1.mp3").arg(trackno);
311  encoder.reset(new LameEncoder(saveDir + outfile, m_quality,
312  titleTrack, mp3usevbr));
313  }
314  else // ogg
315  {
316  outfile = QString("track%1.ogg").arg(trackno);
317  encoder.reset(new VorbisEncoder(saveDir + outfile, m_quality,
318  titleTrack));
319  }
320  }
321  else
322  {
323  outfile = QString("track%1.flac").arg(trackno);
324  encoder.reset(new FlacEncoder(saveDir + outfile, m_quality,
325  titleTrack));
326  }
327 
328  if (!encoder->isValid())
329  {
330  QApplication::postEvent(
331  m_parent,
332  new RipStatusEvent(
334  "Encoder failed to open file for writing"));
335  LOG(VB_GENERAL, LOG_ERR, "MythMusic: Encoder failed"
336  " to open file for writing");
337 
338  RunEpilog();
339  return;
340  }
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  if (m_tracks->at(trackno)->active)
363  {
364  QString ext = QFileInfo(outfile).suffix();
365  QString destFile = filenameFromMetadata(titleTrack) + '.' + ext;
366  QUrl url(m_musicStorageDir);
367 
368  // save the metadata to the DB
369  titleTrack->setFilename(destFile);
370  titleTrack->setHostname(url.host());
371  titleTrack->setFileSize((quint64)QFileInfo(outfile).size());
372  titleTrack->dumpToDatabase();
373 
374  // this will delete the encoder which will write the metadata in it's dtor
375  encoder.reset();
376 
377  // copy track to the BE
378  destFile = gCoreContext->GenMythURL(url.host(), 0, destFile, "Music");
379 
380  QApplication::postEvent(m_parent, new RipStatusEvent(RipStatusEvent::kCopyStartEvent, 0));
381  RemoteFile::CopyFile(saveDir + outfile, destFile, true);
382  QApplication::postEvent(m_parent, new RipStatusEvent(RipStatusEvent::kCopyEndEvent, 0));
383  }
384  }
385  }
386 
387  QString PostRipCDScript = gCoreContext->GetSetting("PostCDRipScript");
388 
389  if (!PostRipCDScript.isEmpty())
390  myth_system(PostRipCDScript);
391 
392  QApplication::postEvent(
394 
395  RunEpilog();
396 }
397 
398 int CDRipperThread::ripTrack(QString &cddevice, Encoder *encoder, int tracknum)
399 {
400 #ifdef HAVE_CDIO
401  QByteArray devname = cddevice.toLatin1();
402  cdrom_drive *device = cdda_identify(devname.constData(), 0, nullptr);
403 
404  if (!device)
405  {
406  LOG(VB_GENERAL, LOG_ERR,
407  QString("cdda_identify failed for device '%1', "
408  "CDRipperThread::ripTrack(tracknum = %2) exiting.")
409  .arg(cddevice).arg(tracknum));
410  return -1;
411  }
412 
413  if (cdda_open(device))
414  {
415  LOG(VB_MEDIA, LOG_INFO,
416  QString("Error: %1('%2',track=%3) failed at cdda_open() - cdda not supported")
417  .arg(__func__).arg(cddevice).arg(tracknum));
418  cdda_close(device);
419  return -1;
420  }
421 
422  cdda_verbose_set(device, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
423  long int start = cdda_track_firstsector(device, tracknum);
424  long int end = cdda_track_lastsector(device, tracknum);
425  LOG(VB_MEDIA, LOG_INFO, QString("%1(%2,track=%3) start=%4 end=%5")
426  .arg(__func__).arg(cddevice).arg(tracknum).arg(start).arg(end));
427 
428  cdrom_paranoia *paranoia = paranoia_init(device);
429  if (gCoreContext->GetSetting("ParanoiaLevel") == "full")
430  paranoia_modeset(paranoia, PARANOIA_MODE_FULL |
431  PARANOIA_MODE_NEVERSKIP);
432  else
433  paranoia_modeset(paranoia, PARANOIA_MODE_OVERLAP);
434 
435  paranoia_seek(paranoia, start, SEEK_SET);
436 
437  long int curpos = start;
438 
439  QApplication::postEvent(
440  m_parent,
442  m_lastTrackPct = -1;
443  m_lastOverallPct = -1;
444 
445  int every15 = 15;
446  while (curpos < end)
447  {
448  int16_t *buffer = paranoia_read(paranoia, paranoia_cb);
449 
450  if (encoder->addSamples(buffer, CD_FRAMESIZE_RAW))
451  break;
452 
453  curpos++;
454 
455  every15--;
456 
457  if (every15 <= 0)
458  {
459  every15 = 15;
460 
461  // updating the UITypes can be slow - only update if we need to:
462  int newOverallPct = (int) (100.0 / ((double) m_totalSectors /
463  (m_totalSectorsDone + curpos - start)));
464  if (newOverallPct != m_lastOverallPct)
465  {
466  m_lastOverallPct = newOverallPct;
467  QApplication::postEvent(
468  m_parent,
470  newOverallPct));
471  QApplication::postEvent(
472  m_parent,
474  m_totalSectorsDone + curpos - start));
475  }
476 
477  int newTrackPct = (int) (100.0 / ((double) (end - start + 1) / (curpos - start)));
478  if (newTrackPct != m_lastTrackPct)
479  {
480  m_lastTrackPct = newTrackPct;
481  QApplication::postEvent(
482  m_parent,
484  newTrackPct));
485  QApplication::postEvent(
486  m_parent,
488  curpos - start));
489  }
490 
491  if (LCD *lcd = LCD::Get())
492  {
493  float fProgress = (float)(m_totalSectorsDone + (curpos - start))
494  / m_totalSectors;
495  lcd->setGenericProgress(fProgress);
496  }
497  }
498 
499  if (isCancelled())
500  {
501  break;
502  }
503  }
504 
505  m_totalSectorsDone += end - start + 1;
506 
507  paranoia_free(paranoia);
508  cdda_close(device);
509 
510  return (curpos - start + 1) * CD_FRAMESIZE_RAW;
511 #else
512  (void)cddevice; (void)encoder; (void)tracknum;
513  return 0;
514 #endif // HAVE_CDIO
515 }
516 
518 
519 Ripper::Ripper(MythScreenStack *parent, QString device) :
520  MythScreenType(parent, "ripcd"),
521  m_tracks(new QVector<RipTrack*>),
522  m_CDdevice(std::move(device))
523 {
524 #ifndef _WIN32
525  // if the MediaMonitor is running stop it
526  m_mediaMonitorActive = false;
528  if (mon && mon->IsActive())
529  {
530  m_mediaMonitorActive = true;
531  mon->StopMonitoring();
532  }
533 #endif // _WIN32
534 
535  // make sure the directory where we temporarily save the rips is present
536  QDir dir;
537  dir.mkpath(GetConfDir() + "/tmp/RipTemp/");
538 
539  // remove any ripped tracks from the temp rip directory
540  QString command = "rm -f " + GetConfDir() + "/tmp/RipTemp/*";
541  myth_system(command);
542 
543  // get last host and directory we ripped to
544  QString lastHost = gCoreContext->GetSetting("MythMusicLastRipHost", gCoreContext->GetMasterHostName());
545  QStringList dirs = StorageGroup::getGroupDirs("Music", lastHost);
546  if (dirs.count() > 0)
547  m_musicStorageDir = StorageGroup::getGroupDirs("Music", lastHost).at(0);
548 }
549 
551 {
552  // remove any ripped tracks from the temp rip directory
553  QString command = "rm -f " + GetConfDir() + "/tmp/RipTemp/*";
554  myth_system(command);
555 
556  delete m_decoder;
557 
558 #ifndef _WIN32
559  // if the MediaMonitor was active when we started then restart it
561  {
563  if (mon)
564  mon->StartMonitoring();
565  }
566 #endif // _WIN32
567 
569  emit ripFinished();
570 }
571 
572 bool Ripper::Create(void)
573 {
574  if (!LoadWindowFromXML("music-ui.xml", "cdripper", this))
575  return false;
576 
577  m_qualityList = dynamic_cast<MythUIButtonList *>(GetChild("quality"));
578  m_artistEdit = dynamic_cast<MythUITextEdit *>(GetChild("artist"));
579  m_searchArtistButton = dynamic_cast<MythUIButton *>(GetChild("searchartist"));
580  m_albumEdit = dynamic_cast<MythUITextEdit *>(GetChild("album"));
581  m_searchAlbumButton = dynamic_cast<MythUIButton *>(GetChild("searchalbum"));
582  m_genreEdit = dynamic_cast<MythUITextEdit *>(GetChild("genre"));
583  m_yearEdit = dynamic_cast<MythUITextEdit *>(GetChild("year"));
584  m_searchGenreButton = dynamic_cast<MythUIButton *>(GetChild("searchgenre"));
585  m_compilationCheck = dynamic_cast<MythUICheckBox *>(GetChild("compilation"));
586  m_switchTitleArtist = dynamic_cast<MythUIButton *>(GetChild("switch"));
587  m_scanButton = dynamic_cast<MythUIButton *>(GetChild("scan"));
588  m_ripButton = dynamic_cast<MythUIButton *>(GetChild("rip"));
589  m_trackList = dynamic_cast<MythUIButtonList *>(GetChild("tracks"));
590 
591  BuildFocusList();
592 
597  {
598  LOG(VB_GENERAL, LOG_ERR,
599  "Missing theme elements for screen 'cdripper'");
600  return false;
601  }
602 
603  connect(m_trackList, SIGNAL(itemClicked(MythUIButtonListItem *)),
605  connect(m_ripButton, SIGNAL(Clicked()), SLOT(startRipper()));
606  connect(m_scanButton, SIGNAL(Clicked()), SLOT(startScanCD()));
607  connect(m_switchTitleArtist, SIGNAL(Clicked()),
608  SLOT(switchTitlesAndArtists()));
609  connect(m_compilationCheck, SIGNAL(toggled(bool)),
610  SLOT(compilationChanged(bool)));
611  connect(m_searchGenreButton, SIGNAL(Clicked()), SLOT(searchGenre()));
612  connect(m_genreEdit, SIGNAL(valueChanged()), SLOT(genreChanged()));
615  connect(m_yearEdit, SIGNAL(valueChanged()), SLOT(yearChanged()));
616  connect(m_artistEdit, SIGNAL(valueChanged()), SLOT(artistChanged()));
617  connect(m_searchArtistButton, SIGNAL(Clicked()), SLOT(searchArtist()));
618  connect(m_albumEdit, SIGNAL(valueChanged()), SLOT(albumChanged()));
619  connect(m_searchAlbumButton, SIGNAL(Clicked()), SLOT(searchAlbum()));
620 
621  // Populate Quality List
622  new MythUIButtonListItem(m_qualityList, tr("Low"), qVariantFromValue(0));
623  new MythUIButtonListItem(m_qualityList, tr("Medium"), qVariantFromValue(1));
624  new MythUIButtonListItem(m_qualityList, tr("High"), qVariantFromValue(2));
625  new MythUIButtonListItem(m_qualityList, tr("Perfect"), qVariantFromValue(3));
626  m_qualityList->SetValueByData(qVariantFromValue(
627  gCoreContext->GetNumSetting("DefaultRipQuality", 1)));
628 
629  QTimer::singleShot(500, this, SLOT(startScanCD()));
630 
631  return true;
632 }
633 
634 bool Ripper::keyPressEvent(QKeyEvent *event)
635 {
636  if (GetFocusWidget() && GetFocusWidget()->keyPressEvent(event))
637  return true;
638 
639  QStringList actions;
640  bool handled = GetMythMainWindow()->TranslateKeyPress("Global", event, actions);
641 
642  for (int i = 0; i < actions.size() && !handled; i++)
643  {
644  QString action = actions[i];
645  handled = true;
646 
647  if (action == "EDIT" || action == "INFO") // INFO purely for historical reasons
649  else if (action == "MENU")
650  ShowMenu();
651  else
652  handled = false;
653  }
654 
655  if (!handled && MythScreenType::keyPressEvent(event))
656  handled = true;
657 
658  return handled;
659 }
660 
662 {
663  if (m_tracks->empty())
664  return;
665 
666  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
667 
668  MythDialogBox *menu = new MythDialogBox("", popupStack, "ripmusicmenu");
669 
670  if (menu->Create())
671  popupStack->AddScreen(menu);
672  else
673  {
674  delete menu;
675  return;
676  }
677 
678  menu->SetReturnEvent(this, "menu");
679  menu->AddButton(tr("Select Where To Save Tracks"), SLOT(chooseBackend()));
680  menu->AddButton(tr("Edit Track Metadata"), SLOT(showEditMetadataDialog()));
681 }
682 
684 {
686 }
687 
689 {
690  QStringList hostList;
691 
692  // get a list of hosts with a directory defined for the 'Music' storage group
693  MSqlQuery query(MSqlQuery::InitCon());
694  QString sql = "SELECT DISTINCT hostname "
695  "FROM storagegroup "
696  "WHERE groupname = 'Music'";
697  if (!query.exec(sql) || !query.isActive())
698  MythDB::DBError("Ripper::chooseBackend get host list", query);
699  else
700  {
701  while(query.next())
702  {
703  hostList.append(query.value(0).toString());
704  }
705  }
706 
707  if (hostList.isEmpty())
708  {
709  LOG(VB_GENERAL, LOG_ERR, "Ripper::chooseBackend: No backends found");
710  return;
711  }
712 
713  QString msg = tr("Select where to save tracks");
714 
715  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
716  MythUISearchDialog *searchDlg = new MythUISearchDialog(popupStack, msg, hostList, false, "");
717 
718  if (!searchDlg->Create())
719  {
720  delete searchDlg;
721  return;
722  }
723 
724  connect(searchDlg, SIGNAL(haveResult(QString)), SLOT(setSaveHost(QString)));
725 
726  popupStack->AddScreen(searchDlg);
727 }
728 
729 void Ripper::setSaveHost(const QString& host)
730 {
731  gCoreContext->SaveSetting("MythMusicLastRipHost", host);
732 
733  QStringList dirs = StorageGroup::getGroupDirs("Music", host);
734  if (dirs.count() > 0)
735  m_musicStorageDir = StorageGroup::getGroupDirs("Music", host).at(0);
736 }
737 
739 {
740  if (m_scanThread)
741  return;
742 
743  QString message = tr("Scanning CD. Please Wait ...");
744  OpenBusyPopup(message);
745 
746  m_scanThread = new CDScannerThread(this);
747  connect(m_scanThread->qthread(), SIGNAL(finished()), SLOT(ScanFinished()));
748  m_scanThread->start();
749 }
750 
752 {
753  delete m_scanThread;
754  m_scanThread = nullptr;
755 
756  m_tracks->clear();
757 
758  if (m_decoder)
759  {
760  MusicMetadata *metadata;
761  bool isCompilation = false;
762 
763  m_artistName.clear();
764  m_albumName.clear();
765  m_genreName.clear();
766  m_year.clear();
767 
768  for (int trackno = 0; trackno < m_decoder->getNumTracks(); trackno++)
769  {
770  RipTrack *ripTrack = new RipTrack;
771 
772  metadata = m_decoder->getMetadata(trackno + 1);
773  if (metadata)
774  {
775  ripTrack->metadata = metadata;
776  ripTrack->length = metadata->Length();
777 
778  if (metadata->Compilation())
779  {
780  isCompilation = true;
781  m_artistName = metadata->CompilationArtist();
782  }
783  else if (m_artistName.isEmpty())
784  {
785  m_artistName = metadata->Artist();
786  }
787 
788  if (m_albumName.isEmpty())
789  m_albumName = metadata->Album();
790 
791  if (m_genreName.isEmpty() && !metadata->Genre().isEmpty())
792  m_genreName = metadata->Genre();
793 
794  if (m_year.isEmpty() && metadata->Year() > 0)
795  m_year = QString::number(metadata->Year());
796 
797  QString title = metadata->Title();
798  ripTrack->isNew = isNewTune(m_artistName, m_albumName, title);
799 
800  ripTrack->active = ripTrack->isNew;
801 
802  m_tracks->push_back(ripTrack);
803 
804  }
805  else
806  delete ripTrack;
807  }
808 
813  m_compilationCheck->SetCheckState(isCompilation);
814 
815  if (!isCompilation)
817  else
819  }
820 
821  BuildFocusList();
822  updateTrackList();
823 
824  CloseBusyPopup();
825 }
826 
827 void Ripper::scanCD(void)
828 {
829 #ifdef HAVE_CDIO
830  {
831  LOG(VB_MEDIA, LOG_INFO, QString("Ripper::%1 CD='%2'").
832  arg(__func__).arg(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  QVector<RipTrack*>::iterator it;
846  for (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 
874  MSqlQuery query(MSqlQuery::InitCon());
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  token.replace(QRegExp("(/|\\\\|:|\'|\\,|\\!|\\(|\\)|\"|\\?|\\|)"),
887  QString("."));
888 
889  queryString += token + "\' AND " + "album_name REGEXP \'";
890  token = album;
891  token.replace(QRegExp("(/|\\\\|:|\'|\\,|\\!|\\(|\\)|\"|\\?|\\|)"),
892  QString("."));
893  queryString += token + "\' AND " + "name REGEXP \'";
894  token = title;
895  token.replace(QRegExp("(/|\\\\|:|\'|\\,|\\!|\\(|\\)|\"|\\?|\\|)"),
896  QString("."));
897  queryString += token + "\' ORDER BY artist_name, album_name,"
898  " name, song_id, filename LIMIT 1";
899  query.prepare(queryString);
900 
901  if (!query.exec() || !query.isActive())
902  {
903  MythDB::DBError("Search music database", query);
904  return false;
905  }
906 
907  if (query.next())
908  {
909  int trackID = query.value(0).toInt();
910  QString filename = query.value(1).toString();
911  QUrl url(m_musicStorageDir);
912  filename = gCoreContext->GenMythURL(url.host(), 0, filename, "Music");
913 
914  // delete file
915  // FIXME: RemoteFile::DeleteFile will only work with files on the master BE
916  if (!RemoteFile::DeleteFile(filename))
917  {
918  LOG(VB_GENERAL, LOG_NOTICE, QString("Ripper::deleteExistingTrack() "
919  "Could not delete %1")
920  .arg(filename));
921  return false;
922  }
923 
924  // remove database entry
925  MSqlQuery deleteQuery(MSqlQuery::InitCon());
926  deleteQuery.prepare("DELETE FROM music_songs"
927  " WHERE song_id = :SONG_ID");
928  deleteQuery.bindValue(":SONG_ID", trackID);
929  if (!deleteQuery.exec())
930  {
931  MythDB::DBError("Delete Track", deleteQuery);
932  return false;
933  }
934  return true;
935  }
936 
937  return false;
938 }
939 
941 {
942  return m_somethingwasripped;
943 }
944 
946 {
947  QString newartist = m_artistEdit->GetText();
948 
949  if (!m_tracks->empty())
950  {
951  for (int trackno = 0; trackno < m_tracks->size(); ++trackno)
952  {
953  MusicMetadata *data = m_tracks->at(trackno)->metadata;
954  if (data)
955  {
957  {
958  data->setCompilationArtist(newartist);
959  }
960  else
961  {
962  data->setArtist(newartist);
963  data->setCompilationArtist("");
964  }
965  }
966  }
967 
968  updateTrackList();
969  }
970 
971  m_artistName = newartist;
972 }
973 
975 {
976  QString newalbum = m_albumEdit->GetText();
977 
978  if (!m_tracks->empty())
979  {
980  for (int trackno = 0; trackno < m_tracks->size(); ++trackno)
981  {
982  MusicMetadata *data = m_tracks->at(trackno)->metadata;
983  if (data)
984  data->setAlbum(newalbum);
985  }
986  }
987 
988  m_albumName = newalbum;
989 }
990 
992 {
993  QString newgenre = m_genreEdit->GetText();
994 
995  if (!m_tracks->empty())
996  {
997  for (int trackno = 0; trackno < m_tracks->size(); ++trackno)
998  {
999  MusicMetadata *data = m_tracks->at(trackno)->metadata;
1000  if (data)
1001  data->setGenre(newgenre);
1002  }
1003  }
1004 
1005  m_genreName = newgenre;
1006 }
1007 
1009 {
1010  QString newyear = m_yearEdit->GetText();
1011 
1012  if (!m_tracks->empty())
1013  {
1014  for (int trackno = 0; trackno < m_tracks->size(); ++trackno)
1015  {
1016  MusicMetadata *data = m_tracks->at(trackno)->metadata;
1017  if (data)
1018  data->setYear(newyear.toInt());
1019  }
1020  }
1021 
1022  m_year = newyear;
1023 }
1024 
1026 {
1027  if (!state)
1028  {
1029  if (!m_tracks->empty())
1030  {
1031  // Update artist MetaData of each track on the ablum...
1032  for (int trackno = 0; trackno < m_tracks->size(); ++trackno)
1033  {
1034  MusicMetadata *data = m_tracks->at(trackno)->metadata;
1035  if (data)
1036  {
1037  data->setCompilationArtist("");
1038  data->setArtist(m_artistName);
1039  data->setCompilation(false);
1040  }
1041  }
1042  }
1043 
1045  }
1046  else
1047  {
1048  if (!m_tracks->empty())
1049  {
1050  // Update artist MetaData of each track on the album...
1051  for (int trackno = 0; trackno < m_tracks->size(); ++trackno)
1052  {
1053  MusicMetadata *data;
1054  data = m_tracks->at(trackno)->metadata;
1055 
1056  if (data)
1057  {
1059  data->setCompilation(true);
1060  }
1061  }
1062  }
1063 
1065  }
1066 
1067  BuildFocusList();
1068  updateTrackList();
1069 }
1070 
1072 {
1074  return;
1075 
1076  MusicMetadata *data;
1077 
1078  // Switch title and artist for each track
1079  QString tmp;
1080  if (!m_tracks->empty())
1081  {
1082  for (int track = 0; track < m_tracks->size(); ++track)
1083  {
1084  data = m_tracks->at(track)->metadata;
1085 
1086  if (data)
1087  {
1088  tmp = data->Artist();
1089  data->setArtist(data->Title());
1090  data->setTitle(tmp);
1091  }
1092  }
1093 
1094  updateTrackList();
1095  }
1096 }
1097 
1099 {
1100  if (m_tracks->isEmpty())
1101  {
1102  ShowOkPopup(tr("There are no tracks to rip?"));
1103  return;
1104  }
1105 
1107 
1108  int quality = m_qualityList->GetItemCurrent()->GetData().toInt();
1109 
1110  RipStatus *statusDialog = new RipStatus(mainStack, m_CDdevice, m_tracks,
1111  quality);
1112 
1113  if (statusDialog->Create())
1114  {
1115  connect(statusDialog, SIGNAL(Result(bool)), SLOT(RipComplete(bool)));
1116  mainStack->AddScreen(statusDialog);
1117  }
1118  else
1119  delete statusDialog;
1120 }
1121 
1122 void 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  SIGNAL(finished()), SLOT(EjectFinished()));
1152  m_ejectThread->start();
1153 }
1154 
1156 {
1157  delete m_ejectThread;
1158  m_ejectThread = nullptr;
1159 
1160  CloseBusyPopup();
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__).arg(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  int i;
1199  for (i = 0; i < m_tracks->size(); i++)
1200  {
1201  if (i >= m_tracks->size())
1202  break;
1203 
1204  RipTrack *track = m_tracks->at(i);
1205  MusicMetadata *metadata = track->metadata;
1206 
1208 
1209  item->setCheckable(true);
1210 
1211  item->SetData(qVariantFromValue(track));
1212 
1213  if (track->isNew)
1214  item->DisplayState("new", "yes");
1215  else
1216  item->DisplayState("new", "no");
1217 
1218  if (track->active)
1220  else
1222 
1223  item->SetText(QString::number(metadata->Track()), "track");
1224  item->SetText(metadata->Title(), "title");
1225  item->SetText(metadata->Artist(), "artist");
1226 
1227  int length = track->length / 1000;
1228  if (length > 0)
1229  {
1230  int min, sec;
1231  min = length / 60;
1232  sec = length % 60;
1233  QString s;
1234  s.sprintf("%02d:%02d", min, sec);
1235  item->SetText(s, "length");
1236  }
1237  else
1238  item->SetText("", "length");
1239 
1240 // if (i == m_currentTrack)
1241 // m_trackList->SetItemCurrent(i);
1242  }
1243  }
1244 }
1245 
1247 {
1248  QString msg = tr("Select an Artist");
1249  QStringList searchList = MusicMetadata::fillFieldList("artist");
1250 
1251  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1252  MythUISearchDialog *searchDlg = new MythUISearchDialog(popupStack, msg, searchList, false, "");
1253 
1254  if (!searchDlg->Create())
1255  {
1256  delete searchDlg;
1257  return;
1258  }
1259 
1260  connect(searchDlg, SIGNAL(haveResult(QString)), SLOT(setArtist(QString)));
1261 
1262  popupStack->AddScreen(searchDlg);
1263 }
1264 
1265 void Ripper::setArtist(const QString& artist)
1266 {
1267  m_artistEdit->SetText(artist);
1268 }
1269 
1271 {
1272  QString msg = tr("Select an Album");
1273  QStringList searchList = MusicMetadata::fillFieldList("album");
1274 
1275  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1276  MythUISearchDialog *searchDlg = new MythUISearchDialog(popupStack, msg, searchList, false, "");
1277 
1278  if (!searchDlg->Create())
1279  {
1280  delete searchDlg;
1281  return;
1282  }
1283 
1284  connect(searchDlg, SIGNAL(haveResult(QString)), SLOT(setAlbum(QString)));
1285 
1286  popupStack->AddScreen(searchDlg);
1287 }
1288 
1289 void Ripper::setAlbum(const QString& album)
1290 {
1291  m_albumEdit->SetText(album);
1292 }
1293 
1295 {
1296  QString msg = tr("Select a Genre");
1297  QStringList searchList = MusicMetadata::fillFieldList("genre");
1298  // load genre list
1299  m_searchList.clear();
1300  for (int x = 0; x < genre_table_size; x++)
1301  m_searchList.push_back(QString(genre_table[x]));
1302  m_searchList.sort();
1303 
1304  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1305  MythUISearchDialog *searchDlg = new MythUISearchDialog(popupStack, msg, searchList, false, "");
1306 
1307  if (!searchDlg->Create())
1308  {
1309  delete searchDlg;
1310  return;
1311  }
1312 
1313  connect(searchDlg, SIGNAL(haveResult(QString)), SLOT(setGenre(QString)));
1314 
1315  popupStack->AddScreen(searchDlg);
1316 }
1317 
1318 void Ripper::setGenre(const QString& genre)
1319 {
1320  m_genreEdit->SetText(genre);
1321 }
1322 
1324 {
1325  if (!item || m_tracks->isEmpty())
1326  return;
1327 
1328  RipTrack *track = item->GetData().value<RipTrack *>();
1329 
1330  if (!track)
1331  return;
1332 
1333  MusicMetadata *editMeta = track->metadata;
1334 
1336 
1337  EditMetadataDialog *editDialog = new EditMetadataDialog(mainStack, editMeta);
1338  editDialog->setSaveMetadataOnly();
1339 
1340  if (!editDialog->Create())
1341  {
1342  delete editDialog;
1343  return;
1344  }
1345 
1346  connect(editDialog, SIGNAL(metadataChanged()), this, SLOT(metadataChanged()));
1347 
1348  mainStack->AddScreen(editDialog);
1349 }
1350 
1352 {
1353  updateTrackList();
1354 }
1355 
1357 {
1358  QVariant data = QVariant::fromValue(track);
1360  if (item)
1361  {
1362  toggleTrackActive(item);
1363  }
1364 }
1365 
1367 {
1368  if (m_tracks->isEmpty() || !item)
1369  return;
1370 
1371  int pos = m_trackList->GetItemPos(item);
1372 
1373  // sanity check item position
1374  if (pos < 0 || pos > m_tracks->count() - 1)
1375  return;
1376 
1377  RipTrack *track = m_tracks->at(pos);
1378 
1379  if (!track->active && !track->isNew)
1380  {
1381  ShowConflictMenu(track);
1382  return;
1383  }
1384 
1385  track->active = !track->active;
1386 
1387  if (track->active)
1389  else
1391 
1393 }
1394 
1396 {
1397  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
1398 
1399  QString msg = tr("This track has been disabled because it is already "
1400  "present in the database.\n"
1401  "Do you want to permanently delete the existing "
1402  "file(s)?");
1403  MythDialogBox *menu = new MythDialogBox(msg, popupStack, "conflictmenu",
1404  true);
1405 
1406  if (menu->Create())
1407  popupStack->AddScreen(menu);
1408  else
1409  {
1410  delete menu;
1411  return;
1412  }
1413 
1414  menu->SetReturnEvent(this, "conflictmenu");
1415  menu->AddButton(tr("No, Cancel"));
1416  menu->AddButton(tr("Yes, Delete"), QVariant::fromValue(track));
1417  menu->AddButton(tr("Yes, Delete All"));
1418 }
1419 
1421 {
1422  QVector<RipTrack*>::iterator it;
1423  int length = 0;
1424 
1425  for (it = m_tracks->end() - 1; it == m_tracks->begin(); --it)
1426  {
1427  RipTrack *track = *it;
1428  if (track->active)
1429  {
1430  track->length = length + track->metadata->Length();
1431  length = 0;
1432  }
1433  else
1434  {
1435  track->length = 0;
1436  length += track->metadata->Length();
1437  }
1438  }
1439 }
1440 
1441 void Ripper::customEvent(QEvent* event)
1442 {
1443  if (event->type() == DialogCompletionEvent::kEventType)
1444  {
1445  DialogCompletionEvent *dce = static_cast<DialogCompletionEvent *>(event);
1446 
1447  if (dce->GetId() == "conflictmenu")
1448  {
1449  int buttonNum = dce->GetResult();
1450  RipTrack *track = dce->GetData().value<RipTrack *>();
1451 
1452  switch (buttonNum)
1453  {
1454  case 0:
1455  // Do nothing
1456  break;
1457  case 1:
1458  if (deleteExistingTrack(track))
1459  {
1460  track->isNew = true;
1461  toggleTrackActive(track);
1462  }
1463  break;
1464  case 2:
1466  break;
1467  default:
1468  break;
1469  }
1470  }
1471 
1472  return;
1473  }
1474 
1475  MythUIType::customEvent(event);
1476 }
1477 
1478 
1480 
1482 {
1483  delete m_ripperThread;
1484  if (LCD *lcd = LCD::Get())
1485  lcd->switchToTime();
1486 }
1487 
1489 {
1490  if (!LoadWindowFromXML("music-ui.xml", "ripstatus", this))
1491  return false;
1492 
1493  m_overallText = dynamic_cast<MythUIText *>(GetChild("overall"));
1494  m_trackText = dynamic_cast<MythUIText *>(GetChild("track"));
1495  m_statusText = dynamic_cast<MythUIText *>(GetChild("status"));
1496  m_trackPctText = dynamic_cast<MythUIText *>(GetChild("trackpct"));
1497  m_overallPctText = dynamic_cast<MythUIText *>(GetChild("overallpct"));
1498 
1499  m_overallProgress = dynamic_cast<MythUIProgressBar *>(GetChild("overall_progress"));
1500  m_trackProgress = dynamic_cast<MythUIProgressBar *>(GetChild("track_progress"));
1501 
1502  BuildFocusList();
1503 
1504  startRip();
1505 
1506  return true;
1507 }
1508 
1509 bool RipStatus::keyPressEvent(QKeyEvent *event)
1510 {
1511  if (GetFocusWidget() && GetFocusWidget()->keyPressEvent(event))
1512  return true;
1513 
1514  QStringList actions;
1515  bool handled = GetMythMainWindow()->TranslateKeyPress("Global", event, actions);
1516 
1517  for (int i = 0; i < actions.size() && !handled; i++)
1518  {
1519  QString action = actions[i];
1520  handled = true;
1521 
1522 
1523  if (action == "ESCAPE" &&
1525  {
1526  MythConfirmationDialog *dialog =
1527  ShowOkPopup(tr("Cancel ripping the CD?"), this,
1528  static_cast<const char*>(nullptr), true);
1529  if (dialog)
1530  dialog->SetReturnEvent(this, "stop_ripping");
1531  }
1532  else
1533  handled = false;
1534  }
1535 
1536  if (!handled && MythScreenType::keyPressEvent(event))
1537  handled = true;
1538 
1539  return handled;
1540 }
1541 
1542 void RipStatus::customEvent(QEvent *event)
1543 {
1544  if (event->type() == DialogCompletionEvent::kEventType)
1545  {
1546  DialogCompletionEvent *dce = static_cast<DialogCompletionEvent *>(event);
1547 
1548  if (dce->GetId() == "stop_ripping" && dce->GetResult())
1549  {
1551  m_ripperThread->wait();
1552  Close();
1553  }
1554 
1555  return;
1556  }
1557 
1558  RipStatusEvent *rse = dynamic_cast<RipStatusEvent *> (event);
1559 
1560  if (!rse)
1561  return;
1562 
1563  if (event->type() == RipStatusEvent::kTrackTextEvent)
1564  {
1565  if (m_trackText)
1566  m_trackText->SetText(rse->m_text);
1567  }
1568  else if (event->type() == RipStatusEvent::kOverallTextEvent)
1569  {
1570  if (m_overallText)
1571  m_overallText->SetText(rse->m_text);
1572  }
1573  else if (event->type() == RipStatusEvent::kStatusTextEvent)
1574  {
1575  if (m_statusText)
1576  m_statusText->SetText(rse->m_text);
1577  }
1578  else if (event->type() == RipStatusEvent::kTrackProgressEvent)
1579  {
1580  if (m_trackProgress)
1582  }
1583  else if (event->type() == RipStatusEvent::kTrackPercentEvent)
1584  {
1585  if (m_trackPctText)
1586  m_trackPctText->SetText(QString("%1%").arg(rse->m_value));
1587  }
1588  else if (event->type() == RipStatusEvent::kTrackStartEvent)
1589  {
1590  if (m_trackProgress)
1592  }
1593  else if (event->type() == RipStatusEvent::kCopyStartEvent)
1594  {
1595  if (m_trackPctText)
1596  m_trackPctText->SetText(tr("Copying Track ..."));
1597  }
1598  else if (event->type() == RipStatusEvent::kCopyEndEvent)
1599  {
1600  if (m_trackPctText)
1601  m_trackPctText->SetText("");
1602  }
1603  else if (event->type() == RipStatusEvent::kOverallProgressEvent)
1604  {
1605  if (m_overallProgress)
1607  }
1608  else if (event->type() == RipStatusEvent::kOverallStartEvent)
1609  {
1610  if (m_overallProgress)
1612  }
1613  else if (event->type() == RipStatusEvent::kOverallPercentEvent)
1614  {
1615  if (m_overallPctText)
1616  m_overallPctText->SetText(QString("%1%").arg(rse->m_value));
1617  }
1618  else if (event->type() == RipStatusEvent::kFinishedEvent)
1619  {
1620  emit Result(true);
1621  Close();
1622  }
1623  else if (event->type() == RipStatusEvent::kEncoderErrorEvent)
1624  {
1625  ShowOkPopup(tr("The encoder failed to create the file.\n"
1626  "Do you have write permissions"
1627  " for the music directory?"));
1628  Close();
1629  }
1630  else
1631  {
1632  LOG(VB_GENERAL, LOG_ERR, "Received an unknown event type!");
1633  }
1634 }
1635 
1637 {
1638  delete m_ripperThread;
1640  m_ripperThread->start();
1641 }
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:215
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:782
MythUITextEdit * m_genreEdit
Definition: cdrip.h:153
MythUITextEdit * m_artistEdit
Definition: cdrip.h:151
void start(QThread::Priority=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:294
void startEjectCD(void)
Definition: cdrip.cpp:1140
static bool DeleteFile(const QString &url)
Definition: remotefile.cpp:418
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:46
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:863
void searchAlbum(void)
Definition: cdrip.cpp:1270
void updateTrackLengths(void)
Definition: cdrip.cpp:1420
void switchTitlesAndArtists()
Definition: cdrip.cpp:1071
MythUITextEdit * m_albumEdit
Definition: cdrip.h:152
QString m_musicStorageDir
Definition: cdrip.h:89
QString m_CDdevice
Definition: cdrip.h:175
int m_quality
Definition: cdrip.h:232
MythUIButton * m_switchTitleArtist
Definition: cdrip.h:161
Dialog asking for user confirmation.
void customEvent(QEvent *) override
Definition: cdrip.cpp:1441
static long int getSectorCount(QString &cddevice, int tracknum)
Definition: cdrip.cpp:120
bool TranslateKeyPress(const QString &context, QKeyEvent *e, QStringList &actions, bool allowJumps=true)
Get a list of actions for a keypress in the given context.
MythUIText * m_trackPctText
Definition: cdrip.h:239
MythConfirmationDialog * ShowOkPopup(const QString &message, QObject *parent, const char *slot, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
void deleteAllExistingTracks(void)
Definition: cdrip.cpp:843
MythUIButton * m_searchGenreButton
Definition: cdrip.h:166
void customEvent(QEvent *event) override
Definition: cdrip.cpp:1542
void setYear(int lyear)
MusicMetadata * metadata
Definition: cdrip.h:50
bool IsActive(void) const
void customEvent(QEvent *event) override
Definition: mythuitype.cpp:977
void SetData(QVariant data)
void SaveSetting(const QString &key, int newValue)
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:102
MythUIProgressBar * m_trackProgress
Definition: cdrip.h:241
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:125
QString Genre() const
int getNumTracks()
Definition: cddecoder.cpp:450
CDRipperThread * m_ripperThread
Definition: cdrip.h:243
bool wait(unsigned long time=ULONG_MAX)
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:311
Basic menu dialog, message and a list of options.
int genre_table_size
Definition: genres.cpp:153
void setLength(int llength)
MythMediaDevice * GetMedia(const QString &path)
Get media device by pathname.
virtual void StartMonitoring(void)
Start the monitoring thread if needed.
static Type kTrackProgressEvent
Definition: cdrip.h:197
RipStatus * m_parent
Definition: cdrip.h:77
void SetFilter(InputFilter filter)
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:136
int GetItemPos(MythUIButtonListItem *item) const
void scanCD(void)
Definition: cdrip.cpp:827
static bool CopyFile(const QString &src, const QString &dst, bool overwrite=false, bool verify=false)
Definition: remotefile.cpp:587
static QStringList fillFieldList(const QString &field)
int Length() const
MythScreenStack * GetStack(const QString &stackname)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static Type kTrackStartEvent
Definition: cdrip.h:199
bool isNew
Definition: cdrip.h:53
void setFilename(const QString &lfilename)
MythScreenStack * GetMainStack()
bool Create(void) override
MusicMetadata * getMetadata(void)
Definition: cddecoder.cpp:538
void ShowMenu(void) override
Definition: cdrip.cpp:661
bool m_quit
Definition: cdrip.h:78
MythUIButton * m_scanButton
Definition: cdrip.h:162
static MythThemedMenu * menu
MythUICheckBox * m_compilationCheck
Definition: cdrip.h:156
InputFilter
bool keyPressEvent(QKeyEvent *) override
Key event handler.
Definition: cdrip.cpp:1509
static guint32 * tmp
Definition: goom_core.c:35
void ShowConflictMenu(RipTrack *)
Definition: cdrip.cpp:1395
void ripFinished(void)
void BuildFocusList(void)
void setGenre(const QString &genre)
Definition: cdrip.cpp:1318
static Type kEventType
Definition: mythdialogbox.h:50
void setHostname(const QString &host)
QString m_year
Definition: cdrip.h:170
static QStringList getGroupDirs(const QString &groupname, const QString &host)
void StopMonitoring(void)
Stop the monitoring thread if needed.
void showEditMetadataDialog(void)
Definition: cdrip.cpp:683
void toggleTrackActive(MythUIButtonListItem *)
Definition: cdrip.cpp:1366
static LCD * Get(void)
Definition: lcddevice.cpp:65
QString GetConfDir(void)
Definition: mythdirs.cpp:224
QVariant value(int i) const
Definition: mythdbcon.h:198
virtual void Close()
void setCheckable(bool flag)
void startRipper(void)
Definition: cdrip.cpp:1098
void setCompilation(bool state)
QString Artist() const
MythUIButtonListItem * GetItemByData(const QVariant &data)
MythUIButton * m_searchAlbumButton
Definition: cdrip.h:165
void dumpToDatabase(void)
Ripper(MythScreenStack *parent, QString device)
Definition: cdrip.cpp:519
bool GetBooleanCheckState(void) const
void updateTrackList(void)
Definition: cdrip.cpp:1189
MythUITextEdit * m_yearEdit
Definition: cdrip.h:154
void ScanFinished(void)
Definition: cdrip.cpp:751
virtual void SetVisible(bool visible)
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
void EjectFinished(void)
Definition: cdrip.cpp:1155
void SetMaxLength(const int length)
int m_lastOverallPct
Definition: cdrip.h:87
static Type kCopyStartEvent
Definition: cdrip.h:203
~RipStatus(void)
Definition: cdrip.cpp:1481
int ripTrack(QString &cddevice, Encoder *encoder, int tracknum)
Definition: cdrip.cpp:398
static Type kTrackPercentEvent
Definition: cdrip.h:198
void startRip(void)
Definition: cdrip.cpp:1636
QString GenMythURL(const QString &host=QString(), int port=0, QString path=QString(), const QString &storageGroup=QString())
QString GetSetting(const QString &key, const QString &defaultval="")
void setDevice(const QString &dev)
Definition: cddecoder.cpp:107
static MediaMonitor * GetMediaMonitor(void)
void setAlbum(const QString &lalbum, const QString &lalbum_sort=nullptr)
bool isRunning(void) const
Definition: mthread.cpp:274
bool isActive(void) const
Definition: mythdbcon.h:204
QString m_albumName
Definition: cdrip.h:170
int m_lastTrackPct
Definition: cdrip.h:86
void SetReturnEvent(QObject *retobject, const QString &resultid)
void setAlbum(const QString &album)
Definition: cdrip.cpp:1289
CDEjectorThread * m_ejectThread
Definition: cdrip.h:177
int Track() const
void RipComplete(bool result)
Definition: cdrip.cpp:1122
void OpenBusyPopup(const QString &message="")
void artistChanged(void)
Definition: cdrip.cpp:945
bool m_mediaMonitorActive
Definition: cdrip.h:173
void searchArtist(void)
Definition: cdrip.cpp:1246
QString m_artistName
Definition: cdrip.h:170
CDScannerThread * m_scanThread
Definition: cdrip.h:178
void metadataChanged(void)
Definition: cdrip.cpp:1351
QString GetMasterHostName(void)
MythUIButton * m_searchArtistButton
Definition: cdrip.h:164
bool keyPressEvent(QKeyEvent *) override
Key event handler.
Definition: cdrip.cpp:634
void SetText(const QString &text, const QString &name="", const QString &state="")
bool Create(void) override
Definition: cdrip.cpp:1488
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:111
static MSqlQueryInfo InitCon(ConnectionReuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
bool somethingWasRipped()
Definition: cdrip.cpp:940
QString Album() const
QString m_CDdevice
Definition: cdrip.h:79
void chooseBackend(void)
Definition: cdrip.cpp:688
void Unlock(MythMediaDevice *pMedia)
decrements the MythMediaDevices reference count
uint myth_system(const QString &command, uint flags, uint timeout)
bool isCancelled(void)
Definition: cdrip.cpp:197
void ejectCD(void)
Definition: cdrip.cpp:1163
MythUIText * m_trackText
Definition: cdrip.h:236
MythUIButtonList * m_trackList
Definition: cdrip.h:158
static Type kOverallProgressEvent
Definition: cdrip.h:200
void setTitle(const QString &ltitle, const QString &ltitle_sort=nullptr)
void albumChanged(void)
Definition: cdrip.cpp:974
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
bool active
Definition: cdrip.h:51
void SetText(const QString &text, bool moveCursor=true)
MythUIType * GetFocusWidget(void) const
QVector< RipTrack * > * m_tracks
Definition: cdrip.h:168
void setArtist(const QString &lartist, const QString &lartist_sort=nullptr)
void Reset() override
Reset the widget to it's original state, should not reset changes made by the theme.
virtual MythMediaError eject(bool open_close=true)
Definition: mythmedia.cpp:305
MythMainWindow * GetMythMainWindow(void)
long int m_totalSectors
Definition: cdrip.h:83
bool Compilation() const
QString m_text
Definition: cdrip.h:191
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:202
int GetNumSetting(const QString &key, int defaultval=0)
bool Create() override
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:807
static Type kTrackTextEvent
Definition: cdrip.h:194
void genreChanged(void)
Definition: cdrip.cpp:991
bool keyPressEvent(QKeyEvent *) override
Key event handler.
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
int Year() const
void CloseBusyPopup(void)
bool ValidateAndLock(MythMediaDevice *pMedia)
Validates the MythMediaDevice and increments its reference count.
void searchGenre(void)
Definition: cdrip.cpp:1294
bool GetBoolSetting(const QString &key, bool defaultval=false)
void cancel(void)
Definition: cdrip.cpp:192
bool Create(void) override
void setArtist(const QString &artist)
Definition: cdrip.cpp:1265
static Type kFinishedEvent
Definition: cdrip.h:205
QString Title() const
bool m_somethingwasripped
Definition: cdrip.h:172
QString m_musicStorageDir
Definition: cdrip.h:147
void setFileSize(uint64_t lfilesize)
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:202
Definition: cdrip.h:48
int m_quality
Definition: cdrip.h:80
static Type kOverallPercentEvent
Definition: cdrip.h:201
QString m_genreName
Definition: cdrip.h:170
MythUIProgressBar * m_overallProgress
Definition: cdrip.h:240
MythUIText * m_overallPctText
Definition: cdrip.h:238
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
Definition: mthread.cpp:244
const char * genre_table[]
Definition: genres.cpp:1
MythUIButton * m_ripButton
Definition: cdrip.h:163
void SetCheckState(MythUIStateType::StateType state)
Definition: lcddevice.h:165
MythUIText * m_overallText
Definition: cdrip.h:235
~Ripper(void)
Definition: cdrip.cpp:550
CdDecoder * m_decoder
Definition: cdrip.h:149
static Type kStatusTextEvent
Definition: cdrip.h:196
QVector< RipTrack * > * m_tracks
Definition: cdrip.h:81
QVector< RipTrack * > * m_tracks
Definition: cdrip.h:231
QString CompilationArtist() const
virtual int addSamples(int16_t *bytes, unsigned int len)=0
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
void setSaveHost(const QString &host)
Definition: cdrip.cpp:729
void compilationChanged(bool state)
Definition: cdrip.cpp:1025
static Type kOverallTextEvent
Definition: cdrip.h:195
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:179
Screen in which all other widgets are contained and rendered.
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:139
static Type kOverallStartEvent
Definition: cdrip.h:202
QString filenameFromMetadata(MusicMetadata *track)
create a filename using the template in the settings and a MusicMetadata object
Definition: musicutils.cpp:77
MythUIText * m_statusText
Definition: cdrip.h:237
long int m_totalSectorsDone
Definition: cdrip.h:84
bool deleteExistingTrack(RipTrack *track)
Definition: cdrip.cpp:860
void setCompilationArtist(const QString &lcompilation_artist, const QString &lcompilation_artist_sort=nullptr)
Event dispatched from MythUI modal dialogs to a listening class containing a result of some form.
Definition: mythdialogbox.h:37
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:132
void DisplayState(const QString &state, const QString &name)
static Type kEncoderErrorEvent
Definition: cdrip.h:206
void Result(bool)
QStringList m_searchList
Definition: cdrip.h:171
void startScanCD(void)
Definition: cdrip.cpp:738
int m_value
Definition: cdrip.h:192
void setChecked(CheckState state)
QString m_CDdevice
Definition: cdrip.h:233
Provide a dialog to quickly find an entry in a list.
QString GetText(void) const
int length
Definition: cdrip.h:52
bool Create(void) override
Definition: cdrip.cpp:572
MythUIButtonListItem * GetItemCurrent() const
void SetValueByData(const QVariant &data)
CDRipperThread(RipStatus *parent, QString device, QVector< RipTrack * > *tracks, int quality)
Definition: cdrip.cpp:168
static Type kCopyEndEvent
Definition: cdrip.h:204
MythUIButtonList * m_qualityList
Definition: cdrip.h:159
void setGenre(const QString &lgenre)
void yearChanged(void)
Definition: cdrip.cpp:1008