15#include <QApplication>
20#include <QRegularExpression>
62#define cdrom_paranoia cdrom_paranoia_t
65#ifndef CD_FRAMESIZE_RAW
66# define CD_FRAMESIZE_RAW CDIO_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));
157static void paranoia_cb(
long , paranoia_cb_mode_t )
163 QVector<RipTrack*> *tracks,
int quality) :
166 m_cdDevice(
std::move(device)), m_quality(quality),
176 if (dirs.count() > 0)
208 for (
int trackno = 0; trackno <
m_tracks->size(); trackno++)
233 QApplication::postEvent(
236 QApplication::postEvent(
239 QApplication::postEvent(
252 QString lcd_tots = tr(
"Importing %1").arg(tots);
253 QList<LCDTextItem> textItems;
255 lcd_tots,
"Generic",
false));
256 lcd->switchToGeneric(textItems);
260 QString saveDir =
GetConfDir() +
"/tmp/RipTemp/";
263 std::unique_ptr<Encoder> encoder;
265 for (
int trackno = 0; trackno <
m_tracks->size(); trackno++)
270 QApplication::postEvent(
273 QString(
"Track %1 of %2")
274 .arg(trackno + 1).arg(
m_tracks->size())));
276 QApplication::postEvent(
280 track =
m_tracks->at(trackno)->metadata;
284 textstatus = track->
Title();
285 QApplication::postEvent(
289 QApplication::postEvent(
292 QApplication::postEvent(
305 if (encodertype ==
"mp3")
307 outfile = QString(
"track%1.mp3").arg(trackno);
308 encoder = std::make_unique<LameEncoder>(saveDir + outfile,
m_quality,
309 titleTrack, mp3usevbr);
313 outfile = QString(
"track%1.ogg").arg(trackno);
314 encoder = std::make_unique<VorbisEncoder>(saveDir + outfile,
m_quality,
320 outfile = QString(
"track%1.flac").arg(trackno);
321 encoder = std::make_unique<FlacEncoder>(saveDir + outfile,
m_quality,
325 if (!encoder->isValid())
327 QApplication::postEvent(
331 "Encoder failed to open file for writing"));
332 LOG(VB_GENERAL, LOG_ERR,
"MythMusic: Encoder failed"
333 " to open file for writing");
342 QApplication::postEvent(
345 "Failed to create encoder"));
346 LOG(VB_GENERAL, LOG_ERR,
"MythMusic: No encoder, failing");
358 QString ext = QFileInfo(outfile).suffix();
365 titleTrack->
setFileSize((quint64)QFileInfo(outfile).size());
382 if (!PostRipCDScript.isEmpty())
385 QApplication::postEvent(
392 [[maybe_unused]]
Encoder *encoder,
393 [[maybe_unused]]
int tracknum)
396 QByteArray devname = cddevice.toLatin1();
397 cdrom_drive *device = cdda_identify(devname.constData(), 0,
nullptr);
401 LOG(VB_GENERAL, LOG_ERR,
402 QString(
"cdda_identify failed for device '%1', "
403 "CDRipperThread::ripTrack(tracknum = %2) exiting.")
404 .arg(cddevice).arg(tracknum));
408 if (cdda_open(device))
410 LOG(VB_MEDIA, LOG_INFO,
411 QString(
"Error: %1('%2',track=%3) failed at cdda_open() - cdda not supported")
412 .arg(__func__, cddevice, QString::number(tracknum)));
417 cdda_verbose_set(device, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT);
418 long int start = cdda_track_firstsector(device, tracknum);
419 long int end = cdda_track_lastsector(device, tracknum);
420 LOG(VB_MEDIA, LOG_INFO, QString(
"%1(%2,track=%3) start=%4 end=%5")
421 .arg(__func__, cddevice).arg(tracknum).arg(
start).arg(end));
423 cdrom_paranoia *paranoia = paranoia_init(device);
426 paranoia_modeset(paranoia, PARANOIA_MODE_FULL |
427 PARANOIA_MODE_NEVERSKIP);
431 paranoia_modeset(paranoia, PARANOIA_MODE_OVERLAP);
434 paranoia_seek(paranoia,
start, SEEK_SET);
436 long int curpos =
start;
438 QApplication::postEvent(
447 int16_t *buffer = paranoia_read(paranoia, paranoia_cb);
449 if (encoder->addSamples(buffer, CD_FRAMESIZE_RAW))
466 QApplication::postEvent(
470 QApplication::postEvent(
476 int newTrackPct = (int) (100.0 / ((
double) (end -
start + 1) / (curpos -
start)));
480 QApplication::postEvent(
484 QApplication::postEvent(
494 lcd->setGenericProgress(fProgress);
506 paranoia_free(paranoia);
509 return (curpos -
start + 1) * CD_FRAMESIZE_RAW;
520 m_cdDevice(
std::move(device))
537 QString command =
"rm -f " +
GetConfDir() +
"/tmp/RipTemp/*";
543 if (dirs.count() > 0)
550 QString command =
"rm -f " +
GetConfDir() +
"/tmp/RipTemp/*";
595 LOG(VB_GENERAL, LOG_ERR,
596 "Missing theme elements for screen 'cdripper'");
639 for (
int i = 0; i < actions.size() && !handled; i++)
641 const QString&
action = actions[i];
646 else if (
action ==
"MENU")
675 menu->SetReturnEvent(
this,
"menu");
677 menu->AddButton(tr(
"Edit Track Metadata"),
688 QStringList hostList;
692 QString sql =
"SELECT DISTINCT hostname "
694 "WHERE groupname = 'Music'";
701 hostList.append(query.
value(0).toString());
705 if (hostList.isEmpty())
707 LOG(VB_GENERAL, LOG_ERR,
"Ripper::chooseBackend: No backends found");
711 QString msg = tr(
"Select where to save tracks");
716 if (!searchDlg->Create())
732 if (dirs.count() > 0)
741 QString message = tr(
"Scanning CD. Please Wait ...");
758 bool isCompilation =
false;
766 for (
int trackno = 0; trackno < max_tracks; trackno++)
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!");
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