* extra data for nfos

* fanart from FanartTV
* notify xmbc or plex in utorrent script
This commit is contained in:
Reinhard Pointner 2012-07-28 10:21:30 +00:00
parent ae6a2cb0ae
commit 5a6a5dcdd0
9 changed files with 208 additions and 160 deletions

View File

@ -35,6 +35,10 @@ class CLILogging extends Handler {
if (record.getLevel().intValue() <= getLevel().intValue())
return;
// make sure all previous messages are already flushed
System.out.flush();
System.err.flush();
// use either System.out or System.err depending on the severity of the error
PrintStream out = record.getLevel().intValue() < Level.WARNING.intValue() ? System.out : System.err;

View File

@ -325,4 +325,22 @@ def _defaults(args) {
/**
* Catch and log exceptions thrown by the closure
*/
this.metaClass._guarded = { c -> try { return c.call() } catch (e) { _log.severe("${e.class.simpleName}: ${e.message}"); return null }}
def _guarded(c) {
try {
return c.call()
} catch (e) {
_log.severe("${e.class.simpleName}: ${e.message}")
return null
}
}
/**
* Same as the above but without logging anything
*/
def tryQuietly(c) {
try {
return c.call()
} catch (e) {
return null
}
}

View File

@ -9,16 +9,22 @@ import java.io.File;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sourceforge.filebot.web.FanartTV.FanartDescriptor.FanartProperty;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import net.sourceforge.filebot.web.FanartTV.FanartDescriptor.FanartProperty;
public class FanartTV {
@ -30,52 +36,76 @@ public class FanartTV {
}
public List<FanartDescriptor> getArtwork(int tvdbid) throws Exception {
return getArtwork(tvdbid, "all", 1, 2);
public List<FanartDescriptor> getSeriesArtwork(int tvdbid) throws Exception {
return getSeriesArtwork(String.valueOf(tvdbid), "all", 1, 2);
}
public List<FanartDescriptor> getArtwork(int tvdbid, String type, int sort, int limit) throws Exception {
String xml = new CachedPage(getResource(tvdbid, "xml", type, sort, limit)).get();
Document dom = getDocument(xml);
public List<FanartDescriptor> getSeriesArtwork(String id, String type, int sort, int limit) throws Exception {
return getArtwork("series", id, type, sort, limit);
}
public List<FanartDescriptor> getMovieArtwork(int tmdbid) throws Exception {
return getMovieArtwork(String.valueOf(tmdbid), "all", 1, 2);
}
public List<FanartDescriptor> getMovieArtwork(String id, String type, int sort, int limit) throws Exception {
return getArtwork("movie", id, type, sort, limit);
}
public List<FanartDescriptor> getArtwork(String category, String id, String type, int sort, int limit) throws Exception {
String resource = getResource(category, id, "xml", type, sort, limit);
// cache results
CachedResource<FanartDescriptor[]> data = new CachedResource<FanartDescriptor[]>(resource, FanartDescriptor[].class, 7 * 24 * 60 * 60 * 1000) {
@Override
public FanartDescriptor[] process(ByteBuffer data) throws Exception {
Document dom = getDocument(Charset.forName("UTF-8").decode(data).toString());
List<FanartDescriptor> fanart = new ArrayList<FanartDescriptor>();
for (Node node : selectNodes("//*[@url]", dom)) {
// e.g. <seasonthumb id="3481" url="http://fanart.tv/fanart/tv/70327/seasonthumb/3481/Buffy (6).jpg" lang="en" likes="0" season="6"/>
Map<FanartProperty, String> data = new EnumMap<FanartProperty, String>(FanartProperty.class);
data.put(FanartProperty.type, node.getNodeName());
Map<FanartProperty, String> fields = new EnumMap<FanartProperty, String>(FanartProperty.class);
fields.put(FanartProperty.type, node.getNodeName());
for (FanartProperty prop : FanartProperty.values()) {
String value = getAttribute(prop.name(), node);
if (value != null) {
data.put(prop, value);
fields.put(prop, value);
}
}
fanart.add(new FanartDescriptor(data));
fanart.add(new FanartDescriptor(fields));
}
return fanart;
return fanart.toArray(new FanartDescriptor[0]);
}
public URL getResource(int tvdbid, String format, String type, int sort, int limit) throws MalformedURLException {
@Override
protected Cache getCache() {
return CacheManager.getInstance().getCache("web-data-diskcache");
}
};
return Arrays.asList(data.get());
}
public String getResource(String category, String id, String format, String type, int sort, int limit) throws MalformedURLException {
// e.g. http://fanart.tv/webservice/series/780b986b22c35e6f7a134a2f392c2deb/70327/xml/all/1/2
return new URL(String.format("http://fanart.tv/webservice/series/%s/%s/%s/%s/%s/%s", apikey, tvdbid, format, type, sort, limit));
return String.format("http://fanart.tv/webservice/%s/%s/%s/%s/%s/%s/%s", category, apikey, id, format, type, sort, limit);
}
public static class FanartDescriptor implements Serializable {
public static enum FanartProperty {
type,
id,
url,
lang,
likes,
season
type, id, url, lang, likes, season, disc_type
}
protected Map<FanartProperty, String> fields;
@ -136,6 +166,15 @@ public class FanartTV {
}
public Locale getLanguage() {
try {
return new Locale(fields.get(FanartProperty.lang));
} catch (Exception e) {
return null;
}
}
public Integer getSeason() {
try {
return new Integer(fields.get(FanartProperty.season));
@ -145,6 +184,11 @@ public class FanartTV {
}
public String getDiskType() {
return fields.get(FanartProperty.disc_type);
}
@Override
public String toString() {
return fields.toString();

View File

@ -434,11 +434,22 @@ public class TMDbClient implements MovieIdentificationService {
}
public List<Person> getCast() {
public List<Person> getPeope() {
return unmodifiableList(asList(people));
}
public List<Person> getCast() {
List<Person> actors = new ArrayList<Person>();
for (Person person : people) {
if (person.isActor()) {
actors.add(person);
}
}
return actors;
}
public String getDirector() {
for (Person person : people) {
if (person.isDirector())
@ -450,10 +461,8 @@ public class TMDbClient implements MovieIdentificationService {
public List<String> getActors() {
List<String> actors = new ArrayList<String>();
for (Person person : people) {
if (person.isActor()) {
actors.add(person.getName());
}
for (Person actor : getCast()) {
actors.add(actor.getName());
}
return actors;
}

View File

@ -4,8 +4,8 @@
* Fetch movie artwork. The movie is determined using the parent folders name.
*/
// xbmc artwork/nfo utility
include("fn:lib/xbmc")
// artwork/nfo helpers
include("fn:lib/htpc")
args.eachMediaFolder { dir ->

View File

@ -4,88 +4,8 @@
* Fetch series and season banners for all tv shows. Series name is auto-detected if possible or the folder name is used.
*/
def fetchBanner(outputFile, series, bannerType, bannerType2 = null, season = null) {
// select and fetch banner
def banner = [_args.locale.language, null].findResult { TheTVDB.getBanner(series, [BannerType:bannerType, BannerType2:bannerType2, Season:season, Language:it]) }
if (banner == null) {
println "Banner not found: $outputFile / $bannerType:$bannerType2"
return null
}
println "Fetching $outputFile => $banner"
return banner.url.saveAs(outputFile)
}
def fetchNfo(outputFile, series) {
def info = TheTVDB.getSeriesInfo(series, _args.locale)
println info
info.applyXmlTemplate('''<tvshow xmlns:gsp='http://groovy.codehaus.org/2005/gsp'>
<title>$name</title>
<year>$firstAired.year</year>
<top250></top250>
<seasons>-1</seasons>
<episode></episode>
<episodeguideurl></episodeguideurl>
<displayseason>-1</displayseason>
<displayepisode>-1</displayepisode>
<rating>$rating</rating>
<votes>$ratingCount</votes>
<outline></outline>
<plot>$overview</plot>
<tagline></tagline>
<runtime>$runtime</runtime>
<mpaa>$contentRating</mpaa>
<playcount></playcount>
<lastplayed></lastplayed>
<id>$id</id>
<episodeguide><url cache="${id}.xml">http://www.thetvdb.com/api/1D62F2F90030C444/series/${id}/all/''' + _args.locale.language + '''.zip</url></episodeguide>
<genre>${!genres.empty ? genres[0] : ''}</genre>
<set></set>
<credits></credits>
<director></director>
<thumb>$bannerUrl</thumb>
<premiered>$firstAired</premiered>
<status>$status</status>
<studio>$network</studio>
<trailer></trailer>
<gsp:scriptlet> actors.each { </gsp:scriptlet>
<actor>
<name>$it</name>
<role></role>
</actor>
<gsp:scriptlet> } </gsp:scriptlet>
<artist></artist>
</tvshow>
''')
.replaceAll(/\t|\r|\n/, '') // xbmc can't handle leading/trailing whitespace properly
.saveAs(outputFile)
}
def fetchSeriesBannersAndNfo(seriesDir, seasonDir, series, season) {
println "Fetch nfo and banners for $series / Season $season"
TheTVDB.getBannerList(series).each {
println "Available banner: $it.url => $it"
}
// fetch nfo
fetchNfo(seriesDir['tvshow.nfo'], series)
// fetch series banner, fanart, posters, etc
["680x1000", null].findResult{ fetchBanner(seriesDir['folder.jpg'], series, "poster", it) }
["graphical", null].findResult{ fetchBanner(seriesDir['banner.jpg'], series, "series", it) }
// fetch highest resolution fanart
["1920x1080", "1280x720", null].findResult{ fetchBanner(seriesDir["fanart.jpg"], series, "fanart", it) }
// fetch season banners
if (seasonDir != seriesDir) {
fetchBanner(seasonDir["folder.jpg"], series, "season", "season", season)
fetchBanner(seasonDir["banner.jpg"], series, "season", "seasonwide", season)
}
}
// artwork/nfo helpers
include("fn:lib/htpc")
args.eachMediaFolder { dir ->
@ -128,5 +48,5 @@ args.eachMediaFolder { dir ->
def season = sxe && sxe.season > 0 ? sxe.season : 1
println "$dir => $series"
fetchSeriesBannersAndNfo(seriesDir, dir, series, season)
fetchSeriesArtworkAndNfo(seriesDir, dir, series, season)
}

View File

@ -1,4 +1,10 @@
// xbmc functions
import static net.sourceforge.filebot.WebServices.*
/**
* XBMC helper functions
*/
def invokeScanVideoLibrary(host, port = 9090) {
try {
telnet(host, port) { writer, reader ->
@ -12,7 +18,35 @@ def invokeScanVideoLibrary(host, port = 9090) {
}
// functions for TheTVDB artwork/nfo
/**
* Plex helpers
*/
def refreshPlexLibrary(server, port = 32400, files = null) {
try {
def sections = new URL("http://$server:$port/plex").getXml()
def locations = sections.Directory.Location.collect{ [path:it.'@path', key:it.parent().'@key'] }
// limit refresh locations
if (files != null) {
locations = locations.findAll{ loc -> files.find{ it.path; it.path.startsWith(loc.path) }}
}
locations*.key.unique().each{ key ->
new URL("http://$server:$port/library/sections/$key/refresh/").get()
}
return true
} catch(e) {
println "${e.class.simpleName}: ${e.message}"
return false
}
}
/**
* TheTVDB artwork/nfo helpers
*/
def fetchSeriesBanner(outputFile, series, bannerType, bannerType2, season, locale) {
// select and fetch banner
def banner = [locale, null].findResult { TheTVDB.getBanner(series, [BannerType:bannerType, BannerType2:bannerType2, Season:season, Language:it]) }
@ -24,44 +58,38 @@ def fetchSeriesBanner(outputFile, series, bannerType, bannerType2, season, local
return banner.url.saveAs(outputFile)
}
def fetchSeriesFanart(outputFile, series, type, season, locale) {
def fanart = [locale, null].findResult{ lang -> FanartTV.getSeriesArtwork(series.seriesId).find{ type == it.type && (season == null || season == it.season) && (lang == null || lang == it.language) }}
if (fanart == null) {
println "Fanart not found: $outputFile / $type"
return null
}
println "Fetching $outputFile => $fanart"
return fanart.url.saveAs(outputFile)
}
def fetchSeriesNfo(outputFile, series, locale) {
def info = TheTVDB.getSeriesInfo(series, locale)
info.applyXmlTemplate('''<tvshow xmlns:gsp='http://groovy.codehaus.org/2005/gsp'>
<title>$name</title>
<year>$firstAired.year</year>
<top250></top250>
<seasons>-1</seasons>
<episode></episode>
<episodeguideurl></episodeguideurl>
<displayseason>-1</displayseason>
<displayepisode>-1</displayepisode>
<rating>$rating</rating>
<votes>$ratingCount</votes>
<outline></outline>
<plot>$overview</plot>
<tagline></tagline>
<runtime>$runtime</runtime>
<mpaa>$contentRating</mpaa>
<playcount></playcount>
<lastplayed></lastplayed>
<id>$id</id>
<episodeguide><url cache="${id}.xml">http://www.thetvdb.com/api/1D62F2F90030C444/series/${id}/all/''' + locale.language + '''.zip</url></episodeguide>
<genre>${!genres.empty ? genres[0] : ''}</genre>
<set></set>
<credits></credits>
<director></director>
<thumb>$bannerUrl</thumb>
<premiered>$firstAired</premiered>
<status>$status</status>
<studio>$network</studio>
<trailer></trailer>
<gsp:scriptlet> actors.each { </gsp:scriptlet>
<actor>
<name>$it</name>
<role></role>
</actor>
<gsp:scriptlet> } </gsp:scriptlet>
<artist></artist>
</tvshow>
''')
.replaceAll(/\t|\r|\n/, '') // xbmc can't handle leading/trailing whitespace properly
@ -85,6 +113,16 @@ def fetchSeriesArtworkAndNfo(seriesDir, seasonDir, series, season, locale = _arg
fetchSeriesBanner(seasonDir["folder.jpg"], series, "season", "season", season, locale)
fetchSeriesBanner(seasonDir["banner.jpg"], series, "season", "seasonwide", season, locale)
}
// fetch fanart
fetchSeriesFanart(seriesDir['clearlogo.png'], series, 'clearlogo', null, locale)
fetchSeriesFanart(seriesDir['clearart.png'], series, 'clearart', null, locale)
fetchSeriesFanart(seriesDir['thumb.jpg'], series, 'tvthumb', null, locale)
// fetch season fanart
if (seasonDir != seriesDir) {
fetchSeriesFanart(seasonDir['thumb.jpg'], series, 'seasonthumb', season, locale)
}
} catch(e) {
println "${e.class.simpleName}: ${e.message}"
}
@ -92,7 +130,9 @@ def fetchSeriesArtworkAndNfo(seriesDir, seasonDir, series, season, locale = _arg
// functions for TheMovieDB artwork/nfo
/**
* TheMovieDB artwork/nfo helpers
*/
def fetchMovieArtwork(outputFile, movieInfo, category, language) {
// select and fetch artwork
def artwork = TheMovieDB.getArtwork(movieInfo.id as String)
@ -105,17 +145,37 @@ def fetchMovieArtwork(outputFile, movieInfo, category, language) {
return selection.url.saveAs(outputFile)
}
def fetchMovieFanart(outputFile, movieInfo, type, diskType, locale) {
def fanart = [locale, null].findResult{ lang -> FanartTV.getMovieArtwork(movieInfo.id).find{ type == it.type && (diskType == null || diskType == it.diskType) && (lang == null || lang == it.language) }}
if (fanart == null) {
println "Fanart not found: $outputFile / $type"
return null
}
println "Fetching $outputFile => $fanart"
return fanart.url.saveAs(outputFile)
}
def fetchMovieNfo(outputFile, movieInfo) {
movieInfo.applyXmlTemplate('''<movie>
movieInfo.applyXmlTemplate('''<movie xmlns:gsp='http://groovy.codehaus.org/2005/gsp'>
<title>$name</title>
<originaltitle>$originalName</originaltitle>
<set>$collection</set>
<year>$released.year</year>
<rating>$rating</rating>
<votes>$votes</votes>
<plot>$overview</plot>
<runtime>$runtime</runtime>
<mpaa>$certification</mpaa>
<genre>${!genres.empty ? genres[0] : ''}</genre>
<id>tt${imdbId.pad(7)}</id>
<plot>$overview</plot>
<tagline>$tagline</tagline>
<runtime>$runtime</runtime>
<genre>${!genres.empty ? genres[0] : ''}</genre>
<director>$director</director>
<gsp:scriptlet> cast.each { </gsp:scriptlet>
<actor>
<name>${it?.name}</name>
<role>${it?.character}</role>
</actor>
<gsp:scriptlet> } </gsp:scriptlet>
</movie>
''')
.replaceAll(/\t|\r|\n/, '') // xbmc can't handle leading/trailing whitespace properly
@ -132,6 +192,10 @@ def fetchMovieArtworkAndNfo(movieDir, movie, locale = _args.locale) {
// fetch series banner, fanart, posters, etc
fetchMovieArtwork(movieDir['folder.jpg'], movieInfo, 'posters', locale.language)
fetchMovieArtwork(movieDir['backdrop.jpg'], movieInfo, 'backdrops', locale.language)
fetchMovieFanart(movieDir['logo.png'], movieInfo, 'movielogo', null, locale)
fetchMovieFanart(movieDir['fanart.png'], movieInfo, 'movieart', null, locale)
['bluray', 'dvd', null].findResult { diskType -> fetchMovieFanart(movieDir['disc.png'], movieInfo, 'moviedisc', diskType, locale) }
} catch(e) {
println "${e.class.simpleName}: ${e.message}"
}

View File

@ -1,14 +0,0 @@
// plex functions
def refreshPlexLibrary(server, port = 32400, files = null) {
def sections = new URL("http://$server:$port/plex").getXml()
def locations = sections.Directory.Location.collect{ [path:it.'@path', key:it.parent().'@key'] }
// limit refresh locations
if (files != null) {
locations = locations.findAll{ loc -> files.find{ it.path; it.path.startsWith(loc.path) }}
}
locations*.key.unique().each{ key ->
new URL("http://$server:$port/library/sections/$key/refresh/").get()
}
}

View File

@ -29,8 +29,8 @@ input = input.findAll{ !(it.path =~ /\b(?i:sample|trailer|extras|deleted.scenes|
// print input fileset
input.each{ println "Input: $it" }
// xbmc artwork/nfo utility
include("fn:lib/xbmc")
// artwork/nfo utility
include("fn:lib/htpc")
// group episodes/movies and rename according to XBMC standards
def groups = input.groupBy{ f ->
@ -100,8 +100,11 @@ groups.each{ group, files ->
// make XBMC scan for new content
// make xbmc or plex scan for new content
xbmc.split(/[\s,|]+/).each{
println "Notify XBMC: $it"
invokeScanVideoLibrary(it)
println "Notify Plex: $it"
refreshPlexLibrary(it)
}