From 5a6a5dcdd0d5f3cd66d96420f05b11d86aad19d8 Mon Sep 17 00:00:00 2001 From: Reinhard Pointner Date: Sat, 28 Jul 2012 10:21:30 +0000 Subject: [PATCH] * extra data for nfos * fanart from FanartTV * notify xmbc or plex in utorrent script --- .../sourceforge/filebot/cli/CLILogging.java | 4 + .../filebot/cli/ScriptShell.lib.groovy | 20 +++- .../net/sourceforge/filebot/web/FanartTV.java | 102 +++++++++++----- .../sourceforge/filebot/web/TMDbClient.java | 19 ++- website/scripts/artwork.tmdb.groovy | 4 +- website/scripts/artwork.tvdb.groovy | 86 +------------- .../scripts/lib/{xbmc.groovy => htpc.groovy} | 110 ++++++++++++++---- website/scripts/lib/plex.groovy | 14 --- website/scripts/utorrent-postprocess.groovy | 9 +- 9 files changed, 208 insertions(+), 160 deletions(-) rename website/scripts/lib/{xbmc.groovy => htpc.groovy} (59%) delete mode 100644 website/scripts/lib/plex.groovy diff --git a/source/net/sourceforge/filebot/cli/CLILogging.java b/source/net/sourceforge/filebot/cli/CLILogging.java index f5e360a7..959d6069 100644 --- a/source/net/sourceforge/filebot/cli/CLILogging.java +++ b/source/net/sourceforge/filebot/cli/CLILogging.java @@ -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; diff --git a/source/net/sourceforge/filebot/cli/ScriptShell.lib.groovy b/source/net/sourceforge/filebot/cli/ScriptShell.lib.groovy index af9bcad9..bd272226 100644 --- a/source/net/sourceforge/filebot/cli/ScriptShell.lib.groovy +++ b/source/net/sourceforge/filebot/cli/ScriptShell.lib.groovy @@ -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 + } +} diff --git a/source/net/sourceforge/filebot/web/FanartTV.java b/source/net/sourceforge/filebot/web/FanartTV.java index 55234b1b..44b89691 100644 --- a/source/net/sourceforge/filebot/web/FanartTV.java +++ b/source/net/sourceforge/filebot/web/FanartTV.java @@ -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 getArtwork(int tvdbid) throws Exception { - return getArtwork(tvdbid, "all", 1, 2); + public List getSeriesArtwork(int tvdbid) throws Exception { + return getSeriesArtwork(String.valueOf(tvdbid), "all", 1, 2); } - public List 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 getSeriesArtwork(String id, String type, int sort, int limit) throws Exception { + return getArtwork("series", id, type, sort, limit); + } + + + public List getMovieArtwork(int tmdbid) throws Exception { + return getMovieArtwork(String.valueOf(tmdbid), "all", 1, 2); + } + + + public List getMovieArtwork(String id, String type, int sort, int limit) throws Exception { + return getArtwork("movie", id, type, sort, limit); + } + + + public List getArtwork(String category, String id, String type, int sort, int limit) throws Exception { + String resource = getResource(category, id, "xml", type, sort, limit); - List fanart = new ArrayList(); - - for (Node node : selectNodes("//*[@url]", dom)) { - // e.g. - Map data = new EnumMap(FanartProperty.class); - data.put(FanartProperty.type, node.getNodeName()); - for (FanartProperty prop : FanartProperty.values()) { - String value = getAttribute(prop.name(), node); - if (value != null) { - data.put(prop, value); + // cache results + CachedResource data = new CachedResource(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 fanart = new ArrayList(); + for (Node node : selectNodes("//*[@url]", dom)) { + // e.g. + Map fields = new EnumMap(FanartProperty.class); + fields.put(FanartProperty.type, node.getNodeName()); + for (FanartProperty prop : FanartProperty.values()) { + String value = getAttribute(prop.name(), node); + if (value != null) { + fields.put(prop, value); + } + } + fanart.add(new FanartDescriptor(fields)); } + + return fanart.toArray(new FanartDescriptor[0]); } - fanart.add(new FanartDescriptor(data)); - } + + + @Override + protected Cache getCache() { + return CacheManager.getInstance().getCache("web-data-diskcache"); + } + }; - return fanart; + return Arrays.asList(data.get()); } - public URL getResource(int tvdbid, String format, String type, int sort, int limit) throws MalformedURLException { + 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 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(); diff --git a/source/net/sourceforge/filebot/web/TMDbClient.java b/source/net/sourceforge/filebot/web/TMDbClient.java index d774dd46..46070aff 100644 --- a/source/net/sourceforge/filebot/web/TMDbClient.java +++ b/source/net/sourceforge/filebot/web/TMDbClient.java @@ -434,11 +434,22 @@ public class TMDbClient implements MovieIdentificationService { } - public List getCast() { + public List getPeope() { return unmodifiableList(asList(people)); } + public List getCast() { + List actors = new ArrayList(); + 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 getActors() { List actors = new ArrayList(); - for (Person person : people) { - if (person.isActor()) { - actors.add(person.getName()); - } + for (Person actor : getCast()) { + actors.add(actor.getName()); } return actors; } diff --git a/website/scripts/artwork.tmdb.groovy b/website/scripts/artwork.tmdb.groovy index 3e52b506..38a14c25 100644 --- a/website/scripts/artwork.tmdb.groovy +++ b/website/scripts/artwork.tmdb.groovy @@ -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 -> diff --git a/website/scripts/artwork.tvdb.groovy b/website/scripts/artwork.tvdb.groovy index 95627767..5de8ffe7 100644 --- a/website/scripts/artwork.tvdb.groovy +++ b/website/scripts/artwork.tvdb.groovy @@ -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(''' - $name - $firstAired.year - - -1 - - - -1 - -1 - $rating - $ratingCount - - $overview - - $runtime - $contentRating - - - $id - http://www.thetvdb.com/api/1D62F2F90030C444/series/${id}/all/''' + _args.locale.language + '''.zip - ${!genres.empty ? genres[0] : ''} - - - - $bannerUrl - $firstAired - $status - $network - - actors.each { - - $it - - - } - - - ''') - .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) } diff --git a/website/scripts/lib/xbmc.groovy b/website/scripts/lib/htpc.groovy similarity index 59% rename from website/scripts/lib/xbmc.groovy rename to website/scripts/lib/htpc.groovy index d7865717..3e17b287 100644 --- a/website/scripts/lib/xbmc.groovy +++ b/website/scripts/lib/htpc.groovy @@ -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(''' $name $firstAired.year - - -1 - - - -1 - -1 $rating $ratingCount - $overview - $runtime $contentRating - - $id http://www.thetvdb.com/api/1D62F2F90030C444/series/${id}/all/''' + locale.language + '''.zip ${!genres.empty ? genres[0] : ''} - - - $bannerUrl $firstAired $status $network - actors.each { $it - } - ''') .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(''' + movieInfo.applyXmlTemplate(''' $name + $originalName + $collection $released.year $rating $votes - $overview - $runtime $certification - ${!genres.empty ? genres[0] : ''} tt${imdbId.pad(7)} + $overview + $tagline + $runtime + ${!genres.empty ? genres[0] : ''} + $director + cast.each { + + ${it?.name} + ${it?.character} + + } ''') .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}" } diff --git a/website/scripts/lib/plex.groovy b/website/scripts/lib/plex.groovy deleted file mode 100644 index 7ceac9b7..00000000 --- a/website/scripts/lib/plex.groovy +++ /dev/null @@ -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() - } -} diff --git a/website/scripts/utorrent-postprocess.groovy b/website/scripts/utorrent-postprocess.groovy index 0d934196..b6882c0e 100644 --- a/website/scripts/utorrent-postprocess.groovy +++ b/website/scripts/utorrent-postprocess.groovy @@ -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) }