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