16#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
17#include <QtSystemDetection>
19#include <QApplication>
24#include <QRegularExpression>
66#define cdrom_paranoia cdrom_paranoia_t
69#ifndef CD_FRAMESIZE_RAW
70# define CD_FRAMESIZE_RAW CDIO_CD_FRAMESIZE_RAW
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();
97 (QEvent::Type) QEvent::registerEventType();
99 (QEvent::Type) QEvent::registerEventType();
120 [[maybe_unused]]
int tracknum)
123 QByteArray devname = cddevice.toLatin1();
124 cdrom_drive *device = cdda_identify(devname.constData(), 0,
nullptr);
128 LOG(VB_GENERAL, LOG_ERR,
129 QString(
"Error: %1('%2',track=%3) failed at cdda_identify()").
130 arg(__func__, cddevice, QString::number(tracknum)));
134 if (cdda_open(device))
136 LOG(VB_GENERAL, LOG_ERR,
137 QString(
"Error: %1('%2',track=%3) failed at cdda_open() - cdda not supported").
138 arg(__func__, cddevice, QString::number(tracknum)));
144 if (cdda_track_audiop (device, tracknum))
146 cdda_verbose_set(device, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
147 long int start = cdda_track_firstsector(device, tracknum);
148 long int end = cdda_track_lastsector( device, tracknum);
150 return end - start + 1;
152 LOG(VB_GENERAL, LOG_ERR,
153 QString(
"Error: cdrip - cdda_track_audiop(%1) returned 0").arg(cddevice));
161static void paranoia_cb(
long , paranoia_cb_mode_t )
167 QVector<RipTrack*> *tracks,
int quality) :
170 m_cdDevice(
std::move(device)), m_quality(quality),
180 if (dirs.count() > 0)
212 for (
int trackno = 0; trackno <
m_tracks->size(); trackno++)
237 QApplication::postEvent(
240 QApplication::postEvent(
243 QApplication::postEvent(
256 QString lcd_tots = tr(
"Importing %1").arg(tots);
257 QList<LCDTextItem> textItems;
259 lcd_tots,
"Generic",
false));
260 lcd->switchToGeneric(textItems);
264 QString saveDir =
GetConfDir() +
"/tmp/RipTemp/";
267 std::unique_ptr<Encoder> encoder;
269 for (
int trackno = 0; trackno <
m_tracks->size(); trackno++)
274 QApplication::postEvent(
277 QString(
"Track %1 of %2")
278 .arg(trackno + 1).arg(
m_tracks->size())));
280 QApplication::postEvent(
284 track =
m_tracks->at(trackno)->metadata;
288 textstatus = track->
Title();
289 QApplication::postEvent(
293 QApplication::postEvent(
296 QApplication::postEvent(
309 if (encodertype ==
"mp3")
311 outfile = QString(
"track%1.mp3").arg(trackno);
312 encoder = std::make_unique<LameEncoder>(saveDir + outfile,
m_quality,
313 titleTrack, mp3usevbr);
317 outfile = QString(
"track%1.ogg").arg(trackno);
318 encoder = std::make_unique<VorbisEncoder>(saveDir + outfile,
m_quality,
324 outfile = QString(
"track%1.flac").arg(trackno);
325 encoder = std::make_unique<FlacEncoder>(saveDir + outfile,
m_quality,
329 if (!encoder->isValid())
331 QApplication::postEvent(
335 "Encoder failed to open file for writing"));
336 LOG(VB_GENERAL, LOG_ERR,
"MythMusic: Encoder failed"
337 " to open file for writing");
346 QApplication::postEvent(
349 "Failed to create encoder"));
350 LOG(VB_GENERAL, LOG_ERR,
"MythMusic: No encoder, failing");
362 QString ext = QFileInfo(outfile).suffix();
369 titleTrack->
setFileSize((quint64)QFileInfo(outfile).size());
386 if (!PostRipCDScript.isEmpty())
389 QApplication::postEvent(
396 [[maybe_unused]]
Encoder *encoder,
397 [[maybe_unused]]
int tracknum)
400 QByteArray devname = cddevice.toLatin1();
401 cdrom_drive *device = cdda_identify(devname.constData(), 0,
nullptr);
405 LOG(VB_GENERAL, LOG_ERR,
406 QString(
"cdda_identify failed for device '%1', "
407 "CDRipperThread::ripTrack(tracknum = %2) exiting.")
408 .arg(cddevice).arg(tracknum));
412 if (cdda_open(device))
414 LOG(VB_MEDIA, LOG_INFO,
415 QString(
"Error: %1('%2',track=%3) failed at cdda_open() - cdda not supported")
416 .arg(__func__, cddevice, QString::number(tracknum)));
421 cdda_verbose_set(device, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
422 long int start = cdda_track_firstsector(device, tracknum);
423 long int end = cdda_track_lastsector(device, tracknum);
424 LOG(VB_MEDIA, LOG_INFO, QString(
"%1(%2,track=%3) start=%4 end=%5")
425 .arg(__func__, cddevice).arg(tracknum).arg(
start).arg(end));
427 cdrom_paranoia *paranoia = paranoia_init(device);
430 paranoia_modeset(paranoia, PARANOIA_MODE_FULL |
431 PARANOIA_MODE_NEVERSKIP);
435 paranoia_modeset(paranoia, PARANOIA_MODE_OVERLAP);
438 paranoia_seek(paranoia,
start, SEEK_SET);
440 long int curpos =
start;
442 QApplication::postEvent(
451 int16_t *buffer = paranoia_read(paranoia, paranoia_cb);
453 if (encoder->addSamples(buffer, CD_FRAMESIZE_RAW))
470 QApplication::postEvent(
474 QApplication::postEvent(
480 int newTrackPct = (int) (100.0 / ((
double) (end -
start + 1) / (curpos -
start)));
484 QApplication::postEvent(
488 QApplication::postEvent(
498 lcd->setGenericProgress(fProgress);
510 paranoia_free(paranoia);
513 return (curpos -
start + 1) * CD_FRAMESIZE_RAW;
524 m_cdDevice(
std::move(device))
541 QString command =
"rm -f " +
GetConfDir() +
"/tmp/RipTemp/*";
547 if (dirs.count() > 0)
554 QString command =
"rm -f " +
GetConfDir() +
"/tmp/RipTemp/*";
599 LOG(VB_GENERAL, LOG_ERR,
600 "Missing theme elements for screen 'cdripper'");
643 for (
int i = 0; i < actions.size() && !handled; i++)
645 const QString&
action = actions[i];
650 else if (
action ==
"MENU")
679 menu->SetReturnEvent(
this,
"menu");
681 menu->AddButton(tr(
"Edit Track Metadata"),
692 QStringList hostList;
696 QString sql =
"SELECT DISTINCT hostname "
698 "WHERE groupname = 'Music'";
705 hostList.append(query.
value(0).toString());
709 if (hostList.isEmpty())
711 LOG(VB_GENERAL, LOG_ERR,
"Ripper::chooseBackend: No backends found");
715 QString msg = tr(
"Select where to save tracks");
720 if (!searchDlg->Create())
736 if (dirs.count() > 0)
745 QString message = tr(
"Scanning CD. Please Wait ...");
762 bool isCompilation =
false;
770 for (
int trackno = 0; trackno < max_tracks; trackno++)
777 ripTrack->metadata = metadata;
778 ripTrack->length = metadata->
Length();
782 isCompilation =
true;
799 QString title = metadata->
Title();
802 ripTrack->active = ripTrack->isNew;
835 LOG(VB_MEDIA, LOG_INFO, QString(
"Ripper::%1 CD='%2'").
837 (void)cdio_close_tray(
m_cdDevice.toLatin1().constData(),
nullptr);
850 for (
auto it =
m_tracks->begin(); it < m_tracks->end(); ++it)
853 if (track && !track->
isNew)
874 QString artist = metadata->
Artist();
875 QString album = metadata->
Album();
876 QString title = metadata->
Title();
879 QString queryString(
"SELECT song_id, "
880 "CONCAT_WS('/', music_directories.path, music_songs.filename) AS filename "
882 "LEFT JOIN music_artists"
883 " ON music_songs.artist_id=music_artists.artist_id "
884 "LEFT JOIN music_albums"
885 " ON music_songs.album_id=music_albums.album_id "
886 "LEFT JOIN music_directories "
887 " ON music_songs.directory_id=music_directories.directory_id "
888 "WHERE artist_name REGEXP \'");
889 QString token = artist;
890 static const QRegularExpression punctuation
891 { R
"((/|\\|:|'|\,|\!|\(|\)|"|\?|\|))" };
892 token.replace(punctuation, QString("."));
893 queryString += token +
"\' AND " +
"album_name REGEXP \'";
895 token.replace(punctuation, QString(
"."));
896 queryString += token +
"\' AND " +
"name REGEXP \'";
898 token.replace(punctuation, QString(
"."));
899 queryString += token +
"\' ORDER BY artist_name, album_name,"
900 " name, song_id, filename LIMIT 1";
911 int trackID = query.
value(0).toInt();
920 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Ripper::deleteExistingTrack() "
921 "Could not delete %1")
928 deleteQuery.
prepare(
"DELETE FROM music_songs"
929 " WHERE song_id = :SONG_ID");
930 deleteQuery.
bindValue(
":SONG_ID", trackID);
931 if (!deleteQuery.
exec())
953 for (
const auto *track : std::as_const(*
m_tracks))
982 for (
const auto *track : std::as_const(*
m_tracks))
999 for (
const auto *track : std::as_const(*
m_tracks))
1016 for (
const auto *track : std::as_const(*
m_tracks))
1020 data->
setYear(newyear.toInt());
1034 for (
const auto *track : std::as_const(*
m_tracks))
1053 for (
const auto *track : std::as_const(*
m_tracks))
1081 for (
const auto *track : std::as_const(*
m_tracks))
1111 if (statusDialog->Create())
1118 delete statusDialog;
1136 lcd->switchToTime();
1145 QString message = tr(
"Ejecting CD. Please Wait ...");
1165 LOG(VB_MEDIA, LOG_INFO, __PRETTY_FUNCTION__);
1170 LOG(VB_MEDIA, LOG_INFO, QString(
"Ripper::%1 '%2'").
1172 (void)cdio_eject_media_drive(
m_cdDevice.toLatin1().constData());
1198 for (
int i = 0; i <
m_tracks->size(); i++)
1208 item->setCheckable(
true);
1210 item->SetData(QVariant::fromValue(track));
1213 item->DisplayState(
"new",
"yes");
1215 item->DisplayState(
"new",
"no");
1222 item->SetText(QString::number(metadata->
Track()),
"track");
1223 item->SetText(metadata->
Title(),
"title");
1224 item->SetText(metadata->
Artist(),
"artist");
1232 item->SetText(
"",
"length");
1243 QString msg = tr(
"Select an Artist");
1249 if (!searchDlg->Create())
1267 QString msg = tr(
"Select an Album");
1273 if (!searchDlg->Create())
1291 QString msg = tr(
"Select a Genre");
1302 if (!searchDlg->Create())
1333 editDialog->setSaveMetadataOnly();
1335 if (!editDialog->Create())
1353 QVariant data = QVariant::fromValue(track);
1369 if (pos < 0 || pos >
m_tracks->count() - 1)
1394 QString msg = tr(
"This track has been disabled because it is already "
1395 "present in the database.\n"
1396 "Do you want to permanently delete the existing "
1408 menu->SetReturnEvent(
this,
"conflictmenu");
1409 menu->AddButton(tr(
"No, Cancel"));
1410 menu->AddButtonV(tr(
"Yes, Delete"), QVariant::fromValue(track));
1411 menu->AddButton(tr(
"Yes, Delete All"));
1416 std::chrono::milliseconds length = 0ms;
1442 if (dce->GetId() ==
"conflictmenu")
1445 auto *track = dce->GetData().value<
RipTrack *>();
1455 track->
isNew =
true;
1480 lcd->switchToTime();
1509 QStringList actions;
1512 for (
int i = 0; i < actions.size() && !handled; i++)
1514 const QString&
action = actions[i];
1518 if (
action ==
"ESCAPE" &&
1545 if ((dce->GetId() ==
"stop_ripping") && (dce->GetResult() != 0))
1621 ShowOkPopup(tr(
"The encoder failed to create the file.\n"
1622 "Do you have write permissions"
1623 " for the music directory?"));
1628 LOG(VB_GENERAL, LOG_ERR,
"Received an unknown event type!");
static long int getSectorCount(QString &cddevice, int tracknum)
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
QVector< RipTrack * > * m_tracks
~CDRipperThread() override
bool isCancelled(void) const
QString m_musicStorageDir
long int m_totalSectorsDone
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
int ripTrack(QString &cddevice, Encoder *encoder, int tracknum)
CDRipperThread(RipStatus *parent, QString device, QVector< RipTrack * > *tracks, int quality)
void run() override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
void setDevice(const QString &dev)
MusicMetadata * getMetadata(void)
Event dispatched from MythUI modal dialogs to a listening class containing a result of some form.
static const Type kEventType
QSqlQuery wrapper that fetches a DB connection from the connection pool.
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
QVariant value(int i) const
bool isActive(void) const
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
This is a wrapper around QThread that does several additional things.
bool isRunning(void) const
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
Dialog asking for user confirmation.
void SetReturnEvent(QObject *retobject, const QString &resultid)
void SaveSetting(const QString &key, int newValue)
QString GetSetting(const QString &key, const QString &defaultval="")
static QString GenMythURL(const QString &host=QString(), int port=0, QString path=QString(), const QString &storageGroup=QString())
int GetNumSetting(const QString &key, int defaultval=0)
QString GetMasterHostName(void)
bool GetBoolSetting(const QString &key, bool defaultval=false)
static void DBError(const QString &where, const MSqlQuery &query)
Basic menu dialog, message and a list of options.
MythScreenStack * GetMainStack()
bool TranslateKeyPress(const QString &Context, QKeyEvent *Event, QStringList &Actions, bool AllowJumps=true)
Get a list of actions for a keypress in the given context.
MythScreenStack * GetStack(const QString &Stackname)
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
Screen in which all other widgets are contained and rendered.
void OpenBusyPopup(const QString &message="")
void BuildFocusList(void)
MythUIType * GetFocusWidget(void) const
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
void CloseBusyPopup(void)
A checkbox widget supporting three check states - on,off,half and two conditions - selected and unsel...
void SetCheckState(MythUIStateType::StateType state)
bool GetBooleanCheckState(void) const
Provide a dialog to quickly find an entry in a list.
A text entry and edit widget.
QString GetText(void) const
void SetText(const QString &text, bool moveCursor=true)
void SetMaxLength(int length)
void SetFilter(InputFilter filter)
All purpose text widget, displays a text string.
virtual void SetText(const QString &text)
void customEvent(QEvent *event) override
virtual void SetVisible(bool visible)
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
static bool CopyFile(const QString &src, const QString &dst, bool overwrite=false, bool verify=false)
static bool DeleteFile(const QString &url)
static bool Exists(const QString &url, struct stat *fileinfo)
static const Type kOverallStartEvent
static const Type kTrackPercentEvent
static const Type kOverallProgressEvent
static const Type kCopyEndEvent
static const Type kTrackTextEvent
static const Type kOverallPercentEvent
static const Type kStatusTextEvent
static const Type kTrackStartEvent
static const Type kFinishedEvent
static const Type kOverallTextEvent
static const Type kCopyStartEvent
static const Type kTrackProgressEvent
static const Type kEncoderErrorEvent
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
MythUIProgressBar * m_overallProgress
MythUIText * m_overallText
bool Create(void) override
MythUIText * m_statusText
CDRipperThread * m_ripperThread
MythUIText * m_overallPctText
void customEvent(QEvent *event) override
~RipStatus(void) override
MythUIProgressBar * m_trackProgress
MythUIText * m_trackPctText
QVector< RipTrack * > * m_tracks
MythUITextEdit * m_yearEdit
MythUICheckBox * m_compilationCheck
MythUIButton * m_searchArtistButton
bool m_somethingwasripped
MythUIButton * m_switchTitleArtist
void metadataChanged(void)
void setArtist(const QString &artist)
MythUIButton * m_searchAlbumButton
void chooseBackend(void) const
bool deleteExistingTrack(RipTrack *track)
QString m_musicStorageDir
void ShowConflictMenu(RipTrack *track)
void updateTrackLengths(void)
MythUITextEdit * m_artistEdit
void ShowMenu(void) override
void setSaveHost(const QString &host)
MythUIButtonList * m_trackList
void switchTitlesAndArtists()
void searchAlbum(void) const
MythUIButton * m_searchGenreButton
CDScannerThread * m_scanThread
void updateTrackList(void)
bool m_mediaMonitorActive
MythUIButton * m_ripButton
MythUITextEdit * m_genreEdit
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
void setGenre(const QString &genre)
bool Create(void) override
bool somethingWasRipped() const
void deleteAllExistingTracks(void)
MythUITextEdit * m_albumEdit
MythUIButton * m_scanButton
CDEjectorThread * m_ejectThread
void showEditMetadataDialog(void)
void toggleTrackActive(MythUIButtonListItem *item)
MythUIButtonList * m_qualityList
void setAlbum(const QString &album)
void searchArtist(void) const
void customEvent(QEvent *event) override
void compilationChanged(bool state)
void RipComplete(bool result)
QVector< RipTrack * > * m_tracks
Ripper(MythScreenStack *parent, QString device)
static QStringList getGroupDirs(const QString &groupname, const QString &host)
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
const genre_table_array genre_table
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
QString filenameFromMetadata(MusicMetadata *track)
create a filename using the template in the settings and a MusicMetadata object
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythConfirmationDialog * ShowOkPopup(const QString &message, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
MythMainWindow * GetMythMainWindow(void)
uint myth_system(const QString &command, uint flags, std::chrono::seconds timeout)
static MythThemedMenu * menu
QString formatTime(std::chrono::milliseconds msecs, QString fmt)
Format a milliseconds time value.
std::chrono::milliseconds length