diff --git a/source/net/sourceforge/filebot/cli/ScriptShell.lib.groovy b/source/net/sourceforge/filebot/cli/ScriptShell.lib.groovy index 13a395ed..045f1111 100644 --- a/source/net/sourceforge/filebot/cli/ScriptShell.lib.groovy +++ b/source/net/sourceforge/filebot/cli/ScriptShell.lib.groovy @@ -123,6 +123,9 @@ import groovy.xml.MarkupBuilder def XML(bc) { def out = new StringWriter() def xmb = new MarkupBuilder(out) + xmb.omitNullAttributes = true + xmb.omitEmptyAttributes = true + xmb.expandEmptyElements= true bc.rehydrate(bc.delegate, xmb, xmb).call() // call closure in MarkupBuilder context return out.toString() } diff --git a/source/net/sourceforge/filebot/format/PropertyBindings.java b/source/net/sourceforge/filebot/format/PropertyBindings.java index bd2a64c2..a77e35d6 100644 --- a/source/net/sourceforge/filebot/format/PropertyBindings.java +++ b/source/net/sourceforge/filebot/format/PropertyBindings.java @@ -26,7 +26,7 @@ public class PropertyBindings extends AbstractMap { this.defaultValue = defaultValue; // get method bindings - for (Method method : object.getClass().getDeclaredMethods()) { + for (Method method : object.getClass().getMethods()) { if (method.getReturnType() != void.class && method.getParameterTypes().length == 0) { // normal properties if (method.getName().length() > 3 && method.getName().substring(0, 3).equalsIgnoreCase("get")) { diff --git a/website/scripts/artwork.tmdb.groovy b/website/scripts/artwork.tmdb.groovy index 3ef17d43..3c1dd432 100644 --- a/website/scripts/artwork.tmdb.groovy +++ b/website/scripts/artwork.tmdb.groovy @@ -5,18 +5,17 @@ */ // artwork/nfo helpers -include("lib/htpc") +include('lib/htpc') args.eachMediaFolder{ dir -> // fetch only missing artwork by default - if (_args.conflict == "skip" && dir.hasFile{it =~ /movie.nfo$/} && dir.hasFile{it =~ /poster.jpg$/} && dir.hasFile{it =~ /fanart.jpg$/}) { + if (_args.conflict == 'skip' && dir.hasFile{it.name == 'movie.nfo'} && dir.hasFile{it.name == 'poster.jpg'} && dir.hasFile{it.name == 'fanart.jpg'}) { println "Skipping $dir" return } def videos = dir.listFiles{ it.isVideo() } - def query = _args.query def options = [] @@ -40,13 +39,13 @@ args.eachMediaFolder{ dir -> // maybe require user input if (options.size() != 1 && !_args.nonStrict && !java.awt.GraphicsEnvironment.headless) { - movie = javax.swing.JOptionPane.showInputDialog(null, "Please select Movie:", dir.path, 3, null, options.toArray(), movie); + movie = javax.swing.JOptionPane.showInputDialog(null, 'Please select Movie:', dir.path, 3, null, options.toArray(), movie) if (movie == null) return null } println "$dir => $movie" try { - fetchMovieArtworkAndNfo(dir, movie, dir.getFiles{ it.isVideo() }.sort{ it.length() }.reverse().findResult{ it }, true) + fetchMovieArtworkAndNfo(dir, movie, dir.getFiles{ it.isVideo() }.sort{ it.length() }.reverse().findResult{ it }, true, true) } catch(e) { println "${e.class.simpleName}: ${e.message}" } diff --git a/website/scripts/artwork.tvdb.groovy b/website/scripts/artwork.tvdb.groovy index ac9eb478..b2f5249e 100644 --- a/website/scripts/artwork.tvdb.groovy +++ b/website/scripts/artwork.tvdb.groovy @@ -5,18 +5,17 @@ */ // artwork/nfo helpers -include("lib/htpc") +include('lib/htpc') args.eachMediaFolder{ dir -> // fetch only missing artwork by default - if (_args.conflict == "skip" && [dir, dir.dir].find{ it.hasFile{it =~ /banner.jpg$/} }) { + if (_args.conflict == 'skip' && [dir, dir.dir].find{ it.hasFile{it.name == 'banner.jpg'} }) { println "Skipping $dir" return } def videos = dir.listFiles{ it.isVideo() } - def query = _args.query ?: detectSeriesName(videos, _args.locale) def sxe = videos.findResult{ parseEpisodeNumber(it) } @@ -39,7 +38,7 @@ args.eachMediaFolder{ dir -> // maybe require user input if (options.size() != 1 && !_args.nonStrict && !java.awt.GraphicsEnvironment.headless) { - series = javax.swing.JOptionPane.showInputDialog(null, "Please select TV Show:", dir.path, 3, null, options.toArray(), series); + series = javax.swing.JOptionPane.showInputDialog(null, 'Please select TV Show:', dir.path, 3, null, options.toArray(), series) if (series == null) return } @@ -48,5 +47,5 @@ args.eachMediaFolder{ dir -> def season = sxe && sxe.season > 0 ? sxe.season : 1 println "$dir => $series" - fetchSeriesArtworkAndNfo(seriesDir, dir, series, season) + fetchSeriesArtworkAndNfo(seriesDir, dir, series, season, true) } diff --git a/website/scripts/lib/htpc.groovy b/website/scripts/lib/htpc.groovy index 7dd15b9c..0bcfc4cb 100644 --- a/website/scripts/lib/htpc.groovy +++ b/website/scripts/lib/htpc.groovy @@ -33,7 +33,12 @@ def refreshPlexLibrary(server, port = 32400) { /** * TheTVDB artwork/nfo helpers */ -def fetchSeriesBanner(outputFile, series, bannerType, bannerType2, season, locale) { +def fetchSeriesBanner(outputFile, series, bannerType, bannerType2, season, override, locale) { + if (outputFile.exists() && !override) { + println "Banner already exists: $outputFile" + return outputFile + } + // select and fetch banner def banner = [locale, null].findResult { TheTVDB.getBanner(series, [BannerType:bannerType, BannerType2:bannerType2, Season:season, Language:it]) } if (banner == null) { @@ -44,7 +49,12 @@ def fetchSeriesBanner(outputFile, series, bannerType, bannerType2, season, local return banner.url.saveAs(outputFile) } -def fetchSeriesFanart(outputFile, series, type, season, locale) { +def fetchSeriesFanart(outputFile, series, type, season, override, locale) { + if (outputFile.exists() && !override) { + println "Fanart already exists: $outputFile" + return outputFile + } + 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" @@ -54,61 +64,64 @@ def fetchSeriesFanart(outputFile, series, type, season, locale) { return fanart.url.saveAs(outputFile) } -def fetchSeriesNfo(outputFile, series, locale) { - def info = TheTVDB.getSeriesInfo(series, locale) - info.applyXml(''' - $name - ${[name, firstAired as String].findAll{ !it.empty }.join(/ :: /)} - $firstAired.year - $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 +def fetchSeriesNfo(outputFile, seriesInfo, override, locale) { + def i = seriesInfo + XML { + tvshow { + title(i.name) + sorttitle([i.name, i.firstAired as String].findAll{ it?.length() > 0 }.join('::')) + year(i.firstAired?.year) + rating(i.rating) + votes(i.ratingCount) + plot(i.overview) + runtime(i.runtime) + mpaa(i.contentRating) + id(i.id) + episodeguide { + url(cache:"${i.id}.xml", "http://www.thetvdb.com/api/1D62F2F90030C444/series/${i.id}/all/${locale.language}.zip") + } + genre(i.genres?.size() > 0 ? i.genres[0] : null) + thumb(i.bannerUrl) + premiered(i.firstAired) + status(i.status) + studio(i.network) + i.actors?.each{ n -> + actor { + name(n) + } + } + } + } .saveAs(outputFile) } -def fetchSeriesArtworkAndNfo(seriesDir, seasonDir, series, season, locale = _args.locale) { +def fetchSeriesArtworkAndNfo(seriesDir, seasonDir, series, season, override = false, locale = _args.locale) { _guarded { // fetch nfo - fetchSeriesNfo(seriesDir['tvshow.nfo'], series, locale) + def seriesInfo = TheTVDB.getSeriesInfo(series, locale) + fetchSeriesNfo(seriesDir['tvshow.nfo'], seriesInfo, override, locale) // fetch series banner, fanart, posters, etc - ["680x1000", null].findResult{ fetchSeriesBanner(seriesDir['poster.jpg'], series, "poster", it, null, locale) } - ["graphical", null].findResult{ fetchSeriesBanner(seriesDir['banner.jpg'], series, "series", it, null, locale) } + ["680x1000", null].findResult{ fetchSeriesBanner(seriesDir['poster.jpg'], series, "poster", it, null, override, locale) } + ["graphical", null].findResult{ fetchSeriesBanner(seriesDir['banner.jpg'], series, "series", it, null, override, locale) } // fetch highest resolution fanart - ["1920x1080", "1280x720", null].findResult{ fetchSeriesBanner(seriesDir["fanart.jpg"], series, "fanart", it, null, locale) } + ["1920x1080", "1280x720", null].findResult{ fetchSeriesBanner(seriesDir["fanart.jpg"], series, "fanart", it, null, override, locale) } // fetch season banners if (seasonDir != seriesDir) { - fetchSeriesBanner(seasonDir["poster.jpg"], series, "season", "season", season, locale) - fetchSeriesBanner(seasonDir["banner.jpg"], series, "season", "seasonwide", season, locale) + fetchSeriesBanner(seasonDir["poster.jpg"], series, "season", "season", season, override, locale) + fetchSeriesBanner(seasonDir["banner.jpg"], series, "season", "seasonwide", season, override, locale) } // fetch fanart - fetchSeriesFanart(seriesDir['clearart.png'], series, 'clearart', null, locale) - fetchSeriesFanart(seriesDir['logo.png'], series, 'clearlogo', null, locale) - fetchSeriesFanart(seriesDir['landscape.jpg'], series, 'tvthumb', null, locale) + fetchSeriesFanart(seriesDir['clearart.png'], series, 'clearart', null, override, locale) + fetchSeriesFanart(seriesDir['logo.png'], series, 'clearlogo', null, override, locale) + fetchSeriesFanart(seriesDir['landscape.jpg'], series, 'tvthumb', null, override, locale) // fetch season fanart if (seasonDir != seriesDir) { - fetchSeriesFanart(seasonDir['landscape.jpg'], series, 'seasonthumb', season, locale) + fetchSeriesFanart(seasonDir['landscape.jpg'], series, 'seasonthumb', season, override, locale) } } } @@ -118,10 +131,15 @@ def fetchSeriesArtworkAndNfo(seriesDir, seasonDir, series, season, locale = _arg /** * TheMovieDB artwork/nfo helpers */ -def fetchMovieArtwork(outputFile, movieInfo, category, language) { +def fetchMovieArtwork(outputFile, movieInfo, category, override, locale) { + if (outputFile.exists() && !override) { + println "Artwork already exists: $outputFile" + return outputFile + } + // select and fetch artwork def artwork = TheMovieDB.getArtwork(movieInfo.id as String) - def selection = [language, 'en', null].findResult{ l -> artwork.find{ (l == it.language || l == null) && it.category == category } } + def selection = [locale.language, 'en', null].findResult{ l -> artwork.find{ (l == it.language || l == null) && it.category == category } } if (selection == null) { println "Artwork not found: $outputFile" return null @@ -130,22 +148,31 @@ def fetchMovieArtwork(outputFile, movieInfo, category, language) { return selection.url.saveAs(outputFile) } -def fetchAllMovieArtwork(outputFolder, movieInfo, category, language) { +def fetchAllMovieArtwork(outputFolder, movieInfo, category, override, locale) { // select and fetch artwork def artwork = TheMovieDB.getArtwork(movieInfo.id as String) - def selection = [language, 'en', null].findResults{ l -> artwork.findAll{ (l == it.language || l == null) && it.category == category } }.flatten().findAll{ it?.url }.unique() + def selection = [locale.language, 'en', null].findResults{ l -> artwork.findAll{ (l == it.language || l == null) && it.category == category } }.flatten().findAll{ it?.url }.unique() if (selection == null) { println "Artwork not found: $outputFolder" return null } selection.eachWithIndex{ s, i -> def outputFile = new File(outputFolder, "$category-${(i+1).pad(2)}.jpg") - println "Fetching $outputFile => $s" - s.url.saveAs(outputFile) + if (outputFile.exists() && !override) { + println "Artwork already exists: $outputFile" + } else { + println "Fetching $outputFile => $s" + s.url.saveAs(outputFile) + } } } -def fetchMovieFanart(outputFile, movieInfo, type, diskType, locale) { +def fetchMovieFanart(outputFile, movieInfo, type, diskType, override, locale) { + if (outputFile.exists() && !override) { + println "Fanart already exists: $outputFile" + return outputFile + } + 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" @@ -155,92 +182,92 @@ def fetchMovieFanart(outputFile, movieInfo, type, diskType, locale) { return fanart.url.saveAs(outputFile) } -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') +def fetchMovieNfo(outputFile, movieInfo, movieFile, override) { + def i = movieInfo + def mi = _guarded{ movieFile ? MediaInfo.snapshot(movieFile) : null } + XML { + movie { + title(i.name) + originaltitle(i.originalName) + sorttitle([i.collection, i.name, i.released as String].findAll{ it?.length() > 0 }.join('::')) + set(i.collection) + year(i.released?.year) + rating(i.rating) + votes(i.votes) + mpaa(i.certification) + id("tt" + (i.imdbId ?: 0).pad(7)) + plot(i.overview) + tagline(i.tagline) + runtime(i.runtime) + genre(i.genres?.size() > 0 ? i.genres[0] : null) + director(i.director) + i.cast?.each{ a -> + actor { + name(a.name) + role(a.character) + } + } + 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 == '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') + if (section == 'text') { + subtitle { + language(s.'Language/String3') + } } } } } } + imdb(id:"tt" + (i.imdbId ?: 0).pad(7), "http://www.imdb.com/title/tt" + (i.imdbId ?: 0).pad(7)) + tmdb(id:i.id, "http://www.themoviedb.org/movie/${i.id}") } - return out.toString() } -} - -def fetchMovieNfo(outputFile, movieInfo, movieFile) { - movieInfo.applyXml(''' - $name - $originalName - ${[collection, name, released as String].findAll{ !it.empty }.join(/ :: /)} - $collection - $released.year - $rating - $votes - $certification - tt${imdbId.pad(7)} - $overview - $tagline - $runtime - ${!genres.empty ? genres[0] : ''} - $director - cast.each { - - ${it?.name} - ${it?.character} - - } - ''' + ((movieFile != null ? createFileInfoXml(movieFile) : null) ?: '') + ''' - 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, movieFile = null, fetchAll = false, locale = _args.locale) { +def fetchMovieArtworkAndNfo(movieDir, movie, movieFile = null, fetchAll = false, override = false, locale = _args.locale) { _guarded { def movieInfo = TheMovieDB.getMovieInfo(movie, locale) // fetch nfo - fetchMovieNfo(movieDir['movie.nfo'], movieInfo, movieFile) + fetchMovieNfo(movieDir['movie.nfo'], movieInfo, movieFile, override) + + // generate url files + [[db:'imdb', id:movieInfo.imdbId, url:"http://www.imdb.com/title/tt{movieInfo.imdbId?.pad(7)}"], [db:'tmdb', id:movieInfo.id, url:"http://www.themoviedb.org/movie/${movieInfo.id}"]].each{ + if (it.id > 0) { + def content = "[InternetShortcut]\nURL=${it.url}\n" + content.saveAs(new File(movieDir, "${it.db}.url")) + } + } // fetch series banner, fanart, posters, etc - fetchMovieArtwork(movieDir['poster.jpg'], movieInfo, 'posters', locale.language) - fetchMovieArtwork(movieDir['fanart.jpg'], movieInfo, 'backdrops', locale.language) + fetchMovieArtwork(movieDir['poster.jpg'], movieInfo, 'posters', override, locale) + fetchMovieArtwork(movieDir['fanart.jpg'], movieInfo, 'backdrops', override, 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) } + fetchMovieFanart(movieDir['clearart.png'], movieInfo, 'movieart', null, override, locale) + fetchMovieFanart(movieDir['logo.png'], movieInfo, 'movielogo', null, override, locale) + ['bluray', 'dvd', null].findResult { diskType -> fetchMovieFanart(movieDir['disc.png'], movieInfo, 'moviedisc', diskType, override, locale) } if (fetchAll) { - fetchAllMovieArtwork(movieDir['backdrops'], movieInfo, 'backdrops', locale.language) + fetchAllMovieArtwork(movieDir['backdrops'], movieInfo, 'backdrops', override, locale) } } }