Go to the documentation of this file.
15 #include <QApplication>
20 #include <QRegularExpression>
61 #ifndef cdrom_paranoia
62 #define cdrom_paranoia cdrom_paranoia_t
63 #endif // cdrom_paranoia
65 #ifndef CD_FRAMESIZE_RAW
66 # define CD_FRAMESIZE_RAW CDIO_CD_FRAMESIZE_RAW
67 #endif // CD_FRAMESIZE_RAW
71 (QEvent::Type) QEvent::registerEventType();
73 (QEvent::Type) QEvent::registerEventType();
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();
95 (QEvent::Type) QEvent::registerEventType();
116 [[maybe_unused]]
int tracknum)
119 QByteArray devname = cddevice.toLatin1();
120 cdrom_drive *device = cdda_identify(devname.constData(), 0,
nullptr);
124 LOG(VB_GENERAL, LOG_ERR,
125 QString(
"Error: %1('%2',track=%3) failed at cdda_identify()").
126 arg(__func__, cddevice, QString::number(tracknum)));
130 if (cdda_open(device))
132 LOG(VB_GENERAL, LOG_ERR,
133 QString(
"Error: %1('%2',track=%3) failed at cdda_open() - cdda not supported").
134 arg(__func__, cddevice, QString::number(tracknum)));
140 if (cdda_track_audiop (device, tracknum))
142 cdda_verbose_set(device, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
143 long int start = cdda_track_firstsector(device, tracknum);
144 long int end = cdda_track_lastsector( device, tracknum);
146 return end - start + 1;
148 LOG(VB_GENERAL, LOG_ERR,
149 QString(
"Error: cdrip - cdda_track_audiop(%1) returned 0").arg(cddevice));
157 static void paranoia_cb(
long , paranoia_cb_mode_t )
163 QVector<RipTrack*> *tracks,
int quality) :
166 m_cdDevice(std::move(device)), m_quality(quality),
169 #ifdef WIN32 // libcdio needs the drive letter with no path
176 if (dirs.count() > 0)
208 for (
int trackno = 0; trackno <
m_tracks->size(); trackno++)
231 QApplication::postEvent(
234 QApplication::postEvent(
237 QApplication::postEvent(
250 QString lcd_tots = tr(
"Importing %1").arg(tots);
251 QList<LCDTextItem> textItems;
253 lcd_tots,
"Generic",
false));
254 lcd->switchToGeneric(textItems);
258 QString saveDir =
GetConfDir() +
"/tmp/RipTemp/";
261 std::unique_ptr<Encoder> encoder;
263 for (
int trackno = 0; trackno <
m_tracks->size(); trackno++)
268 QApplication::postEvent(
271 QString(
"Track %1 of %2")
272 .arg(trackno + 1).arg(
m_tracks->size())));
274 QApplication::postEvent(
278 track =
m_tracks->at(trackno)->metadata;
282 textstatus = track->
Title();
283 QApplication::postEvent(
287 QApplication::postEvent(
290 QApplication::postEvent(
302 if (encodertype ==
"mp3")
304 outfile = QString(
"track%1.mp3").arg(trackno);
305 encoder = std::make_unique<LameEncoder>(saveDir + outfile,
m_quality,
306 titleTrack, mp3usevbr);
310 outfile = QString(
"track%1.ogg").arg(trackno);
311 encoder = std::make_unique<VorbisEncoder>(saveDir + outfile,
m_quality,
317 outfile = QString(
"track%1.flac").arg(trackno);
318 encoder = std::make_unique<FlacEncoder>(saveDir + outfile,
m_quality,
324 QApplication::postEvent(
328 "Encoder failed to open file for writing"));
329 LOG(VB_GENERAL, LOG_ERR,
"MythMusic: Encoder failed"
330 " to open file for writing");
340 QApplication::postEvent(
343 "Failed to create encoder"));
344 LOG(VB_GENERAL, LOG_ERR,
"MythMusic: No encoder, failing");
358 QString ext = QFileInfo(outfile).suffix();
365 titleTrack->
setFileSize((quint64)QFileInfo(outfile).size());
383 if (!PostRipCDScript.isEmpty())
386 QApplication::postEvent(
393 [[maybe_unused]]
Encoder *encoder,
394 [[maybe_unused]]
int tracknum)
397 QByteArray devname = cddevice.toLatin1();
398 cdrom_drive *device = cdda_identify(devname.constData(), 0,
nullptr);
402 LOG(VB_GENERAL, LOG_ERR,
403 QString(
"cdda_identify failed for device '%1', "
404 "CDRipperThread::ripTrack(tracknum = %2) exiting.")
405 .arg(cddevice).arg(tracknum));
409 if (cdda_open(device))
411 LOG(VB_MEDIA, LOG_INFO,
412 QString(
"Error: %1('%2',track=%3) failed at cdda_open() - cdda not supported")
413 .arg(__func__, cddevice, QString::number(tracknum)));
418 cdda_verbose_set(device, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
419 long int start = cdda_track_firstsector(device, tracknum);
420 long int end = cdda_track_lastsector(device, tracknum);
421 LOG(VB_MEDIA, LOG_INFO, QString(
"%1(%2,track=%3) start=%4 end=%5")
422 .arg(__func__, cddevice).arg(tracknum).arg(
start).arg(end));
424 cdrom_paranoia *paranoia = paranoia_init(device);
427 paranoia_modeset(paranoia, PARANOIA_MODE_FULL |
428 PARANOIA_MODE_NEVERSKIP);
432 paranoia_modeset(paranoia, PARANOIA_MODE_OVERLAP);
435 paranoia_seek(paranoia,
start, SEEK_SET);
437 long int curpos =
start;
439 QApplication::postEvent(
448 int16_t *buffer = paranoia_read(paranoia, paranoia_cb);
450 if (encoder->addSamples(buffer, CD_FRAMESIZE_RAW))
467 QApplication::postEvent(
471 QApplication::postEvent(
477 int newTrackPct = (int) (100.0 / ((
double) (end -
start + 1) / (curpos -
start)));
481 QApplication::postEvent(
485 QApplication::postEvent(
495 lcd->setGenericProgress(fProgress);
507 paranoia_free(paranoia);
510 return (curpos -
start + 1) * CD_FRAMESIZE_RAW;
521 m_cdDevice(std::move(device))
538 QString command =
"rm -f " +
GetConfDir() +
"/tmp/RipTemp/*";
544 if (dirs.count() > 0)
551 QString command =
"rm -f " +
GetConfDir() +
"/tmp/RipTemp/*";
596 LOG(VB_GENERAL, LOG_ERR,
597 "Missing theme elements for screen 'cdripper'");
640 for (
int i = 0; i < actions.size() && !handled; i++)
642 const QString&
action = actions[i];
647 else if (
action ==
"MENU")
676 menu->SetReturnEvent(
this,
"menu");
678 menu->AddButton(tr(
"Edit Track Metadata"),
689 QStringList hostList;
693 QString sql =
"SELECT DISTINCT hostname "
695 "WHERE groupname = 'Music'";
702 hostList.append(query.
value(0).toString());
706 if (hostList.isEmpty())
708 LOG(VB_GENERAL, LOG_ERR,
"Ripper::chooseBackend: No backends found");
712 QString msg = tr(
"Select where to save tracks");
717 if (!searchDlg->Create())
733 if (dirs.count() > 0)
742 QString message = tr(
"Scanning CD. Please Wait ...");
759 bool isCompilation =
false;
773 ripTrack->metadata = metadata;
774 ripTrack->length = metadata->
Length();
778 isCompilation =
true;
795 QString title = metadata->
Title();
798 ripTrack->active = ripTrack->isNew;
831 LOG(VB_MEDIA, LOG_INFO, QString(
"Ripper::%1 CD='%2'").
833 (void)cdio_close_tray(
m_cdDevice.toLatin1().constData(),
nullptr);
846 for (
auto it =
m_tracks->begin(); it < m_tracks->end(); ++it)
849 if (track && !track->
isNew)
870 QString artist = metadata->
Artist();
871 QString album = metadata->
Album();
872 QString title = metadata->
Title();
875 QString queryString(
"SELECT song_id, "
876 "CONCAT_WS('/', music_directories.path, music_songs.filename) AS filename "
878 "LEFT JOIN music_artists"
879 " ON music_songs.artist_id=music_artists.artist_id "
880 "LEFT JOIN music_albums"
881 " ON music_songs.album_id=music_albums.album_id "
882 "LEFT JOIN music_directories "
883 " ON music_songs.directory_id=music_directories.directory_id "
884 "WHERE artist_name REGEXP \'");
885 QString token = artist;
886 static const QRegularExpression punctuation
887 { R
"((/|\\|:|'|\,|\!|\(|\)|"|\?|\|))" };
888 token.replace(punctuation, QString("."));
889 queryString += token +
"\' AND " +
"album_name REGEXP \'";
891 token.replace(punctuation, QString(
"."));
892 queryString += token +
"\' AND " +
"name REGEXP \'";
894 token.replace(punctuation, QString(
"."));
895 queryString += token +
"\' ORDER BY artist_name, album_name,"
896 " name, song_id, filename LIMIT 1";
907 int trackID = query.
value(0).toInt();
916 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Ripper::deleteExistingTrack() "
917 "Could not delete %1")
924 deleteQuery.
prepare(
"DELETE FROM music_songs"
925 " WHERE song_id = :SONG_ID");
926 deleteQuery.
bindValue(
":SONG_ID", trackID);
927 if (!deleteQuery.
exec())
949 for (
const auto *track : std::as_const(*
m_tracks))
978 for (
const auto *track : std::as_const(*
m_tracks))
995 for (
const auto *track : std::as_const(*
m_tracks))
1012 for (
const auto *track : std::as_const(*
m_tracks))
1016 data->
setYear(newyear.toInt());
1030 for (
const auto *track : std::as_const(*
m_tracks))
1049 for (
const auto *track : std::as_const(*
m_tracks))
1077 for (
const auto *track : std::as_const(*
m_tracks))
1107 if (statusDialog->Create())
1114 delete statusDialog;
1132 lcd->switchToTime();
1141 QString message = tr(
"Ejecting CD. Please Wait ...");
1161 LOG(VB_MEDIA, LOG_INFO, __PRETTY_FUNCTION__);
1166 LOG(VB_MEDIA, LOG_INFO, QString(
"Ripper::%1 '%2'").
1168 (void)cdio_eject_media_drive(
m_cdDevice.toLatin1().constData());
1194 for (
int i = 0; i <
m_tracks->size(); i++)
1204 item->setCheckable(
true);
1206 item->SetData(QVariant::fromValue(track));
1209 item->DisplayState(
"new",
"yes");
1211 item->DisplayState(
"new",
"no");
1218 item->SetText(QString::number(metadata->
Track()),
"track");
1219 item->SetText(metadata->
Title(),
"title");
1220 item->SetText(metadata->
Artist(),
"artist");
1228 item->SetText(
"",
"length");
1239 QString msg = tr(
"Select an Artist");
1245 if (!searchDlg->Create())
1263 QString msg = tr(
"Select an Album");
1269 if (!searchDlg->Create())
1287 QString msg = tr(
"Select a Genre");
1298 if (!searchDlg->Create())
1329 editDialog->setSaveMetadataOnly();
1331 if (!editDialog->Create())
1349 QVariant data = QVariant::fromValue(track);
1365 if (pos < 0 || pos >
m_tracks->count() - 1)
1390 QString msg = tr(
"This track has been disabled because it is already "
1391 "present in the database.\n"
1392 "Do you want to permanently delete the existing "
1404 menu->SetReturnEvent(
this,
"conflictmenu");
1405 menu->AddButton(tr(
"No, Cancel"));
1406 menu->AddButtonV(tr(
"Yes, Delete"), QVariant::fromValue(track));
1407 menu->AddButton(tr(
"Yes, Delete All"));
1412 std::chrono::milliseconds length = 0ms;
1438 if (dce->GetId() ==
"conflictmenu")
1441 auto *track = dce->GetData().value<
RipTrack *>();
1451 track->
isNew =
true;
1476 lcd->switchToTime();
1505 QStringList actions;
1508 for (
int i = 0; i < actions.size() && !handled; i++)
1510 const QString&
action = actions[i];
1514 if (
action ==
"ESCAPE" &&
1541 if ((dce->GetId() ==
"stop_ripping") && (dce->GetResult() != 0))
1617 ShowOkPopup(tr(
"The encoder failed to create the file.\n"
1618 "Do you have write permissions"
1619 " for the music directory?"));
1624 LOG(VB_GENERAL, LOG_ERR,
"Received an unknown event type!");
MusicMetadata * getMetadata(void)
bool isActive(void) const
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
QSqlQuery wrapper that fetches a DB connection from the connection pool.
MythScreenStack * GetMainStack()
MythUITextEdit * m_artistEdit
void SetMaxLength(int length)
Provide a dialog to quickly find an entry in a list.
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
bool m_somethingwasripped
QString GetMasterHostName(void)
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
MythUIButtonList * m_qualityList
QVector< RipTrack * > * m_tracks
bool Create(void) override
std::chrono::milliseconds length
MythUIButton * m_searchGenreButton
bool isCancelled(void) const
void setGenre(const QString &genre)
static const Type kFinishedEvent
MythUIButton * m_searchArtistButton
bool deleteExistingTrack(RipTrack *track)
static const Type kTrackTextEvent
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
void SetReturnEvent(QObject *retobject, const QString &resultid)
void toggleTrackActive(MythUIButtonListItem *item)
bool GetBooleanCheckState(void) const
const genre_table_array genre_table
QVariant value(int i) const
MythUIProgressBar * m_trackProgress
QString formatTime(std::chrono::milliseconds msecs, QString fmt)
Format a milliseconds time value.
void deleteAllExistingTracks(void)
void customEvent(QEvent *event) override
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
void compilationChanged(bool state)
A text entry and edit widget.
CDRipperThread * m_ripperThread
void OpenBusyPopup(const QString &message="")
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Screen in which all other widgets are contained and rendered.
MythUIText * m_trackPctText
MythUIButton * m_scanButton
QString m_musicStorageDir
QString GetText(void) const
CDRipperThread(RipStatus *parent, QString device, QVector< RipTrack * > *tracks, int quality)
uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
MythUIText * m_overallText
static const Type kOverallProgressEvent
void chooseBackend(void) const
MythUIButtonList * m_trackList
~RipStatus(void) override
MythUIType * GetFocusWidget(void) const
bool m_mediaMonitorActive
static const Type kOverallTextEvent
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
void SetText(const QString &text, bool moveCursor=true)
void searchArtist(void) const
static QString GenMythURL(const QString &host=QString(), int port=0, QString path=QString(), const QString &storageGroup=QString())
bool TranslateKeyPress(const QString &Context, QKeyEvent *Event, QStringList &Actions, bool AllowJumps=true)
Get a list of actions for a keypress in the given context.
MythUIButton * m_searchAlbumButton
QVector< RipTrack * > * m_tracks
static long int getSectorCount([[maybe_unused]] QString &cddevice, [[maybe_unused]] int tracknum)
long int m_totalSectorsDone
Basic menu dialog, message and a list of options.
QString filenameFromMetadata(MusicMetadata *track)
create a filename using the template in the settings and a MusicMetadata object
static MythThemedMenu * menu
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
CDScannerThread * m_scanThread
static void DBError(const QString &where, const MSqlQuery &query)
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
static const Type kCopyEndEvent
void BuildFocusList(void)
static QStringList getGroupDirs(const QString &groupname, const QString &host)
Ripper(MythScreenStack *parent, QString device)
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
static const Type kCopyStartEvent
static const Type kTrackPercentEvent
void setArtist(const QString &artist)
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
static bool DeleteFile(const QString &url)
void SetFilter(InputFilter filter)
void ShowMenu(void) override
static const Type kStatusTextEvent
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static const Type kEncoderErrorEvent
A checkbox widget supporting three check states - on,off,half and two conditions - selected and unsel...
void setDevice(const QString &dev)
static bool CopyFile(const QString &src, const QString &dst, bool overwrite=false, bool verify=false)
void updateTrackLengths(void)
int GetNumSetting(const QString &key, int defaultval=0)
QString m_musicStorageDir
~CDRipperThread() override
static const Type kOverallPercentEvent
MythUIButton * m_switchTitleArtist
bool GetBoolSetting(const QString &key, bool defaultval=false)
void customEvent(QEvent *event) override
void updateTrackList(void)
MythUIText * m_overallPctText
static const Type kTrackStartEvent
bool Create(void) override
void RipComplete(bool result)
All purpose text widget, displays a text string.
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
void customEvent(QEvent *event) override
Dialog asking for user confirmation. Ok and optional Cancel button.
void SetCheckState(MythUIStateType::StateType state)
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
MythUIProgressBar * m_overallProgress
static const Type kTrackProgressEvent
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
MythUIText * m_statusText
Event dispatched from MythUI modal dialogs to a listening class containing a result of some form.
MythUITextEdit * m_genreEdit
void showEditMetadataDialog(void)
virtual void SetText(const QString &text)
MythUITextEdit * m_yearEdit
virtual void SetVisible(bool visible)
This is a wrapper around QThread that does several additional things.
static const Type kEventType
QVector< RipTrack * > * m_tracks
void ShowConflictMenu(RipTrack *track)
void searchAlbum(void) const
MythMainWindow * GetMythMainWindow(void)
bool isRunning(void) const
void setSaveHost(const QString &host)
MythUITextEdit * m_albumEdit
MythScreenStack * GetStack(const QString &Stackname)
void switchTitlesAndArtists()
int ripTrack(QString &cddevice, Encoder *encoder, int tracknum)
void setAlbum(const QString &album)
CDEjectorThread * m_ejectThread
void SaveSetting(const QString &key, int newValue)
MythUIButton * m_ripButton
void metadataChanged(void)
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
MythConfirmationDialog * ShowOkPopup(const QString &message, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
static const Type kOverallStartEvent
void CloseBusyPopup(void)
bool somethingWasRipped() const
QString GetSetting(const QString &key, const QString &defaultval="")
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
MythUICheckBox * m_compilationCheck