MythTV master
musicmetautils.cpp
Go to the documentation of this file.
1// qt
2#include <QDir>
3#include <QDomDocument>
4#include <QProcess>
5
6// libmyth* headers
9#include "libmythbase/mythconfig.h"
18
19extern "C" {
20#include <libavformat/avformat.h>
21#include <libavcodec/avcodec.h>
22}
23
24// mythutils headers
25#include "musicmetautils.h"
27
29{
30 bool ok = true;
31 int result = GENERIC_EXIT_OK;
32
33 if (cmdline.toString("songid").isEmpty())
34 {
35 LOG(VB_GENERAL, LOG_ERR, "Missing --songid option");
37 }
38 int songID = cmdline.toInt("songid");
39
41 if (!mdata)
42 {
43 LOG(VB_GENERAL, LOG_ERR, QString("Cannot find metadata for trackid: %1").arg(songID));
45 }
46
47 if (!cmdline.toString("title").isEmpty())
48 mdata->setTitle(cmdline.toString("title"));
49
50 if (!cmdline.toString("artist").isEmpty())
51 mdata->setArtist(cmdline.toString("artist"));
52
53 if (!cmdline.toString("album").isEmpty())
54 mdata->setAlbum(cmdline.toString("album"));
55
56 if (!cmdline.toString("genre").isEmpty())
57 mdata->setGenre(cmdline.toString("genre"));
58
59 if (!cmdline.toString("trackno").isEmpty())
60 mdata->setTrack(cmdline.toInt("trackno"));
61
62 if (!cmdline.toString("year").isEmpty())
63 mdata->setYear(cmdline.toInt("year"));
64
65 if (!cmdline.toString("rating").isEmpty())
66 mdata->setRating(cmdline.toInt("rating"));
67
68 if (!cmdline.toString("playcount").isEmpty())
69 mdata->setPlaycount(cmdline.toInt("playcount"));
70
71 if (!cmdline.toString("lastplayed").isEmpty())
72 mdata->setLastPlay(cmdline.toDateTime("lastplayed"));
73
74 mdata->dumpToDatabase();
75
76 MetaIO *tagger = mdata->getTagger();
77 if (tagger)
78 {
79 ok = tagger->write(mdata->getLocalFilename(), mdata);
80
81 if (!ok)
82 LOG(VB_GENERAL, LOG_ERR, QString("Failed to write to tag for trackid: %1").arg(songID));
83 }
84
85 // tell any clients that the metadata for this track has changed
86 gCoreContext->SendMessage(QString("MUSIC_METADATA_CHANGED %1").arg(songID));
87
88 if (!ok)
89 result = GENERIC_EXIT_NOT_OK;
90
91 return result;
92}
93
95{
96 if (cmdline.toString("songid").isEmpty())
97 {
98 LOG(VB_GENERAL, LOG_ERR, "Missing --songid option");
100 }
101
102 if (cmdline.toString("imagetype").isEmpty())
103 {
104 LOG(VB_GENERAL, LOG_ERR, "Missing --imagetype option");
106 }
107
108 int songID = cmdline.toInt("songid");
109 ImageType type = (ImageType)cmdline.toInt("imagetype");
110
112 if (!mdata)
113 {
114 LOG(VB_GENERAL, LOG_ERR, QString("Cannot find metadata for trackid: %1").arg(songID));
115 return GENERIC_EXIT_NOT_OK;
116 }
117
118 AlbumArtImage *image = mdata->getAlbumArtImages()->getImage(type);
119 if (!image)
120 {
121 LOG(VB_GENERAL, LOG_ERR, QString("Cannot find image of type: %1").arg(type));
122 return GENERIC_EXIT_NOT_OK;
123 }
124
125 MetaIO *tagger = mdata->getTagger();
126 if (!tagger)
127 {
128 LOG(VB_GENERAL, LOG_ERR, QString("Cannot find a tagger for this file: %1").arg(mdata->Filename(false)));
129 return GENERIC_EXIT_NOT_OK;
130 }
131
132
133 if (!image->m_embedded || !tagger->supportsEmbeddedImages())
134 {
135 LOG(VB_GENERAL, LOG_ERR, QString("Either the image isn't embedded or the tagger doesn't support embedded images"));
136 delete tagger;
137 return GENERIC_EXIT_NOT_OK;
138 }
139
140 // find the tracks actual filename
141 StorageGroup musicGroup("Music", gCoreContext->GetHostName(), false);
142 QString trackFilename = musicGroup.FindFile(mdata->Filename(false));
143
144 // where are we going to save the image
145 QString path;
146 StorageGroup artGroup("MusicArt", gCoreContext->GetHostName(), false);
147 QStringList dirList = artGroup.GetDirList();
148 if (!dirList.empty())
149 path = artGroup.FindNextDirMostFree();
150
151 if (!QDir(path).exists())
152 {
153 LOG(VB_GENERAL, LOG_ERR, "Cannot find a directory in the 'MusicArt' storage group to save to");
154 delete tagger;
155 return GENERIC_EXIT_NOT_OK;
156 }
157
158 path += "/AlbumArt/";
159 QDir dir(path);
160
161 QString filename = QString("%1-%2.jpg").arg(mdata->ID()).arg(AlbumArtImages::getTypeFilename(image->m_imageType));
162
163 if (QFile::exists(path + filename))
164 QFile::remove(path + filename);
165
166 if (!dir.exists())
167 dir.mkpath(path);
168
169 QImage *saveImage = tagger->getAlbumArt(trackFilename, image->m_imageType);
170 if (saveImage)
171 {
172 saveImage->save(path + filename, "JPEG");
173 delete saveImage;
174 }
175
176 delete tagger;
177
178 // tell any clients that the albumart for this track has changed
179 gCoreContext->SendMessage(QString("MUSIC_ALBUMART_CHANGED %1 %2").arg(songID).arg(type));
180
181 return GENERIC_EXIT_OK;
182}
183
185{
186 auto *fscan = new MusicFileScanner(cmdline.toBool("musicforce"));
187 QStringList dirList;
188
189 if (!StorageGroup::FindDirs("Music", gCoreContext->GetHostName(), &dirList))
190 {
191 LOG(VB_GENERAL, LOG_ERR, "Failed to find any directories in the 'Music' storage group");
192 delete fscan;
193 return GENERIC_EXIT_NOT_OK;
194 }
195
196 fscan->SearchDirs(dirList);
197 delete fscan;
198
199 return GENERIC_EXIT_OK;
200}
201
202static int UpdateRadioStreams(const MythUtilCommandLineParser &/*cmdline*/)
203{
204 // check we have the correct Music Schema Version (maybe the FE hasn't been run yet)
205 if (gCoreContext->GetNumSetting("MusicDBSchemaVer", 0) < 1024)
206 {
207 LOG(VB_GENERAL, LOG_ERR, "Can't update the radio streams the DB schema hasn't been updated yet! Aborting");
208 return GENERIC_EXIT_NOT_OK;
209 }
210
212 return GENERIC_EXIT_NOT_OK;
213
214 return GENERIC_EXIT_OK;
215}
216
218{
219 if (cmdline.toString("songid").isEmpty())
220 {
221 LOG(VB_GENERAL, LOG_ERR, "Missing --songid option");
223 }
224
225 int songID = cmdline.toInt("songid");
226
228 if (!mdata)
229 {
230 LOG(VB_GENERAL, LOG_ERR, QString("Cannot find metadata for trackid: %1").arg(songID));
231 return GENERIC_EXIT_NOT_OK;
232 }
233
234 QString musicFile = mdata->getLocalFilename();
235
236 if (musicFile.isEmpty() || !QFile::exists(musicFile))
237 {
238 LOG(VB_GENERAL, LOG_ERR, QString("Cannot find file for trackid: %1").arg(songID));
239 return GENERIC_EXIT_NOT_OK;
240 }
241
242 AVFormatContext *inputFC = nullptr;
243 AVInputFormat *fmt = nullptr;
244
245 // Open track
246 LOG(VB_GENERAL, LOG_DEBUG, QString("CalcTrackLength: Opening '%1'")
247 .arg(musicFile));
248
249 QByteArray inFileBA = musicFile.toLocal8Bit();
250
251 int ret = avformat_open_input(&inputFC, inFileBA.constData(), fmt, nullptr);
252
253 if (ret)
254 {
255 LOG(VB_GENERAL, LOG_ERR, "CalcTrackLength: Couldn't open input file" +
256 ENO);
257 return GENERIC_EXIT_NOT_OK;
258 }
259
260 // Getting stream information
261 ret = avformat_find_stream_info(inputFC, nullptr);
262
263 if (ret < 0)
264 {
265 LOG(VB_GENERAL, LOG_ERR,
266 QString("CalcTrackLength: Couldn't get stream info, error #%1").arg(ret));
267 avformat_close_input(&inputFC);
268 inputFC = nullptr;
269 return GENERIC_EXIT_NOT_OK;;
270 }
271
272 std::chrono::seconds duration = 0s;
273 long long time = 0;
274
275 for (uint i = 0; i < inputFC->nb_streams; i++)
276 {
277 AVStream *st = inputFC->streams[i];
278 std::array<char,256> buf {};
279
280 const AVCodec *pCodec = avcodec_find_decoder(st->codecpar->codec_id);
281 if (!pCodec)
282 {
283 LOG(VB_GENERAL, LOG_WARNING,
284 QString("avcodec_find_decoder fail for %1").arg(st->codecpar->codec_id));
285 continue;
286 }
287 AVCodecContext *avctx = avcodec_alloc_context3(pCodec);
288 avcodec_parameters_to_context(avctx, st->codecpar);
289 avctx->pkt_timebase = st->time_base;
290
291 avcodec_string(buf.data(), buf.size(), avctx, static_cast<int>(false));
292
293 switch (inputFC->streams[i]->codecpar->codec_type)
294 {
295 case AVMEDIA_TYPE_AUDIO:
296 {
297 AVPacket *pkt = av_packet_alloc();
298 if (pkt == nullptr)
299 {
300 LOG(VB_GENERAL, LOG_ERR, "packet allocation failed");
301 break;
302 }
303
304 while (av_read_frame(inputFC, pkt) >= 0)
305 {
306 if (pkt->stream_index == (int)i)
307 time = time + pkt->duration;
308
309 av_packet_unref(pkt);
310 }
311
312 av_packet_free(&pkt);
313
314 duration = secondsFromFloat(time * av_q2d(inputFC->streams[i]->time_base));
315 break;
316 }
317
318 default:
319 LOG(VB_GENERAL, LOG_ERR,
320 QString("Skipping unsupported codec %1 on stream %2")
321 .arg(inputFC->streams[i]->codecpar->codec_type).arg(i));
322 break;
323 }
324 avcodec_free_context(&avctx);
325 }
326
327 // Close input file
328 avformat_close_input(&inputFC);
329 inputFC = nullptr;
330
331 std::chrono::seconds dbLength = duration_cast<std::chrono::seconds>(mdata->Length());
332 if (dbLength != duration)
333 {
334 LOG(VB_GENERAL, LOG_INFO, QString("The length of this track in the database was %1s "
335 "it is now %2s").arg(dbLength.count()).arg(duration.count()));
336
337 // update the track length in the database
338 mdata->setLength(duration);
339 mdata->dumpToDatabase();
340
341 // tell any clients that the metadata for this track has changed
342 gCoreContext->SendMessage(QString("MUSIC_METADATA_CHANGED %1").arg(songID));
343 }
344 else
345 {
346 LOG(VB_GENERAL, LOG_INFO, QString("The length of this track is unchanged %1s")
347 .arg(dbLength.count()));
348 }
349
350 return GENERIC_EXIT_OK;
351}
352
354{
355public:
356 QString m_name;
357 QString m_filename;
358 int m_priority {99};
359};
360
362{
363
364 #ifdef Q_OS_DARWIN
365 QString path = QCoreApplication::applicationDirPath();
366 setenv("PYTHONPATH",
367 QString("%1/../Resources/lib/python3:%1/../Resources/lib/python3/site-packages:%1/../Resources/lib/python3/lib-dynload:%2")
368 .arg(path)
369 .arg(QProcessEnvironment::systemEnvironment().value("PYTHONPATH"))
370 .toUtf8().constData(), 1);
371 QString PYTHON_LOCAL_EXE = path + "python3";
372 #else
373 QString PYTHON_LOCAL_EXE = QString(PYTHON_EXE);
374 #endif
375
376 // make sure our lyrics cache directory exists
377 QString lyricsDir = GetConfDir() + "/MythMusic/Lyrics/";
378 QDir dir(lyricsDir);
379 if (!dir.exists())
380 dir.mkpath(lyricsDir);
381
382 if (cmdline.toString("songid").isEmpty())
383 {
384 LOG(VB_GENERAL, LOG_ERR, "Missing --songid option");
386 }
387
388 int songID = cmdline.toInt("songid");
389 QString grabberName = "ALL";
390 QString lyricsFile;
391 QString artist;
392 QString album;
393 QString title;
394 QString filename;
395
396 if (!cmdline.toString("grabber").isEmpty())
397 grabberName = cmdline.toString("grabber");
398
399 if (ID_TO_REPO(songID) == RT_Database)
400 {
402 if (!mdata)
403 {
404 LOG(VB_GENERAL, LOG_ERR, QString("Cannot find metadata for trackid: %1").arg(songID));
405 return GENERIC_EXIT_NOT_OK;
406 }
407
408 QString musicFile = mdata->getLocalFilename();
409
410 if (musicFile.isEmpty() || !QFile::exists(musicFile))
411 {
412 LOG(VB_GENERAL, LOG_ERR, QString("Cannot find file for trackid: %1").arg(songID));
413 //return GENERIC_EXIT_NOT_OK;
414 }
415
416 // first check if we have already saved a lyrics file for this track
417 lyricsFile = GetConfDir() + QString("/MythMusic/Lyrics/%1.txt").arg(songID);
418 if (QFile::exists(lyricsFile))
419 {
420 // if the user specified a specific grabber assume they want to
421 // re-search for the lyrics using the given grabber
422 if (grabberName != "ALL")
423 QFile::remove(lyricsFile);
424 else
425 {
426 // load these lyrics to speed up future lookups
427 QFile file(QLatin1String(qPrintable(lyricsFile)));
428 QString lyrics;
429
430 if (file.open(QIODevice::ReadOnly))
431 {
432 QTextStream stream(&file);
433
434 while (!stream.atEnd())
435 {
436 lyrics.append(stream.readLine());
437 }
438
439 file.close();
440 }
441
442 // tell any clients that a lyrics file is available for this track
443 gCoreContext->SendMessage(QString("MUSIC_LYRICS_FOUND %1 %2").arg(songID).arg(lyrics));
444
445 return GENERIC_EXIT_OK;
446 }
447 }
448
449 artist = mdata->Artist();
450 album = mdata->Album();
451 title = mdata->Title();
452 filename = mdata->getLocalFilename();
453 }
454 else
455 {
456 // must be a CD or Radio Track
457 if (cmdline.toString("artist").isEmpty())
458 {
459 LOG(VB_GENERAL, LOG_ERR, "Missing --artist option");
461 }
462 artist = cmdline.toString("artist");
463
464 if (cmdline.toString("album").isEmpty())
465 {
466 LOG(VB_GENERAL, LOG_ERR, "Missing --album option");
468 }
469 album = cmdline.toString("album");
470
471 if (cmdline.toString("title").isEmpty())
472 {
473 LOG(VB_GENERAL, LOG_ERR, "Missing --title option");
475 }
476 title = cmdline.toString("title");
477 }
478
479 // not found so try the grabbers
480 // first get a list of available grabbers
481 QString scriptDir = GetShareDir() + "metadata/Music/lyrics";
482 QDir d(scriptDir);
483
484 if (!d.exists())
485 {
486 LOG(VB_GENERAL, LOG_ERR, QString("Cannot find lyric scripts directory: %1").arg(scriptDir));
487 gCoreContext->SendMessage(QString("MUSIC_LYRICS_ERROR NO_SCRIPTS_DIR"));
488 return GENERIC_EXIT_NOT_OK;
489 }
490
491 d.setFilter(QDir::Files | QDir::NoDotAndDotDot);
492 d.setNameFilters(QStringList("*.py"));
493 QFileInfoList list = d.entryInfoList();
494 if (list.isEmpty())
495 {
496 LOG(VB_GENERAL, LOG_ERR, QString("Cannot find any lyric scripts in: %1").arg(scriptDir));
497 gCoreContext->SendMessage(QString("MUSIC_LYRICS_ERROR NO_SCRIPTS_FOUND"));
498 return GENERIC_EXIT_NOT_OK;
499 }
500
501 QStringList scripts;
502
503 for (const auto& fi : std::as_const(list))
504 {
505 LOG(VB_GENERAL, LOG_NOTICE, QString("Found lyric script at: %1").arg(fi.filePath()));
506 scripts.append(fi.filePath());
507 }
508
509 QMap<int, LyricsGrabber> grabberMap;
510
511 // query the grabbers to get their priority
512 for (int x = 0; x < scripts.count(); x++)
513 {
514 QStringList args { scripts.at(x), "-v" };
515 QProcess p;
516 p.start(PYTHON_LOCAL_EXE, args);
517 p.waitForFinished(-1);
518 QString result = p.readAllStandardOutput();
519
520 QDomDocument domDoc;
521#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
522 QString errorMsg;
523 int errorLine = 0;
524 int errorColumn = 0;
525
526 if (!domDoc.setContent(result, false, &errorMsg, &errorLine, &errorColumn))
527 {
528 LOG(VB_GENERAL, LOG_ERR,
529 QString("FindLyrics: Could not parse version from %1").arg(scripts.at(x)) +
530 QString("\n\t\t\tError at line: %1 column: %2 msg: %3").arg(errorLine).arg(errorColumn).arg(errorMsg));
531 continue;
532 }
533#else
534 auto parseResult = domDoc.setContent(result);
535 if (!parseResult)
536 {
537 LOG(VB_GENERAL, LOG_ERR,
538 QString("FindLyrics: Could not parse version from %1").arg(scripts.at(x)) +
539 QString("\n\t\t\tError at line: %1 column: %2 msg: %3")
540 .arg(parseResult.errorLine).arg(parseResult.errorColumn)
541 .arg(parseResult.errorMessage));
542 continue;
543 }
544#endif
545
546 QDomNodeList itemList = domDoc.elementsByTagName("grabber");
547 QDomNode itemNode = itemList.item(0);
548
549 LyricsGrabber grabber;
550 grabber.m_name = itemNode.namedItem(QString("name")).toElement().text();
551 grabber.m_priority = itemNode.namedItem(QString("priority")).toElement().text().toInt();
552 grabber.m_filename = scripts.at(x);
553
554 grabberMap.insert(grabber.m_priority, grabber);
555 }
556
557 // try each grabber in turn until we find a match
558 QMap<int, LyricsGrabber>::const_iterator i = grabberMap.constBegin();
559 while (i != grabberMap.constEnd())
560 {
561 LyricsGrabber grabber = i.value();
562
563 ++i;
564
565 if (grabberName != "ALL" && grabberName != grabber.m_name)
566 continue;
567
568 LOG(VB_GENERAL, LOG_NOTICE, QString("Trying grabber: %1, Priority: %2").arg(grabber.m_name).arg(grabber.m_priority));
569 QString statusMessage = QObject::tr("Searching '%1' for lyrics...").arg(grabber.m_name);
570 gCoreContext->SendMessage(QString("MUSIC_LYRICS_STATUS %1 %2").arg(songID).arg(statusMessage));
571
572 QProcess p;
573 QStringList args { grabber.m_filename,
574 QString("--artist=%1").arg(artist),
575 QString("--album=%1").arg(album),
576 QString("--title=%1").arg(title),
577 QString("--filename=%1").arg(filename) };
578 p.start(PYTHON_LOCAL_EXE, args);
579 p.waitForFinished(-1);
580 QString result = p.readAllStandardOutput();
581
582 LOG(VB_GENERAL, LOG_DEBUG, QString("Grabber: %1, Exited with code: %2").arg(grabber.m_name).arg(p.exitCode()));
583
584 if (p.exitCode() == 0)
585 {
586 LOG(VB_GENERAL, LOG_NOTICE, QString("Lyrics Found using: %1").arg(grabber.m_name));
587
588 // save these lyrics to speed up future lookups if it is a DB track
589 if (ID_TO_REPO(songID) == RT_Database)
590 {
591 QFile file(QLatin1String(qPrintable(lyricsFile)));
592
593 if (file.open(QIODevice::WriteOnly))
594 {
595 QTextStream stream(&file);
596 stream << result;
597 file.close();
598 }
599 }
600
601 gCoreContext->SendMessage(QString("MUSIC_LYRICS_FOUND %1 %2").arg(songID).arg(result));
602 return GENERIC_EXIT_OK;
603 }
604 }
605
606 // if we got here we didn't find any lyrics
607 gCoreContext->SendMessage(QString("MUSIC_LYRICS_NOTFOUND %1").arg(songID));
608
609 return GENERIC_EXIT_OK;
610}
611
613{
614 utilMap["updatemeta"] = &UpdateMeta;
615 utilMap["extractimage"] = &ExtractImage;
616 utilMap["scanmusic"] = &ScanMusic;
617 utilMap["updateradiostreams"] = &UpdateRadioStreams;
618 utilMap["calctracklen"] = &CalcTrackLength;
619 utilMap["findlyrics"] = &FindLyrics;
620}
ImageType m_imageType
Definition: musicmetadata.h:57
AlbumArtImage * getImage(ImageType type)
static QString getTypeFilename(ImageType type)
Definition: metaio.h:18
virtual bool write(const QString &filename, MusicMetadata *mdata)=0
Writes all metadata back to a file.
virtual QImage * getAlbumArt(const QString &filename, ImageType type)
Definition: metaio.h:92
virtual bool supportsEmbeddedImages(void)
Does the tag support embedded cover art.
Definition: metaio.h:57
void setYear(int lyear)
void setGenre(const QString &lgenre)
std::chrono::milliseconds Length() const
void setLength(T llength)
QString Title() const
void setTitle(const QString &ltitle, const QString &ltitle_sort=nullptr)
IdType ID() const
QString Filename(bool find=true)
QString Artist() const
void setRating(int lrating)
void setPlaycount(int lplaycount)
QString getLocalFilename(void)
try to find the track on the local file system
void setAlbum(const QString &lalbum, const QString &lalbum_sort=nullptr)
void setTrack(int ltrack)
AlbumArtImages * getAlbumArtImages(void)
static MusicMetadata * createFromID(int trackid)
MetaIO * getTagger(void)
QString Album() const
void setArtist(const QString &lartist, const QString &lartist_sort=nullptr)
static bool updateStreamList(void)
void dumpToDatabase(void)
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
Definition: storagegroup.h:23
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)
#define setenv(x, y, z)
Definition: compat.h:85
@ GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:13
@ GENERIC_EXIT_INVALID_CMDLINE
Command line parse error.
Definition: exitcodes.h:18
@ GENERIC_EXIT_NOT_OK
Exited with error.
Definition: exitcodes.h:14
unsigned int uint
Definition: freesurround.h:24
static const iso6937table * d
ImageType
Definition: musicmetadata.h:35
@ RT_Database
Definition: musicmetadata.h:66
static constexpr uint32_t ID_TO_REPO(uint32_t x)
Definition: musicmetadata.h:77
static int FindLyrics(const MythUtilCommandLineParser &cmdline)
static int ExtractImage(const MythUtilCommandLineParser &cmdline)
static int ScanMusic(const MythUtilCommandLineParser &cmdline)
static int UpdateMeta(const MythUtilCommandLineParser &cmdline)
static int UpdateRadioStreams(const MythUtilCommandLineParser &)
static int CalcTrackLength(const MythUtilCommandLineParser &cmdline)
void registerMusicUtils(UtilMap &utilMap)
std::enable_if_t< std::is_floating_point_v< T >, std::chrono::seconds > secondsFromFloat(T value)
Helper function for convert a floating point number to a duration.
Definition: mythchrono.h:80
MythCommFlagCommandLineParser cmdline
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QString GetShareDir(void)
Definition: mythdirs.cpp:261
QString GetConfDir(void)
Definition: mythdirs.cpp:263
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:74
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
QMap< QString, UtilFunc > UtilMap
Definition: mythutil.h:15
bool exists(str path)
Definition: xbmcvfs.py:51