16#if QT_VERSION >= QT_VERSION_CHECK(6,5,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")
681 menu->SetReturnEvent(
this,
"menu");
683 menu->AddButton(tr(
"Edit Track Metadata"),
694 QStringList hostList;
698 QString sql =
"SELECT DISTINCT hostname "
700 "WHERE groupname = 'Music'";
709 hostList.append(query.
value(0).toString());
713 if (hostList.isEmpty())
715 LOG(VB_GENERAL, LOG_ERR,
"Ripper::chooseBackend: No backends found");
719 QString msg = tr(
"Select where to save tracks");
724 if (!searchDlg->Create())
740 if (dirs.count() > 0)
749 QString message = tr(
"Scanning CD. Please Wait ...");
766 bool isCompilation =
false;
774 for (
int trackno = 0; trackno < max_tracks; trackno++)
781 ripTrack->metadata = metadata;
782 ripTrack->length = metadata->
Length();
786 isCompilation =
true;
803 QString title = metadata->
Title();
806 ripTrack->active = ripTrack->isNew;
839 LOG(VB_MEDIA, LOG_INFO, QString(
"Ripper::%1 CD='%2'").
841 (void)cdio_close_tray(
m_cdDevice.toLatin1().constData(),
nullptr);
854 for (
auto it =
m_tracks->begin(); it < m_tracks->end(); ++it)
857 if (track && !track->
isNew)
878 QString artist = metadata->
Artist();
879 QString album = metadata->
Album();
880 QString title = metadata->
Title();
883 QString queryString(
"SELECT song_id, "
884 "CONCAT_WS('/', music_directories.path, music_songs.filename) AS filename "
886 "LEFT JOIN music_artists"
887 " ON music_songs.artist_id=music_artists.artist_id "
888 "LEFT JOIN music_albums"
889 " ON music_songs.album_id=music_albums.album_id "
890 "LEFT JOIN music_directories "
891 " ON music_songs.directory_id=music_directories.directory_id "
892 "WHERE artist_name REGEXP \'");
893 QString token = artist;
894 static const QRegularExpression punctuation
895 { R
"((/|\\|:|'|\,|\!|\(|\)|"|\?|\|))" };
896 token.replace(punctuation, QString("."));
897 queryString += token +
"\' AND " +
"album_name REGEXP \'";
899 token.replace(punctuation, QString(
"."));
900 queryString += token +
"\' AND " +
"name REGEXP \'";
902 token.replace(punctuation, QString(
"."));
903 queryString += token +
"\' ORDER BY artist_name, album_name,"
904 " name, song_id, filename LIMIT 1";
915 int trackID = query.
value(0).toInt();
924 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Ripper::deleteExistingTrack() "
925 "Could not delete %1")
932 deleteQuery.
prepare(
"DELETE FROM music_songs"
933 " WHERE song_id = :SONG_ID");
934 deleteQuery.
bindValue(
":SONG_ID", trackID);
935 if (!deleteQuery.
exec())
957 for (
const auto *track : std::as_const(*
m_tracks))
986 for (
const auto *track : std::as_const(*
m_tracks))
1003 for (
const auto *track : std::as_const(*
m_tracks))
1020 for (
const auto *track : std::as_const(*
m_tracks))
1024 data->
setYear(newyear.toInt());
1038 for (
const auto *track : std::as_const(*
m_tracks))
1057 for (
const auto *track : std::as_const(*
m_tracks))
1085 for (
const auto *track : std::as_const(*
m_tracks))
1115 if (statusDialog->Create())
1122 delete statusDialog;
1140 lcd->switchToTime();
1149 QString message = tr(
"Ejecting CD. Please Wait ...");
1169 LOG(VB_MEDIA, LOG_INFO, __PRETTY_FUNCTION__);
1174 LOG(VB_MEDIA, LOG_INFO, QString(
"Ripper::%1 '%2'").
1176 (void)cdio_eject_media_drive(
m_cdDevice.toLatin1().constData());
1202 for (
int i = 0; i <
m_tracks->size(); i++)
1212 item->setCheckable(
true);
1214 item->SetData(QVariant::fromValue(track));
1217 item->DisplayState(
"new",
"yes");
1219 item->DisplayState(
"new",
"no");
1226 item->SetText(QString::number(metadata->
Track()),
"track");
1227 item->SetText(metadata->
Title(),
"title");
1228 item->SetText(metadata->
Artist(),
"artist");
1236 item->SetText(
"",
"length");
1247 QString msg = tr(
"Select an Artist");
1253 if (!searchDlg->Create())
1271 QString msg = tr(
"Select an Album");
1277 if (!searchDlg->Create())
1295 QString msg = tr(
"Select a Genre");
1306 if (!searchDlg->Create())
1337 editDialog->setSaveMetadataOnly();
1339 if (!editDialog->Create())
1357 QVariant data = QVariant::fromValue(track);
1373 if (pos < 0 || pos >
m_tracks->count() - 1)
1398 QString msg = tr(
"This track has been disabled because it is already "
1399 "present in the database.\n"
1400 "Do you want to permanently delete the existing "
1414 menu->SetReturnEvent(
this,
"conflictmenu");
1415 menu->AddButton(tr(
"No, Cancel"));
1416 menu->AddButtonV(tr(
"Yes, Delete"), QVariant::fromValue(track));
1417 menu->AddButton(tr(
"Yes, Delete All"));
1422 std::chrono::milliseconds length = 0ms;
1448 if (dce->GetId() ==
"conflictmenu")
1451 auto *track = dce->GetData().value<
RipTrack *>();
1461 track->
isNew =
true;
1486 lcd->switchToTime();
1515 QStringList actions;
1518 for (
int i = 0; i < actions.size() && !handled; i++)
1520 const QString&
action = actions[i];
1524 if (
action ==
"ESCAPE" &&
1551 if ((dce->GetId() ==
"stop_ripping") && (dce->GetResult() != 0))
1627 ShowOkPopup(tr(
"The encoder failed to create the file.\n"
1628 "Do you have write permissions"
1629 " for the music directory?"));
1634 LOG(VB_GENERAL, LOG_ERR,
"Received an unknown event type!");
1645#include "moc_cdrip.cpp"
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