3#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
4#include <QtEnvironmentVariables>
13#include "libmythbase/mythconfig.h"
24#include <libavformat/avformat.h>
25#include <libavcodec/avcodec.h>
39 LOG(VB_GENERAL, LOG_ERR,
"Missing --songid option");
47 LOG(VB_GENERAL, LOG_ERR, QString(
"Cannot find metadata for trackid: %1").arg(songID));
86 LOG(VB_GENERAL, LOG_ERR, QString(
"Failed to write to tag for trackid: %1").arg(songID));
102 LOG(VB_GENERAL, LOG_ERR,
"Missing --songid option");
108 LOG(VB_GENERAL, LOG_ERR,
"Missing --imagetype option");
118 LOG(VB_GENERAL, LOG_ERR, QString(
"Cannot find metadata for trackid: %1").arg(songID));
125 LOG(VB_GENERAL, LOG_ERR, QString(
"Cannot find image of type: %1").arg(
type));
132 LOG(VB_GENERAL, LOG_ERR, QString(
"Cannot find a tagger for this file: %1").arg(mdata->
Filename(
false)));
139 LOG(VB_GENERAL, LOG_ERR, QString(
"Either the image isn't embedded or the tagger doesn't support embedded images"));
152 if (!dirList.empty())
157 LOG(VB_GENERAL, LOG_ERR,
"Cannot find a directory in the 'MusicArt' storage group to save to");
162 path +=
"/AlbumArt/";
176 saveImage->save(path +
filename,
"JPEG");
195 LOG(VB_GENERAL, LOG_ERR,
"Failed to find any directories in the 'Music' storage group");
200 fscan->SearchDirs(dirList);
211 LOG(VB_GENERAL, LOG_ERR,
"Can't update the radio streams the DB schema hasn't been updated yet! Aborting");
225 LOG(VB_GENERAL, LOG_ERR,
"Missing --songid option");
234 LOG(VB_GENERAL, LOG_ERR, QString(
"Cannot find metadata for trackid: %1").arg(songID));
242 LOG(VB_GENERAL, LOG_ERR, QString(
"Cannot find file for trackid: %1").arg(songID));
246 AVFormatContext *inputFC =
nullptr;
247 AVInputFormat *fmt =
nullptr;
250 LOG(VB_GENERAL, LOG_DEBUG, QString(
"CalcTrackLength: Opening '%1'")
253 QByteArray inFileBA = musicFile.toLocal8Bit();
255 int ret = avformat_open_input(&inputFC, inFileBA.constData(), fmt,
nullptr);
259 LOG(VB_GENERAL, LOG_ERR,
"CalcTrackLength: Couldn't open input file" +
265 ret = avformat_find_stream_info(inputFC,
nullptr);
269 LOG(VB_GENERAL, LOG_ERR,
270 QString(
"CalcTrackLength: Couldn't get stream info, error #%1").arg(ret));
271 avformat_close_input(&inputFC);
276 std::chrono::seconds duration = 0s;
279 for (
uint i = 0; i < inputFC->nb_streams; i++)
281 AVStream *st = inputFC->streams[i];
282 std::array<char,256> buf {};
284 const AVCodec *pCodec = avcodec_find_decoder(st->codecpar->codec_id);
287 LOG(VB_GENERAL, LOG_WARNING,
288 QString(
"avcodec_find_decoder fail for %1").arg(st->codecpar->codec_id));
291 AVCodecContext *avctx = avcodec_alloc_context3(pCodec);
292 avcodec_parameters_to_context(avctx, st->codecpar);
293 avctx->pkt_timebase = st->time_base;
295 avcodec_string(buf.data(), buf.size(), avctx,
static_cast<int>(
false));
297 switch (inputFC->streams[i]->codecpar->codec_type)
299 case AVMEDIA_TYPE_AUDIO:
301 AVPacket *pkt = av_packet_alloc();
304 LOG(VB_GENERAL, LOG_ERR,
"packet allocation failed");
308 while (av_read_frame(inputFC, pkt) >= 0)
310 if (pkt->stream_index == (
int)i)
311 time = time + pkt->duration;
313 av_packet_unref(pkt);
316 av_packet_free(&pkt);
323 LOG(VB_GENERAL, LOG_ERR,
324 QString(
"Skipping unsupported codec %1 on stream %2")
325 .arg(inputFC->streams[i]->codecpar->codec_type).arg(i));
328 avcodec_free_context(&avctx);
332 avformat_close_input(&inputFC);
335 auto dbLength = duration_cast<std::chrono::seconds>(mdata->
Length());
336 if (dbLength != duration)
338 LOG(VB_GENERAL, LOG_INFO, QString(
"The length of this track in the database was %1s "
339 "it is now %2s").arg(dbLength.count()).arg(duration.count()));
350 LOG(VB_GENERAL, LOG_INFO, QString(
"The length of this track is unchanged %1s")
351 .arg(dbLength.count()));
369 QString path = QCoreApplication::applicationDirPath();
370 qputenv(
"PYTHONPATH",
371 QString(
"%1/../Resources/lib/%2:%1/../Resources/lib/%2/site-packages:%1/../Resources/lib/%2/lib-dynload:%3")
373 .arg(QFileInfo(PYTHON_EXE).fileName())
374 .arg(QProcessEnvironment::systemEnvironment().value(
"PYTHONPATH"))
375 .toUtf8().constData());
376 QString PYTHON_LOCAL_EXE = path + QFileInfo(PYTHON_EXE).fileName();
378 QString PYTHON_LOCAL_EXE = QString(PYTHON_EXE);
382 QString lyricsDir =
GetConfDir() +
"/MythMusic/Lyrics/";
385 dir.mkpath(lyricsDir);
389 LOG(VB_GENERAL, LOG_ERR,
"Missing --songid option");
394 QString grabberName =
"ALL";
409 LOG(VB_GENERAL, LOG_ERR, QString(
"Cannot find metadata for trackid: %1").arg(songID));
417 LOG(VB_GENERAL, LOG_ERR, QString(
"Cannot find file for trackid: %1").arg(songID));
422 lyricsFile =
GetConfDir() + QString(
"/MythMusic/Lyrics/%1.txt").arg(songID);
427 if (grabberName !=
"ALL")
429 QFile::remove(lyricsFile);
434 QFile
file(QLatin1String(qPrintable(lyricsFile)));
437 if (
file.open(QIODevice::ReadOnly))
439 QTextStream stream(&
file);
441 while (!stream.atEnd())
443 lyrics.append(stream.readLine());
457 album = mdata->
Album();
458 title = mdata->
Title();
466 LOG(VB_GENERAL, LOG_ERR,
"Missing --artist option");
473 LOG(VB_GENERAL, LOG_ERR,
"Missing --album option");
480 LOG(VB_GENERAL, LOG_ERR,
"Missing --title option");
488 QString scriptDir =
GetShareDir() +
"metadata/Music/lyrics";
493 LOG(VB_GENERAL, LOG_ERR, QString(
"Cannot find lyric scripts directory: %1").arg(scriptDir));
498 d.setFilter(QDir::Files | QDir::NoDotAndDotDot);
499 d.setNameFilters(QStringList(
"*.py"));
500 QFileInfoList list =
d.entryInfoList();
503 LOG(VB_GENERAL, LOG_ERR, QString(
"Cannot find any lyric scripts in: %1").arg(scriptDir));
510 for (
const auto& fi : std::as_const(list))
512 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Found lyric script at: %1").arg(fi.filePath()));
513 scripts.append(fi.filePath());
516 QMap<int, LyricsGrabber> grabberMap;
519 for (
int x = 0; x < scripts.count(); x++)
521 QStringList
args { scripts.at(x),
"-v" };
523 p.start(PYTHON_LOCAL_EXE,
args);
524 p.waitForFinished(-1);
525 QString result =
p.readAllStandardOutput();
528#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
533 if (!domDoc.setContent(result,
false, &errorMsg, &errorLine, &errorColumn))
535 LOG(VB_GENERAL, LOG_ERR,
536 QString(
"FindLyrics: Could not parse version from %1").arg(scripts.at(x)) +
537 QString(
"\n\t\t\tError at line: %1 column: %2 msg: %3").arg(errorLine).arg(errorColumn).arg(errorMsg));
541 auto parseResult = domDoc.setContent(result);
544 LOG(VB_GENERAL, LOG_ERR,
545 QString(
"FindLyrics: Could not parse version from %1").arg(scripts.at(x)) +
546 QString(
"\n\t\t\tError at line: %1 column: %2 msg: %3")
547 .arg(parseResult.errorLine).arg(parseResult.errorColumn)
548 .arg(parseResult.errorMessage));
553 QDomNodeList itemList = domDoc.elementsByTagName(
"grabber");
554 QDomNode itemNode = itemList.item(0);
557 grabber.
m_name = itemNode.namedItem(QString(
"name")).toElement().text();
558 grabber.
m_priority = itemNode.namedItem(QString(
"priority")).toElement().text().toInt();
561 grabberMap.insert(grabber.
m_priority, grabber);
565 QMap<int, LyricsGrabber>::const_iterator i = grabberMap.constBegin();
566 while (i != grabberMap.constEnd())
572 if (grabberName !=
"ALL" && grabberName != grabber.
m_name)
575 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Trying grabber: %1, Priority: %2").arg(grabber.
m_name).arg(grabber.
m_priority));
576 QString statusMessage = QObject::tr(
"Searching '%1' for lyrics...").arg(grabber.
m_name);
581 QString(
"--artist=%1").arg(artist),
582 QString(
"--album=%1").arg(album),
583 QString(
"--title=%1").arg(title),
584 QString(
"--filename=%1").arg(
filename) };
585 p.start(PYTHON_LOCAL_EXE,
args);
586 p.waitForFinished(-1);
587 QString result =
p.readAllStandardOutput();
589 LOG(VB_GENERAL, LOG_DEBUG, QString(
"Grabber: %1, Exited with code: %2").arg(grabber.
m_name).arg(
p.exitCode()));
591 if (
p.exitCode() == 0)
593 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Lyrics Found using: %1").arg(grabber.
m_name));
598 QFile
file(QLatin1String(qPrintable(lyricsFile)));
600 if (
file.open(QIODevice::WriteOnly))
602 QTextStream stream(&
file);
AlbumArtImage * getImage(ImageType type)
static QString getTypeFilename(ImageType type)
bool toBool(const QString &key) const
Returns stored QVariant as a boolean.
int toInt(const QString &key) const
Returns stored QVariant as an integer, falling to default if not provided.
QString toString(const QString &key) const
Returns stored QVariant as a QString, falling to default if not provided.
QDateTime toDateTime(const QString &key) const
Returns stored QVariant as a QDateTime, falling to default if not provided.
QString GetHostName(void)
void SendMessage(const QString &message)
int GetNumSetting(const QString &key, int defaultval=0)
QStringList GetDirList(void) const
static bool FindDirs(const QString &group="Default", const QString &hostname="", QStringList *dirlist=nullptr)
Finds and and optionally initialize a directory list associated with a Storage Group.
QString FindFile(const QString &filename)
QString FindNextDirMostFree(void)
@ GENERIC_EXIT_OK
Exited with no error.
@ GENERIC_EXIT_INVALID_CMDLINE
Command line parse error.
@ GENERIC_EXIT_NOT_OK
Exited with error.
static const iso6937table * d
std::chrono::seconds secondsFromFloat(T value)
Helper function for convert a floating point number to a duration.
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QString GetShareDir(void)
#define ENO
This can be appended to the LOG args with "+".
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
QMap< QString, UtilFunc > UtilMap
MythCommFlagCommandLineParser cmdline