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,
322 if (!encoder->isValid())
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))
539 QString command =
"rm -f " +
GetConfDir() +
"/tmp/RipTemp/*";
545 if (dirs.count() > 0)
552 QString command =
"rm -f " +
GetConfDir() +
"/tmp/RipTemp/*";
597 LOG(VB_GENERAL, LOG_ERR,
598 "Missing theme elements for screen 'cdripper'");
641 for (
int i = 0; i < actions.size() && !handled; i++)
643 QString
action = actions[i];
648 else if (
action ==
"MENU")
677 menu->SetReturnEvent(
this,
"menu");
679 menu->AddButton(tr(
"Edit Track Metadata"),
690 QStringList hostList;
694 QString sql =
"SELECT DISTINCT hostname "
696 "WHERE groupname = 'Music'";
703 hostList.append(query.
value(0).toString());
707 if (hostList.isEmpty())
709 LOG(VB_GENERAL, LOG_ERR,
"Ripper::chooseBackend: No backends found");
713 QString msg = tr(
"Select where to save tracks");
718 if (!searchDlg->Create())
734 if (dirs.count() > 0)
743 QString message = tr(
"Scanning CD. Please Wait ...");
760 bool isCompilation =
false;
774 ripTrack->metadata = metadata;
775 ripTrack->length = metadata->
Length();
779 isCompilation =
true;
796 QString title = metadata->
Title();
799 ripTrack->active = ripTrack->isNew;
830 LOG(VB_MEDIA, LOG_INFO, QString(
"Ripper::%1 CD='%2'").
832 (void)cdio_close_tray(
m_cdDevice.toLatin1().constData(),
nullptr);
845 for (
auto it =
m_tracks->begin(); it < m_tracks->end(); ++it)
848 if (track && !track->
isNew)
869 QString artist = metadata->
Artist();
870 QString album = metadata->
Album();
871 QString title = metadata->
Title();
874 QString queryString(
"SELECT song_id, "
875 "CONCAT_WS('/', music_directories.path, music_songs.filename) AS filename "
877 "LEFT JOIN music_artists"
878 " ON music_songs.artist_id=music_artists.artist_id "
879 "LEFT JOIN music_albums"
880 " ON music_songs.album_id=music_albums.album_id "
881 "LEFT JOIN music_directories "
882 " ON music_songs.directory_id=music_directories.directory_id "
883 "WHERE artist_name REGEXP \'");
884 QString token = artist;
885 static const QRegularExpression punctuation
886 { R
"((/|\\|:|'|\,|\!|\(|\)|"|\?|\|))" };
887 token.replace(punctuation, QString("."));
888 queryString += token +
"\' AND " +
"album_name REGEXP \'";
890 token.replace(punctuation, QString(
"."));
891 queryString += token +
"\' AND " +
"name REGEXP \'";
893 token.replace(punctuation, QString(
"."));
894 queryString += token +
"\' ORDER BY artist_name, album_name,"
895 " name, song_id, filename LIMIT 1";
906 int trackID = query.
value(0).toInt();
915 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Ripper::deleteExistingTrack() "
916 "Could not delete %1")
923 deleteQuery.
prepare(
"DELETE FROM music_songs"
924 " WHERE song_id = :SONG_ID");
925 deleteQuery.
bindValue(
":SONG_ID", trackID);
926 if (!deleteQuery.
exec())
948 for (
const auto *track : qAsConst(*
m_tracks))
977 for (
const auto *track : qAsConst(*
m_tracks))
994 for (
const auto *track : qAsConst(*
m_tracks))
1011 for (
const auto *track : qAsConst(*
m_tracks))
1015 data->
setYear(newyear.toInt());
1029 for (
const auto *track : qAsConst(*
m_tracks))
1048 for (
const auto *track : qAsConst(*
m_tracks))
1076 for (
const auto *track : qAsConst(*
m_tracks))
1106 if (statusDialog->Create())
1112 delete statusDialog;
1129 lcd->switchToTime();
1138 QString message = tr(
"Ejecting CD. Please Wait ...");
1158 LOG(VB_MEDIA, LOG_INFO, __PRETTY_FUNCTION__);
1163 LOG(VB_MEDIA, LOG_INFO, QString(
"Ripper::%1 '%2'").
1165 (void)cdio_eject_media_drive(
m_cdDevice.toLatin1().constData());
1191 for (
int i = 0; i <
m_tracks->size(); i++)
1201 item->setCheckable(
true);
1203 item->SetData(QVariant::fromValue(track));
1206 item->DisplayState(
"new",
"yes");
1208 item->DisplayState(
"new",
"no");
1215 item->SetText(QString::number(metadata->
Track()),
"track");
1216 item->SetText(metadata->
Title(),
"title");
1217 item->SetText(metadata->
Artist(),
"artist");
1224 item->SetText(
"",
"length");
1234 QString msg = tr(
"Select an Artist");
1240 if (!searchDlg->Create())
1258 QString msg = tr(
"Select an Album");
1264 if (!searchDlg->Create())
1282 QString msg = tr(
"Select a Genre");
1293 if (!searchDlg->Create())
1324 editDialog->setSaveMetadataOnly();
1326 if (!editDialog->Create())
1344 QVariant data = QVariant::fromValue(track);
1360 if (pos < 0 || pos >
m_tracks->count() - 1)
1385 QString msg = tr(
"This track has been disabled because it is already "
1386 "present in the database.\n"
1387 "Do you want to permanently delete the existing "
1399 menu->SetReturnEvent(
this,
"conflictmenu");
1400 menu->AddButton(tr(
"No, Cancel"));
1401 menu->AddButtonV(tr(
"Yes, Delete"), QVariant::fromValue(track));
1402 menu->AddButton(tr(
"Yes, Delete All"));
1407 std::chrono::milliseconds length = 0ms;
1433 if (dce->GetId() ==
"conflictmenu")
1436 auto *track = dce->GetData().value<
RipTrack *>();
1446 track->
isNew =
true;
1471 lcd->switchToTime();
1500 QStringList actions;
1503 for (
int i = 0; i < actions.size() && !handled; i++)
1505 QString
action = actions[i];
1509 if (
action ==
"ESCAPE" &&
1534 if ((dce->GetId() ==
"stop_ripping") && (dce->GetResult() != 0))
1610 ShowOkPopup(tr(
"The encoder failed to create the file.\n"
1611 "Do you have write permissions"
1612 " for the music directory?"));
1617 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