Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Metadata: Parse XBMC-style NFO files.
XBMC has a XML NFO format that goes side-by-side files in media directories.  These XML files contain most of the basic metadata.  This commit adds basic support for parsing the metadata out of them.

I didn't implement everything in the format, yet, and I haven't turned it on for Television stuff (since the format is somewhat ugly in this regard-- there's one file for the series, and one for the episode.  I want to think a bit more about it.).  The format also doesn't implement all of the metadata *we* keep and pull in, but it does have the basics that people care about-- title, subtitle, year, season, episode, plot, inetref, etc.

I'll flesh out the parser a little more to cover cast, director, and other people, and maybe watched/unwatched status too.  Once I'm done with that I'll circle back and get it working for TV shows as well.  Most of the needed stuff is already in the parser, just need to combine the two sets of metadata in a way that makes sense.

Format documentation: http://wiki.xbmc.org/?title=Import_-_Export_Library
Hilarious logging censored by sphery.
  • Loading branch information
Robert McNamara committed Jun 30, 2011
1 parent 7f37123 commit 581175a
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 28 deletions.
2 changes: 1 addition & 1 deletion mythtv/libs/libmythbase/mythversion.h
Expand Up @@ -12,7 +12,7 @@
/// Update this whenever the plug-in API changes. /// Update this whenever the plug-in API changes.
/// Including changes in the libmythbase, libmyth, libmythtv, libmythav* and /// Including changes in the libmythbase, libmyth, libmythtv, libmythav* and
/// libmythui class methods used by plug-ins. /// libmythui class methods used by plug-ins.
#define MYTH_BINARY_VERSION "0.25.20110628-2" #define MYTH_BINARY_VERSION "0.25.20110629-1"


/** \brief Increment this whenever the MythTV network protocol changes. /** \brief Increment this whenever the MythTV network protocol changes.
* *
Expand Down
70 changes: 67 additions & 3 deletions mythtv/libs/libmythmetadata/metadatacommon.cpp
Expand Up @@ -15,6 +15,7 @@ MetadataLookup::MetadataLookup(void) :
m_allowoverwrites(false), m_allowoverwrites(false),
m_dvdorder(false), m_dvdorder(false),
m_host(), m_host(),
m_filename(),
m_title(), m_title(),
m_categories(), m_categories(),
m_userrating(0), m_userrating(0),
Expand Down Expand Up @@ -58,6 +59,7 @@ MetadataLookup::MetadataLookup(
bool allowoverwrites, bool allowoverwrites,
bool preferdvdorder, bool preferdvdorder,
QString host, QString host,
QString filename,
QString title, QString title,
const QStringList categories, const QStringList categories,
const float userrating, const float userrating,
Expand Down Expand Up @@ -98,6 +100,7 @@ MetadataLookup::MetadataLookup(
m_allowoverwrites(allowoverwrites), m_allowoverwrites(allowoverwrites),
m_dvdorder(preferdvdorder), m_dvdorder(preferdvdorder),
m_host(host), m_host(host),
m_filename(filename),
m_title(title), m_title(title),
m_categories(categories), m_categories(categories),
m_userrating(userrating), m_userrating(userrating),
Expand Down Expand Up @@ -152,6 +155,7 @@ ArtworkList MetadataLookup::GetArtwork(VideoArtworkType type) const


void MetadataLookup::toMap(MetadataMap &metadataMap) void MetadataLookup::toMap(MetadataMap &metadataMap)
{ {
metadataMap["filename"] = m_filename;
metadataMap["title"] = m_title; metadataMap["title"] = m_title;
metadataMap["category"] = m_categories.join(", "); metadataMap["category"] = m_categories.join(", ");
metadataMap["userrating"] = QString::number(m_userrating); metadataMap["userrating"] = QString::number(m_userrating);
Expand Down Expand Up @@ -362,14 +366,74 @@ MetadataLookup* ParseMetadataItem(const QDomElement& item,
return new MetadataLookup(lookup->GetType(), lookup->GetData(), return new MetadataLookup(lookup->GetType(), lookup->GetData(),
lookup->GetStep(), lookup->GetAutomatic(), lookup->GetHandleImages(), lookup->GetStep(), lookup->GetAutomatic(), lookup->GetHandleImages(),
lookup->GetAllowOverwrites(), lookup->GetPreferDVDOrdering(), lookup->GetAllowOverwrites(), lookup->GetPreferDVDOrdering(),
lookup->GetHost(), title, categories, userrating, language, subtitle, lookup->GetHost(), lookup->GetFilename(), title, categories,
tagline, description, season, episode, certification, countries, userrating, language, subtitle, tagline, description,
popularity, budget, revenue, album, tracknum, system, year, season, episode, certification, countries, popularity,
budget, revenue, album, tracknum, system, year,
releasedate, lastupdated, runtime, runtimesecs, inetref, releasedate, lastupdated, runtime, runtimesecs, inetref,
tmsref, imdb, people, studios, homepage, trailerURL, artwork, tmsref, imdb, people, studios, homepage, trailerURL, artwork,
DownloadMap()); DownloadMap());
} }


MetadataLookup* ParseMetadataMovieNFO(const QDomElement& item,
MetadataLookup *lookup)
{
if (!lookup)
return new MetadataLookup();

uint year = 0, runtime = 0, runtimesecs = 0,
season = 0, episode = 0;
QString title, subtitle, tagline, description,
inetref, trailer, certification;
float userrating = 0;
QDate releasedate;
QStringList categories;
PeopleMap people;
ArtworkMap artwork;

// Get the easy parses
QString titletmp;
if (item.tagName() == "movie")
title = Parse::UnescapeHTML(item.firstChildElement("title").text());
else if (item.tagName() == "episodedetails")
subtitle = Parse::UnescapeHTML(item.firstChildElement("title").text());
userrating = item.firstChildElement("rating").text().toFloat();
year = item.firstChildElement("year").text().toUInt();
season = item.firstChildElement("season").text().toUInt();
episode = item.firstChildElement("episode").text().toUInt();
description = Parse::UnescapeHTML(item.firstChildElement("plot").text());
tagline = Parse::UnescapeHTML(item.firstChildElement("tagline").text());
inetref = item.firstChildElement("id").text();
trailer = item.firstChildElement("trailer").text();
certification = item.firstChildElement("mpaa").text();
categories.append(item.firstChildElement("genre").text());

QString tmpDate = item.firstChildElement("releasedate").text();
if (!tmpDate.isEmpty())
releasedate = QDate::fromString(tmpDate, "yyyy-MM-dd");
else if (year > 0)
releasedate = QDate::fromString(QString::number(year), "yyyy");

runtime = item.firstChildElement("runtime").text()
.remove(QRegExp("[A-Za-z]"))
.trimmed().toUInt();
runtimesecs = runtime * 60;

return new MetadataLookup(lookup->GetType(), lookup->GetData(),
lookup->GetStep(), lookup->GetAutomatic(), lookup->GetHandleImages(),
lookup->GetAllowOverwrites(), lookup->GetPreferDVDOrdering(),
lookup->GetHost(), lookup->GetFilename(), title, categories,
userrating, QString() /*language*/, subtitle,
tagline, description, season, episode,
certification, QStringList() /*countries*/,
0 /*popularity*/, 0 /*budget*/, 0 /*revenue*/, QString() /*album*/,
0 /*tracknum*/, QString() /*system*/, year,
releasedate, QDateTime() /*lastupdated*/, runtime, runtimesecs,
inetref, QString() /*tmsref*/, QString() /*imdb*/, people,
QStringList() /*studios*/, QString() /*homepage*/, trailer, artwork,
DownloadMap());
}

PeopleMap ParsePeople(QDomElement people) PeopleMap ParsePeople(QDomElement people)
{ {
PeopleMap ret; PeopleMap ret;
Expand Down
6 changes: 6 additions & 0 deletions mythtv/libs/libmythmetadata/metadatacommon.h
Expand Up @@ -94,6 +94,7 @@ class META_PUBLIC MetadataLookup : public QObject
bool allowoverwrites, bool allowoverwrites,
bool preferdvdorder, bool preferdvdorder,
QString host, QString host,
QString filename,
QString title, QString title,
const QStringList categories, const QStringList categories,
const float userrating, const float userrating,
Expand Down Expand Up @@ -147,6 +148,7 @@ class META_PUBLIC MetadataLookup : public QObject


// General Sets // General Sets
void SetTitle(QString title) { m_title = title; }; void SetTitle(QString title) { m_title = title; };
void SetFilename(QString filename) { m_filename = filename; };


// General Sets - Video // General Sets - Video
void SetSubtitle(QString subtitle) { m_subtitle = subtitle; }; void SetSubtitle(QString subtitle) { m_subtitle = subtitle; };
Expand Down Expand Up @@ -176,6 +178,7 @@ class META_PUBLIC MetadataLookup : public QObject
bool GetAllowOverwrites() const { return m_allowoverwrites; }; bool GetAllowOverwrites() const { return m_allowoverwrites; };


// General // General
QString GetFilename() const { return m_filename; };
QString GetTitle() const { return m_title; }; QString GetTitle() const { return m_title; };
QStringList GetCategories() const { return m_categories; }; QStringList GetCategories() const { return m_categories; };
float GetUserRating() const { return m_userrating; }; float GetUserRating() const { return m_userrating; };
Expand Down Expand Up @@ -237,6 +240,7 @@ class META_PUBLIC MetadataLookup : public QObject
bool m_dvdorder; bool m_dvdorder;
QString m_host; QString m_host;


QString m_filename;
QString m_title; QString m_title;
const QStringList m_categories; const QStringList m_categories;
float m_userrating; float m_userrating;
Expand Down Expand Up @@ -290,6 +294,8 @@ Q_DECLARE_METATYPE(MetadataLookup*)
META_PUBLIC MetadataLookup* ParseMetadataItem(const QDomElement& item, META_PUBLIC MetadataLookup* ParseMetadataItem(const QDomElement& item,
MetadataLookup *lookup, MetadataLookup *lookup,
bool passseas = true); bool passseas = true);
META_PUBLIC MetadataLookup* ParseMetadataMovieNFO(const QDomElement& item,
MetadataLookup *lookup);
META_PUBLIC PeopleMap ParsePeople(QDomElement people); META_PUBLIC PeopleMap ParsePeople(QDomElement people);
META_PUBLIC ArtworkMap ParseArtwork(QDomElement artwork); META_PUBLIC ArtworkMap ParseArtwork(QDomElement artwork);


Expand Down
141 changes: 117 additions & 24 deletions mythtv/libs/libmythmetadata/metadatadownload.cpp
Expand Up @@ -2,6 +2,7 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QEvent> #include <QEvent>
#include <QDir> #include <QDir>
#include <QUrl>


// myth // myth
#include "mythcorecontext.h" #include "mythcorecontext.h"
Expand All @@ -10,6 +11,7 @@
#include "mythsystem.h" #include "mythsystem.h"
#include "metadatadownload.h" #include "metadatadownload.h"
#include "util.h" #include "util.h"
#include "remotefile.h"
#include "mythlogging.h" #include "mythlogging.h"


QEvent::Type MetadataLookupEvent::kEventType = QEvent::Type MetadataLookupEvent::kEventType =
Expand Down Expand Up @@ -204,6 +206,67 @@ MetadataLookupList MetadataDownload::runGrabber(QString cmd, QStringList args,
return list; return list;
} }


MetadataLookupList MetadataDownload::readNFO(QString NFOpath,
MetadataLookup* lookup)
{
MetadataLookupList list;

VERBOSE(VB_GENERAL, QString("Matching NFO file found. "
"Parsing %1 for metadata...")
.arg(NFOpath));

if (lookup->GetType() == VID)
{
QByteArray nforaw;
QDomElement item;
if (NFOpath.startsWith("myth://"))
{
RemoteFile *rf = new RemoteFile(NFOpath);
if (rf && rf->Open())
{
bool loaded = rf->SaveAs(nforaw);
if (loaded)
{
QDomDocument doc;
if (doc.setContent(nforaw, true))
{
lookup->SetStep(GETDATA);
item = doc.documentElement();
}
else
VERBOSE(VB_GENERAL, QString("PIRATE ERROR: Invalid NFO file found."));
}
rf->Close();
}

delete rf;
rf = NULL;
}
else
{
QFile file(NFOpath);
if (file.open(QIODevice::ReadOnly))
{
nforaw = file.readAll();
QDomDocument doc;
if (doc.setContent(nforaw, true))
{
lookup->SetStep(GETDATA);
item = doc.documentElement();
}
else
VERBOSE(VB_GENERAL, QString("PIRATE ERROR: Invalid NFO file found."));
file.close();
}
}

MetadataLookup *tmp = ParseMetadataMovieNFO(item, lookup);
list.append(tmp);
}

return list;
}

MetadataLookupList MetadataDownload::handleGame(MetadataLookup* lookup) MetadataLookupList MetadataDownload::handleGame(MetadataLookup* lookup)
{ {
MetadataLookupList list; MetadataLookupList list;
Expand Down Expand Up @@ -245,35 +308,42 @@ MetadataLookupList MetadataDownload::handleMovie(MetadataLookup* lookup)
{ {
MetadataLookupList list; MetadataLookupList list;


QString def_cmd = QDir::cleanPath(QString("%1/%2") QString nfo = getNFOPath(lookup->GetFilename());
.arg(GetShareDir())
.arg("metadata/Movie/tmdb.py"));


QString cmd = gCoreContext->GetSetting("MovieGrabber", def_cmd); if (nfo.isEmpty())
{
QString def_cmd = QDir::cleanPath(QString("%1/%2")
.arg(GetShareDir())
.arg("metadata/Movie/tmdb.py"));


QStringList args; QString cmd = gCoreContext->GetSetting("MovieGrabber", def_cmd);
args.append(QString("-l")); // Language Flag
args.append(gCoreContext->GetLanguage()); // UI Language


// If the inetref is populated, even in search mode, QStringList args;
// become a getdata grab and use that. args.append(QString("-l")); // Language Flag
if (lookup->GetStep() == SEARCH && args.append(gCoreContext->GetLanguage()); // UI Language
(!lookup->GetInetref().isEmpty() &&
lookup->GetInetref() != "00000000"))
lookup->SetStep(GETDATA);


if (lookup->GetStep() == SEARCH) // If the inetref is populated, even in search mode,
{ // become a getdata grab and use that.
args.append(QString("-M")); if (lookup->GetStep() == SEARCH &&
QString title = lookup->GetTitle(); (!lookup->GetInetref().isEmpty() &&
args.append(ShellEscape(title)); lookup->GetInetref() != "00000000"))
} lookup->SetStep(GETDATA);
else if (lookup->GetStep() == GETDATA)
{ if (lookup->GetStep() == SEARCH)
args.append(QString("-D")); {
args.append(lookup->GetInetref()); args.append(QString("-M"));
QString title = lookup->GetTitle();
args.append(ShellEscape(title));
}
else if (lookup->GetStep() == GETDATA)
{
args.append(QString("-D"));
args.append(lookup->GetInetref());
}
list = runGrabber(cmd, args, lookup);
} }
list = runGrabber(cmd, args, lookup); else
list = readNFO(nfo, lookup);


return list; return list;
} }
Expand Down Expand Up @@ -351,3 +421,26 @@ MetadataLookupList MetadataDownload::handleVideoUndetermined(
return list; return list;
} }


QString MetadataDownload::getNFOPath(QString filename)
{
QString ret;
QString nfoname;
QUrl qurl(filename);
QString ext = QFileInfo(qurl.path()).suffix();
nfoname = filename.left(filename.size() - ext.size()) + "nfo";

VERBOSE(VB_GENERAL, QString("NFOName = %1 ").arg(nfoname));

if (nfoname.startsWith("myth://"))
{
if (RemoteFile::Exists(nfoname))
ret = nfoname;
}
else
{
if (QFile::exists(nfoname))
ret = nfoname;
}

return ret;
}
3 changes: 3 additions & 0 deletions mythtv/libs/libmythmetadata/metadatadownload.h
Expand Up @@ -48,6 +48,8 @@ class META_PUBLIC MetadataDownload : public QThread


void run(); void run();


QString getNFOPath(QString filename);

private: private:
// Video handling // Video handling
MetadataLookupList handleMovie(MetadataLookup* lookup); MetadataLookupList handleMovie(MetadataLookup* lookup);
Expand All @@ -61,6 +63,7 @@ class META_PUBLIC MetadataDownload : public QThread
MetadataLookupList runGrabber(QString cmd, QStringList args, MetadataLookupList runGrabber(QString cmd, QStringList args,
MetadataLookup* lookup, MetadataLookup* lookup,
bool passseas = true); bool passseas = true);
MetadataLookupList readNFO(QString NFOpath, MetadataLookup* lookup);
MetadataLookup* moreWork(); MetadataLookup* moreWork();


QObject *m_parent; QObject *m_parent;
Expand Down
7 changes: 7 additions & 0 deletions mythtv/programs/mythfrontend/videodlg.cpp
Expand Up @@ -3578,6 +3578,13 @@ void VideoDialog::VideoSearch(MythGenericTree *node,
lookup->SetSeason(metadata->GetSeason()); lookup->SetSeason(metadata->GetSeason());
lookup->SetEpisode(metadata->GetEpisode()); lookup->SetEpisode(metadata->GetEpisode());
lookup->SetInetref(metadata->GetInetRef()); lookup->SetInetref(metadata->GetInetRef());
QString fntmp;
if (metadata->GetHost().isEmpty())
fntmp = metadata->GetFilename();
else
fntmp = generate_file_url("Videos", metadata->GetHost(),
metadata->GetFilename());
lookup->SetFilename(fntmp);
if (m_query->isRunning()) if (m_query->isRunning())
m_query->prependLookup(lookup); m_query->prependLookup(lookup);
else else
Expand Down

0 comments on commit 581175a

Please sign in to comment.