diff --git a/source/net/sourceforge/filebot/cli/ScriptShell.lib.groovy b/source/net/sourceforge/filebot/cli/ScriptShell.lib.groovy index 04526fab..1d3d2e62 100644 --- a/source/net/sourceforge/filebot/cli/ScriptShell.lib.groovy +++ b/source/net/sourceforge/filebot/cli/ScriptShell.lib.groovy @@ -328,7 +328,7 @@ def _defaults(args) { def _guarded(c) { try { return c.call() - } catch (e) { + } catch (Throwable e) { _log.severe("${e.class.simpleName}: ${e.message}") return null } @@ -340,7 +340,7 @@ def _guarded(c) { def tryQuietly(c) { try { return c.call() - } catch (e) { + } catch (Throwable e) { return null } } diff --git a/source/net/sourceforge/filebot/mediainfo/MediaInfo.java b/source/net/sourceforge/filebot/mediainfo/MediaInfo.java index e9ab25f0..fbba9e90 100644 --- a/source/net/sourceforge/filebot/mediainfo/MediaInfo.java +++ b/source/net/sourceforge/filebot/mediainfo/MediaInfo.java @@ -4,6 +4,7 @@ package net.sourceforge.filebot.mediainfo; import java.io.Closeable; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.EnumMap; import java.util.LinkedHashMap; @@ -34,7 +35,7 @@ public class MediaInfo implements Closeable { private Pointer handle; - + public MediaInfo() { try { handle = MediaInfoLibrary.INSTANCE.New(); @@ -43,62 +44,62 @@ public class MediaInfo implements Closeable { } } - + public synchronized boolean open(File file) { return file.isFile() && MediaInfoLibrary.INSTANCE.Open(handle, new WString(file.getAbsolutePath())) > 0; } - + public synchronized String inform() { return MediaInfoLibrary.INSTANCE.Inform(handle).toString(); } - + public String option(String option) { return option(option, ""); } - + public synchronized String option(String option, String value) { return MediaInfoLibrary.INSTANCE.Option(handle, new WString(option), new WString(value)).toString(); } - + public String get(StreamKind streamKind, int streamNumber, String parameter) { return get(streamKind, streamNumber, parameter, InfoKind.Text, InfoKind.Name); } - + public String get(StreamKind streamKind, int streamNumber, String parameter, InfoKind infoKind) { return get(streamKind, streamNumber, parameter, infoKind, InfoKind.Name); } - + public synchronized String get(StreamKind streamKind, int streamNumber, String parameter, InfoKind infoKind, InfoKind searchKind) { return MediaInfoLibrary.INSTANCE.Get(handle, streamKind.ordinal(), streamNumber, new WString(parameter), infoKind.ordinal(), searchKind.ordinal()).toString(); } - + public String get(StreamKind streamKind, int streamNumber, int parameterIndex) { return get(streamKind, streamNumber, parameterIndex, InfoKind.Text); } - + public synchronized String get(StreamKind streamKind, int streamNumber, int parameterIndex, InfoKind infoKind) { return MediaInfoLibrary.INSTANCE.GetI(handle, streamKind.ordinal(), streamNumber, parameterIndex, infoKind.ordinal()).toString(); } - + public synchronized int streamCount(StreamKind streamKind) { return MediaInfoLibrary.INSTANCE.Count_Get(handle, streamKind.ordinal(), -1); } - + public synchronized int parameterCount(StreamKind streamKind, int streamNumber) { return MediaInfoLibrary.INSTANCE.Count_Get(handle, streamKind.ordinal(), streamNumber); } - + public Map>> snapshot() { Map>> mediaInfo = new EnumMap>>(StreamKind.class); @@ -119,7 +120,7 @@ public class MediaInfo implements Closeable { return mediaInfo; } - + public Map snapshot(StreamKind streamKind, int streamNumber) { Map streamInfo = new LinkedHashMap(); @@ -134,13 +135,13 @@ public class MediaInfo implements Closeable { return streamInfo; } - + @Override public synchronized void close() { MediaInfoLibrary.INSTANCE.Close(handle); } - + public synchronized void dispose() { if (handle == null) return; @@ -150,24 +151,18 @@ public class MediaInfo implements Closeable { handle = null; } - + @Override protected void finalize() { dispose(); } - + public enum StreamKind { - General, - Video, - Audio, - Text, - Chapters, - Image, - Menu; + General, Video, Audio, Text, Chapters, Image, Menu; } - + public enum InfoKind { /** * Unique name of parameter. @@ -202,8 +197,7 @@ public class MediaInfo implements Closeable { Info, /** - * How this parameter is supported, could be N (No), B (Beta), R (Read only), W - * (Read/Write). + * How this parameter is supported, could be N (No), B (Beta), R (Read only), W (Read/Write). */ HowTo, @@ -213,32 +207,32 @@ public class MediaInfo implements Closeable { Domain; } - + public static String version() { return staticOption("Info_Version"); } - + public static String parameters() { return staticOption("Info_Parameters"); } - + public static String codecs() { return staticOption("Info_Codecs"); } - + public static String capacities() { return staticOption("Info_Capacities"); } - + public static String staticOption(String option) { return staticOption(option, ""); } - + public static String staticOption(String option, String value) { try { return MediaInfoLibrary.INSTANCE.Option(null, new WString(option), new WString(value)).toString(); @@ -247,4 +241,20 @@ public class MediaInfo implements Closeable { } } + + /** + * Helper for easy usage + */ + public static Map>> snapshot(File file) throws IOException { + MediaInfo mi = new MediaInfo(); + try { + if (mi.open(file)) { + return mi.snapshot(); + } else { + throw new IOException("Failed to open file: " + file); + } + } finally { + mi.close(); + } + } } diff --git a/website/scripts/artwork.tmdb.groovy b/website/scripts/artwork.tmdb.groovy index a17e557e..070f7d78 100644 --- a/website/scripts/artwork.tmdb.groovy +++ b/website/scripts/artwork.tmdb.groovy @@ -38,7 +38,7 @@ args.eachMediaFolder { dir -> println "$dir => $movie" try { - fetchMovieArtworkAndNfo(dir, movie) + fetchMovieArtworkAndNfo(dir, movie, dir.getFiles{ it.isVideo() }.sort{ it.length() }.reverse().findResult{ it } ) } catch(e) { println "${e.class.simpleName}: ${e.message}" } diff --git a/website/scripts/lib/htpc.groovy b/website/scripts/lib/htpc.groovy index 07bfcc6d..91bd394b 100644 --- a/website/scripts/lib/htpc.groovy +++ b/website/scripts/lib/htpc.groovy @@ -1,19 +1,19 @@ import static net.sourceforge.filebot.WebServices.* +import groovy.xml.* +import net.sourceforge.filebot.mediainfo.* + + /** * XBMC helper functions */ def invokeScanVideoLibrary(host, port = 9090) { - try { + _guarded { telnet(host, port) { writer, reader -> writer.println('{"id":1,"method":"VideoLibrary.Scan","params":[],"jsonrpc":"2.0"}') // API call for latest XBMC release } - return true - } catch(e) { - println "${e.class.simpleName}: ${e.message}" - return false } } @@ -23,7 +23,7 @@ def invokeScanVideoLibrary(host, port = 9090) { * Plex helpers */ def refreshPlexLibrary(server, port = 32400, files = null) { - try { + _guarded { def sections = new URL("http://$server:$port/plex").getXml() def locations = sections.Directory.Location.collect{ [path:it.'@path', key:it.parent().'@key'] } @@ -35,10 +35,6 @@ def refreshPlexLibrary(server, port = 32400, files = null) { 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 } } @@ -97,7 +93,7 @@ def fetchSeriesNfo(outputFile, series, locale) { } def fetchSeriesArtworkAndNfo(seriesDir, seasonDir, series, season, locale = _args.locale) { - try { + _guarded { // fetch nfo fetchSeriesNfo(seriesDir['tvshow.nfo'], series, locale) @@ -123,8 +119,6 @@ def fetchSeriesArtworkAndNfo(seriesDir, seasonDir, series, season, locale = _arg if (seasonDir != seriesDir) { fetchSeriesFanart(seasonDir['landscape.jpg'], series, 'seasonthumb', season, locale) } - } catch(e) { - println "${e.class.simpleName}: ${e.message}" } } @@ -155,7 +149,45 @@ def fetchMovieFanart(outputFile, movieInfo, type, diskType, locale) { return fanart.url.saveAs(outputFile) } -def fetchMovieNfo(outputFile, movieInfo) { +def createFileInfoXml(file) { + _guarded { + def mi = MediaInfo.snapshot(file) + def out = new StringWriter() + def xml = new MarkupBuilder(out) + xml.fileinfo() { + streamdetails() { + mi.each { kind, streams -> + def section = kind.toString().toLowerCase() + streams.each { s -> + if (section == 'video') { + video() { + codec((s.'Encoded_Library/Name' ?: s.'CodecID/Hint' ?: s.'Format').replaceAll(/[ ].+/, '').trim()) + aspect(s.'DisplayAspectRatio') + width(s.'Width') + height(s.'Height') + } + } + if (section == 'audio') { + audio() { + codec((s.'CodecID/Hint' ?: s.'Format').replaceAll(/\p{Punct}/, '').trim()) + language(s.'Language/String3') + channels(s.'Channel(s)') + } + } + if (section == 'text') { + subtitle() { + language(s.'Language/String3') + } + } + } + } + } + } + return out.toString() + } +} + +def fetchMovieNfo(outputFile, movieInfo, movieFile) { movieInfo.applyXmlTemplate(''' $name $originalName @@ -176,18 +208,21 @@ def fetchMovieNfo(outputFile, movieInfo) { ${it?.character} } + ''' + (createFileInfoXml(movieFile) ?: '') + ''' + http://www.imdb.com/title/tt${imdbId.pad(7)}/ + http://www.themoviedb.org/movie/$id ''') .replaceAll(/\t|\r|\n/, '') // xbmc can't handle leading/trailing whitespace properly .saveAs(outputFile) } -def fetchMovieArtworkAndNfo(movieDir, movie, locale = _args.locale) { - try { +def fetchMovieArtworkAndNfo(movieDir, movie, movieFile = null, locale = _args.locale) { + _guarded { def movieInfo = TheMovieDB.getMovieInfo(movie, locale) // fetch nfo - fetchMovieNfo(movieDir['movie.nfo'], movieInfo) + fetchMovieNfo(movieDir['movie.nfo'], movieInfo, movieFile) // fetch series banner, fanart, posters, etc fetchMovieArtwork(movieDir['poster.jpg'], movieInfo, 'posters', locale.language) @@ -196,7 +231,5 @@ def fetchMovieArtworkAndNfo(movieDir, movie, locale = _args.locale) { fetchMovieFanart(movieDir['clearart.png'], movieInfo, 'movieart', null, locale) fetchMovieFanart(movieDir['logo.png'], movieInfo, 'movielogo', 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/utorrent-postprocess.groovy b/website/scripts/utorrent-postprocess.groovy index e2b680ce..f4d7f20e 100644 --- a/website/scripts/utorrent-postprocess.groovy +++ b/website/scripts/utorrent-postprocess.groovy @@ -107,7 +107,7 @@ groups.each{ group, files -> if (dest && artwork) { dest.mapByFolder().each{ dir, fs -> println "Fetching artwork for $dir from TheMovieDB" - fetchMovieArtworkAndNfo(dir, group.mov) + fetchMovieArtworkAndNfo(dir, group.mov, fs.findAll{ it.isVideo() }.sort{ it.length() }.reverse().findResult{ it }) } } if (dest == null && failOnError) {