7#include <QRegularExpression>
34 QString hash, QString trailer,
35 QString coverfile, QString screenshot, QString banner,
36 QString fanart,
const QString &title, QString sortTitle,
37 const QString &subtitle, QString sortSubtitle,
38 QString tagline,
int year,
const QDate releasedate,
39 QString inetref,
int collectionref, QString homepage,
40 QString director, QString studio,
41 QString plot,
float userrating,
42 QString
rating,
int length,
int playcount,
43 int season,
int episode,
const QDate insertdate,
45 int childID,
bool browse,
bool watched,
46 QString playcommand, QString category,
51 bool processed =
false,
73 if (title.isEmpty() and subtitle.isEmpty()
74 and season == 0 and episode == 0)
172 void SetTitle(
const QString& title,
const QString& sortTitle =
"")
181 void SetSubtitle(
const QString &subtitle,
const QString &sortSubtitle =
"") {
331 QString
GetImage(
const QString &name)
const;
413 d.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
414 QFileInfoList contents =
d.entryInfoList();
415 if (contents.empty())
417 return d.rmdir(dirName);
420 for (
const auto& entry : std::as_const(contents))
424 QString fileName = entry.fileName();
430 if (!QFile(entry.fileName()).remove())
434 return d.rmdir(dirName);
440 bool isremoved =
false;
462 LOG(VB_GENERAL, LOG_DEBUG, QString(
"Could not delete file: %1")
501 for (
long value : genres.
values)
519 for (
long value : countries.
values)
537 for (
long value : cast.
values)
542 m_cast.emplace_back(value, name);
625 QString coverfile = metadataMap[
"coverfile"];
626 QString screenshot = metadataMap[
"screenshotfile"];
627 QString bannerfile = metadataMap[
"bannerfile"];
628 QString fanartfile = metadataMap[
"fanartfile"];
644 if (m_userrating < -10.0F || m_userrating > 10.0F)
656 bool inserting =
m_id == 0;
666 query.
prepare(
"INSERT INTO videometadata (title,subtitle,tagline,director,studio,plot,"
667 "rating,year,userrating,length,season,episode,filename,hash,"
668 "showlevel,coverfile,inetref,homepage,browse,watched,trailer,"
669 "screenshot,banner,fanart,host,processed,contenttype) VALUES (:TITLE, :SUBTITLE, "
670 ":TAGLINE, :DIRECTOR, :STUDIO, :PLOT, :RATING, :YEAR, :USERRATING, "
671 ":LENGTH, :SEASON, :EPISODE, :FILENAME, :HASH, :SHOWLEVEL, "
672 ":COVERFILE, :INETREF, :HOMEPAGE, :BROWSE, :WATCHED, "
673 ":TRAILER, :SCREENSHOT, :BANNER, :FANART, :HOST, :PROCESSED, :CONTENTTYPE)");
677 query.
prepare(
"UPDATE videometadata SET title = :TITLE, subtitle = :SUBTITLE, "
678 "tagline = :TAGLINE, director = :DIRECTOR, studio = :STUDIO, "
679 "plot = :PLOT, rating= :RATING, year = :YEAR, "
680 "releasedate = :RELEASEDATE, userrating = :USERRATING, "
681 "length = :LENGTH, playcount = :PLAYCOUNT, season = :SEASON, "
682 "episode = :EPISODE, filename = :FILENAME, hash = :HASH, trailer = :TRAILER, "
683 "showlevel = :SHOWLEVEL, coverfile = :COVERFILE, "
684 "screenshot = :SCREENSHOT, banner = :BANNER, fanart = :FANART, "
685 "inetref = :INETREF, collectionref = :COLLECTION, homepage = :HOMEPAGE, "
686 "browse = :BROWSE, watched = :WATCHED, host = :HOST, playcommand = :PLAYCOMMAND, "
687 "childid = :CHILDID, category = :CATEGORY, processed = :PROCESSED, "
688 "contenttype = :CONTENTTYPE WHERE intid = :INTID");
738 if (!query.
exec(
"SELECT LAST_INSERT_ID()") || !query.
next())
748 LOG(VB_GENERAL, LOG_ERR,
749 QString(
"%1: The id of the last inserted row to "
750 "videometadata seems to be 0. This is odd.")
778 query.
prepare(
"DELETE FROM videometadata WHERE intid = :ID");
785 query.
prepare(
"DELETE FROM filemarkup WHERE filename = :FILENAME");
814 LOG(VB_GENERAL, LOG_ERR,
"Unknown category id");
828 if (!genre->second.trimmed().isEmpty())
849 if (!country->second.trimmed().isEmpty())
867 auto cast =
m_cast.begin();
868 while (cast !=
m_cast.end())
870 if (!cast->second.trimmed().isEmpty())
878 cast =
m_cast.erase(cast);
899 imageMap[QStringLiteral(u
"coverfile")] = coverfile;
900 imageMap[QStringLiteral(u
"coverart")] = coverfile;
902 QString screenshotfile;
914 imageMap[QStringLiteral(u
"screenshotfile")] = screenshotfile;
915 imageMap[QStringLiteral(u
"screenshot")] = screenshotfile;
929 imageMap[QStringLiteral(u
"bannerfile")] = bannerfile;
930 imageMap[QStringLiteral(u
"banner")] = bannerfile;
944 imageMap[QStringLiteral(u
"fanartfile")] = fanartfile;
945 imageMap[QStringLiteral(u
"fanart")] = fanartfile;
947 QString smartimage = coverfile;
949 smartimage = screenshotfile;
950 imageMap[QStringLiteral(u
"smartimage")] = smartimage;
957 if ((name == QStringLiteral(u
"coverfile")) ||
958 (name == QStringLiteral(u
"coverart")))
962 && !coverfile.startsWith(u
'/')
963 && !coverfile.isEmpty()
970 if ((name == QStringLiteral(u
"screenshotfile")) ||
971 (name == QStringLiteral(u
"screenshot")))
974 if (
IsHostSet() && !screenshot.startsWith(u
'/')
975 && !screenshot.isEmpty())
981 if ((name == QStringLiteral(u
"bannerfile")) ||
982 (name == QStringLiteral(u
"banner")))
985 if (
IsHostSet() && !bannerfile.startsWith(u
'/')
986 && !bannerfile.isEmpty())
992 if ((name == QStringLiteral(u
"fanartfile")) ||
993 (name == QStringLiteral(u
"fanart")))
996 if (
IsHostSet() && !fanartfile.startsWith(
'/')
997 && !fanartfile.isEmpty())
1003 if ((name == QStringLiteral(u
"smartimage")) ||
1004 (name == QStringLiteral(u
"buttonimage")))
1008 QString screenshotfile =
GetImage(
"screenshot");
1009 if (!screenshotfile.isEmpty())
1010 return screenshotfile;
1023 QString
eatBraces(
const QString &title,
const QString &left_brace,
1024 const QString &right_brace)
1027 bool keep_checking =
true;
1029 while (keep_checking)
1031 int left_position = ret.indexOf(left_brace);
1032 int right_position = ret.indexOf(right_brace);
1033 if (left_position == -1 || right_position == -1)
1039 keep_checking =
false;
1043 if (left_position < right_position)
1049 ret = ret.left(left_position) +
1050 ret.right(ret.length() - right_position - 1);
1052 else if (left_position > right_position)
1058 ret = ret.left(right_position) +
1059 ret.right(ret.length() - left_position - 1);
1069 const QString &file_name,
1070 const QString &host)
1074 query.
prepare(
"SELECT intid,filename FROM videometadata WHERE "
1087 int intid = query.
value(0).toInt();
1088 QString oldfilename = query.
value(1).toString();
1090 query.
prepare(
"UPDATE videometadata SET filename = :FILENAME, "
1091 "host = :HOST WHERE intid = :INTID");
1092 query.
bindValue(
":FILENAME", file_name);
1098 MythDB::DBError(
"Video hashed metadata update (videometadata)", query);
1102 query.
prepare(
"UPDATE filemarkup SET filename = :FILENAME "
1103 "WHERE filename = :OLDFILENAME");
1104 query.
bindValue(
":FILENAME", file_name);
1105 query.
bindValue(
":OLDFILENAME", oldfilename);
1117 const QString &host)
1125 QString fullname = sgroup.
FindFile(file_name);
1140 QString cleanFilename = file_name.left(file_name.lastIndexOf(
'.'));
1141 static const QRegularExpression kSpaceRE {
"%20" };
1142 static const QRegularExpression kUnderscoreRE {
"_" };
1143 static const QRegularExpression kDotRE {
"\\." };
1144 cleanFilename.replace(kSpaceRE,
" ");
1145 cleanFilename.replace(kUnderscoreRE,
" ");
1146 cleanFilename.replace(kDotRE,
" ");
1151 QString season_translation = tr(
"Season",
"Metadata file name parsing");
1156 QString episode_translation = tr(
"Episode",
"Metadata file name parsing");
1159 QString separator =
"(?:\\s?(?:-|/)?\\s?)?";
1160 QString regexp = QString(
1163 "(?:s|(?:Season|%2))?"
1167 "(?:[ex/]|Episode|%3)"
1172 ).arg(separator, season_translation, episode_translation);
1173 static const QRegularExpression filename_parse { regexp,
1174 QRegularExpression::CaseInsensitiveOption|QRegularExpression::UseUnicodePropertiesOption };
1177 QString regexp2 = QString(
"(%1(?:(?:Season|%2)%1\\d*%1)*%1)$")
1178 .arg(separator, season_translation);
1179 static const QRegularExpression title_parse {regexp2,
1180 QRegularExpression::CaseInsensitiveOption|QRegularExpression::UseUnicodePropertiesOption };
1182 auto match = filename_parse.match(cleanFilename);
1183 if (match.hasMatch())
1186 if (position == 1 && !match.capturedView(1).isEmpty())
1189 QString title = match.captured(1);
1190 match = title_parse.match(title);
1191 if (match.hasMatch())
1192 title = title.left(match.capturedStart());
1193 title = title.right(title.length() - title.lastIndexOf(
'/') -1);
1194 return title.trimmed();
1197 return match.captured(2).trimmed();
1199 return match.captured(3).trimmed();
1201 return match.captured(4).trimmed();
1203 else if (position == 1)
1205 QString title = cleanFilename;
1208 title = title.right(title.length() -
1209 title.lastIndexOf(
'/') -1);
1214 return title.trimmed();
1216 else if (position == 2 || position == 3)
1225 const QString &hash,
const QString &trailer,
const QString &coverfile,
1226 const QString &screenshot,
const QString &banner,
const QString &fanart,
1227 const QString &title,
const QString &sortTitle,
1228 const QString &subtitle,
const QString &sortSubtitle,
const QString &tagline,
1229 int year,
const QDate releasedate,
const QString &inetref,
int collectionref,
1230 const QString &homepage,
const QString &director,
const QString &studio,
1231 const QString &plot,
float userrating,
const QString &
rating,
1232 int length,
int playcount,
int season,
int episode,
const QDate insertdate,
1234 int childID,
bool browse,
bool watched,
1235 const QString &playcommand,
const QString &category,
1239 const QString &host,
bool processed,
1243 screenshot, banner, fanart, title, sortTitle, subtitle,
1244 sortSubtitle, tagline, year, releasedate, inetref,
1245 collectionref, homepage, director, studio, plot, userrating,
rating,
1246 length, playcount, season, episode, insertdate,
id, showlevel, categoryID,
1247 childID, browse, watched, playcommand, category, genres, countries,
1248 cast, host, processed, contenttype);
1280 metadataMap[QStringLiteral(u
"filename")] =
GetFilename();
1282 metadataMap[QStringLiteral(u
"title")] =
GetTitle();
1283 metadataMap[QStringLiteral(u
"sorttitle")] =
GetSortTitle();
1284 metadataMap[QStringLiteral(u
"subtitle")] =
GetSubtitle();
1286 metadataMap[QStringLiteral(u
"tagline")] =
GetTagline();
1287 metadataMap[QStringLiteral(u
"director")] =
GetDirector();
1288 metadataMap[QStringLiteral(u
"studio")] =
GetStudio();
1289 metadataMap[QStringLiteral(u
"description0")] = metadataMap[QStringLiteral(u
"description")] =
GetPlot();
1292 metadataMap[QStringLiteral(u
"cast")] =
GetDisplayCast(*this).join(
", ");
1295 metadataMap[QStringLiteral(u
"playcount")] = QString::number(
GetPlayCount());
1307 QString usingSE = QStringLiteral(u
"s%1e%2")
1310 metadataMap[QStringLiteral(u
"s##e##")] = metadataMap[QStringLiteral(u
"s00e00")] = usingSE;
1311 QString usingX = QStringLiteral(u
"%1x%2")
1314 metadataMap[QStringLiteral(u
"##x##")] = metadataMap[QStringLiteral(u
"00x00")] = usingX;
1318 metadataMap[QStringLiteral(u
"s##e##")] = metadataMap[QStringLiteral(u
"##x##")] = QString();
1319 metadataMap[QStringLiteral(u
"s00e00")] = metadataMap[QStringLiteral(u
"00x00")] = QString();
1320 metadataMap[QStringLiteral(u
"season")] = metadataMap[QStringLiteral(u
"episode")] = QString();
1327 metadataMap[QStringLiteral(u
"inetref")] =
GetInetRef();
1328 metadataMap[QStringLiteral(u
"homepage")] =
GetHomepage();
1329 metadataMap[QStringLiteral(u
"child_id")] = QString::number(
GetChildID());
1333 metadataMap[QStringLiteral(u
"category")] =
GetCategory();
1340 if (name == QStringLiteral(u
"filename"))
1342 if (name == QStringLiteral(u
"sortfilename"))
1344 if (name == QStringLiteral(u
"title"))
1346 if (name == QStringLiteral(u
"sorttitle"))
1348 if (name == QStringLiteral(u
"subtitle"))
1350 if (name == QStringLiteral(u
"sortsubtitle"))
1352 if (name == QStringLiteral(u
"tagline"))
1354 if (name == QStringLiteral(u
"director"))
1356 if (name == QStringLiteral(u
"studio"))
1358 if ((name == QStringLiteral(u
"description")) ||
1359 (name == QStringLiteral(u
"description0")))
1361 if (name == QStringLiteral(u
"genres"))
1363 if (name == QStringLiteral(u
"countries"))
1365 if (name == QStringLiteral(u
"cast"))
1367 if (name == QStringLiteral(u
"rating"))
1369 if (name == QStringLiteral(u
"length"))
1371 if (name == QStringLiteral(u
"playcount"))
1373 if (name == QStringLiteral(u
"year"))
1376 if (name == QStringLiteral(u
"releasedate"))
1379 if (name == QStringLiteral(u
"userrating"))
1384 if (name == QStringLiteral(u
"season"))
1386 if (name == QStringLiteral(u
"episode"))
1388 if ((name == QStringLiteral(u
"s##e##")) ||
1389 (name == QStringLiteral(u
"s00e00")))
1391 return QStringLiteral(u
"s%1e%2")
1395 if ((name == QStringLiteral(u
"##x##")) ||
1396 (name == QStringLiteral(u
"00x00")))
1398 return QStringLiteral(u
"%1x%2")
1404 if (name == QStringLiteral(u
"insertdate"))
1407 if (name == QStringLiteral(u
"inetref"))
1409 if (name == QStringLiteral(u
"homepage"))
1411 if (name == QStringLiteral(u
"child_id"))
1413 if (name == QStringLiteral(u
"browseable"))
1415 if (name == QStringLiteral(u
"watched"))
1417 if (name == QStringLiteral(u
"processed"))
1419 if (name == QStringLiteral(u
"category"))
1427 stateMap[QStringLiteral(u
"userratingstate")] =
1437 if (name == QStringLiteral(u
"trailerstate"))
1439 if (name == QStringLiteral(u
"userratingstate"))
1441 if (name == QStringLiteral(u
"watchedstate"))
1443 if (name == QStringLiteral(u
"videolevel"))
1460 metadataMap[
"coverfile"] =
"";
1461 metadataMap[
"screenshotfile"] =
"";
1462 metadataMap[
"bannerfile"] =
"";
1463 metadataMap[
"fanartfile"] =
"";
1464 metadataMap[
"filename"] =
"";
1465 metadataMap[
"sortfilename"] =
"";
1466 metadataMap[
"title"] =
"";
1467 metadataMap[
"sorttitle"] =
"";
1468 metadataMap[
"subtitle"] =
"";
1469 metadataMap[
"sortsubtitle"] =
"";
1470 metadataMap[
"tagline"] =
"";
1471 metadataMap[
"director"] =
"";
1472 metadataMap[
"studio"] =
"";
1473 metadataMap[
"description"] =
"";
1474 metadataMap[
"description0"] =
"";
1475 metadataMap[
"genres"] =
"";
1476 metadataMap[
"countries"] =
"";
1477 metadataMap[
"cast"] =
"";
1478 metadataMap[
"rating"] =
"";
1479 metadataMap[
"length"] =
"";
1480 metadataMap[
"playcount"] =
"";
1481 metadataMap[
"year"] =
"";
1482 metadataMap[
"releasedate"] =
"";
1483 metadataMap[
"userrating"] =
"";
1484 metadataMap[
"season"] =
"";
1485 metadataMap[
"episode"] =
"";
1486 metadataMap[
"s##e##"] =
"";
1487 metadataMap[
"##x##"] =
"";
1488 metadataMap[
"trailerstate"] =
"";
1489 metadataMap[
"userratingstate"] =
"";
1490 metadataMap[
"watchedstate"] =
"";
1491 metadataMap[
"videolevel"] =
"";
1492 metadataMap[
"insertdate"] =
"";
1493 metadataMap[
"inetref"] =
"";
1494 metadataMap[
"homepage"] =
"";
1495 metadataMap[
"child_id"] =
"";
1496 metadataMap[
"browseable"] =
"";
1497 metadataMap[
"watched"] =
"";
1498 metadataMap[
"category"] =
"";
1499 metadataMap[
"processed"] =
"";
1504 if (data ==
nullptr)
1507 QString result = metadata->
GetText(name);
1508 if (!result.isEmpty())
1510 result = metadata->GetImage(name);
1511 if (!result.isEmpty())
1513 return metadata->GetState(name);
1518 if (data ==
nullptr)
1526 if (data ==
nullptr)
1965 if (
m_imp->getID() == 0)
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
void bindValueNoNull(const QString &placeholder, const QVariant &val)
Add a single binding, taking care not to set a NULL value.
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.
int add(int id, int value)
bool get(int id, entry &values)
void remove(int id, int value)
bool IsThisHost(const QString &addr)
is this address mapped to this host
bool IsMasterBackend(void)
is this the actual MBE process
static void DBError(const QString &where, const MSqlQuery &query)
static QString GetFileHash(const QString &url)
static bool DeleteFile(const QString &url)
static bool Exists(const QString &url, struct stat *fileinfo)
bool get(int id, QString &category)
int add(const QString &name)
QString FindFile(const QString &filename)
static QString generate_file_url(const QString &storage_group, const QString &host, const QString &path)
static VideoCastMap & getCastMap()
static VideoCast & GetCast()
static VideoCategory & GetCategory()
static VideoCountryMap & getCountryMap()
static VideoCountry & getCountry()
static VideoGenreMap & getGenreMap()
static VideoGenre & getGenre()
VideoMetadata(const QString &filename=QString(), const QString &sortFilename=QString(), const QString &hash=QString(), const QString &trailer=QString(), const QString &coverfile=QString(), const QString &screenshot=QString(), const QString &banner=QString(), const QString &fanart=QString(), const QString &title=QString(), const QString &sortTitle=QString(), const QString &subtitle=QString(), const QString &sortSubtitle=QString(), const QString &tagline=QString(), int year=VIDEO_YEAR_DEFAULT, QDate releasedate=QDate(), const QString &inetref=QString(), int collectionref=0, const QString &homepage=QString(), const QString &director=QString(), const QString &studio=QString(), const QString &plot=QString(), float userrating=0.0, const QString &rating=QString(), int length=0, int playcount=0, int season=0, int episode=0, QDate insertdate=QDate(), int id=0, ParentalLevel::Level showlevel=ParentalLevel::plLowest, int categoryID=0, int childID=-1, bool browse=true, bool watched=false, const QString &playcommand=QString(), const QString &category=QString(), const genre_list &genres=genre_list(), const country_list &countries=country_list(), const cast_list &cast=cast_list(), const QString &host="", bool processed=false, VideoContentType contenttype=kContentUnknown)
const QString VIDEO_INETREF_DEFAULT
const QString VIDEO_PLOT_DEFAULT
const QString VIDEO_TRAILER_DEFAULT
const QString VIDEO_BANNER_DEFAULT
const QString VIDEO_SCREENSHOT_DEFAULT
const QString VIDEO_FANART_DEFAULT
const QString VIDEO_RATING_DEFAULT
const QString VIDEO_DIRECTOR_UNKNOWN
const QString VIDEO_DIRECTOR_DEFAULT
const QString VIDEO_COVERFILE_DEFAULT
static const iso6937table * d
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
int ternary_compare(const QDateTime &a, const QDateTime &b)
balanced ternary (three way) comparison This is equivalent to C++20's operator <=>.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
QString FileHash(const QString &filename)
std::shared_ptr< MythSortHelper > getMythSortHelper(void)
Get a pointer to the MythSortHelper singleton.
QHash< QString, QString > InfoMap
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
@ kDateFull
Default local time.
@ kAddYear
Add year to string if not included.
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
MBASE_PUBLIC int naturalCompare(const QString &_a, const QString &_b, Qt::CaseSensitivity caseSensitivity=Qt::CaseSensitive)
This method chops the input a and b into pieces of digits and non-digits (a1.05 becomes a | 1 | .
QString intToPaddedString(int n, int width=2)
Creates a zero padded string representation of an integer.
def rating(profile, smoonURL, gate)
QString ParentalLevelToState(const ParentalLevel &level)
QString GetDisplayUserRating(float userrating)
QString WatchedToState(bool watched)
QString GetDisplayProcessed(bool processed)
QString TrailerToState(const QString &trailerFile)
QString GetDisplayRating(const QString &rating)
QString GetDisplayWatched(bool watched)
VideoContentType ContentTypeFromString(const QString &type)
QString ContentTypeToString(VideoContentType type)
bool IsDefaultCoverFile(const QString &coverfile)
QString GetDisplayCountries(const VideoMetadata &item)
QStringList GetDisplayCast(const VideoMetadata &item)
QString GetDisplayLength(std::chrono::minutes length)
QString GetDisplayYear(int year)
QString GetDisplayGenres(const VideoMetadata &item)
QString GetDisplayBrowse(bool browse)