* added fetch banner script

* refactored thetvdb banner api
This commit is contained in:
Reinhard Pointner 2011-12-20 13:00:38 +00:00
parent 00b1947fd5
commit 1a43b7c5fd
6 changed files with 221 additions and 68 deletions

View File

@ -8,8 +8,9 @@ File.metaClass.isVideo = { _types.getFilter("video").accept(delegate) }
File.metaClass.isAudio = { _types.getFilter("audio").accept(delegate) } File.metaClass.isAudio = { _types.getFilter("audio").accept(delegate) }
File.metaClass.isSubtitle = { _types.getFilter("subtitle").accept(delegate) } File.metaClass.isSubtitle = { _types.getFilter("subtitle").accept(delegate) }
File.metaClass.isVerification = { _types.getFilter("verification").accept(delegate) } File.metaClass.isVerification = { _types.getFilter("verification").accept(delegate) }
File.metaClass.isArchive = { _types.getFilter("archive").accept(delegate) }
File.metaClass.dir = { getParentFile() } File.metaClass.getDir = { getParentFile() }
File.metaClass.hasFile = { c -> isDirectory() && listFiles().find(c) } File.metaClass.hasFile = { c -> isDirectory() && listFiles().find(c) }
String.metaClass.getFiles = { c -> new File(delegate).getFiles(c) } String.metaClass.getFiles = { c -> new File(delegate).getFiles(c) }
@ -38,6 +39,8 @@ File.metaClass.validateFilePath = { validateFilePath(delegate) }
File.metaClass.moveTo = { f -> renameFile(delegate, f) } File.metaClass.moveTo = { f -> renameFile(delegate, f) }
List.metaClass.mapByFolder = { mapByFolder(delegate) } List.metaClass.mapByFolder = { mapByFolder(delegate) }
List.metaClass.mapByExtension = { mapByExtension(delegate) } List.metaClass.mapByExtension = { mapByExtension(delegate) }
String.metaClass.getExtension = { getExtension(delegate) }
String.metaClass.hasExtension = { String... ext -> hasExtension(delegate, ext) }
// WebRequest utility methods // WebRequest utility methods
@ -78,19 +81,20 @@ def getWatchService(Closure callback, List folders) {
folders.find{ if (!it.isDirectory()) throw new Exception("Must be a folder: " + it) } folders.find{ if (!it.isDirectory()) throw new Exception("Must be a folder: " + it) }
// create watch service and setup callback // create watch service and setup callback
def watchService = new FolderWatchService() { def watchService = new FolderWatchService(true) {
@Override @Override
def void processCommitSet(File[] fileset) { def void processCommitSet(File[] fileset, File dir) {
callback(fileset.toList()) callback(fileset.toList())
} }
} }
// collect updates for 5 minutes and then batch process // collect updates for 5 minutes and then batch process
watchService.setCommitDelay(5 * 60 * 1000) watchService.setCommitDelay(5 * 60 * 1000)
watchService.setCommitPerFolder(true)
// start watching given files // start watching given files
folders.each { watchService.watch(it) } folders.each { dir -> _guarded { watchService.watchFolder(dir) } }
return watchService return watchService
} }
@ -99,6 +103,22 @@ File.metaClass.watch = { c -> getWatchService(c, [delegate]) }
List.metaClass.watch = { c -> getWatchService(c, delegate) } List.metaClass.watch = { c -> getWatchService(c, delegate) }
// Season / Episode helpers
import net.sourceforge.filebot.mediainfo.ReleaseInfo
import net.sourceforge.filebot.similarity.SeasonEpisodeMatcher
def guessEpisodeNumber(path) {
def input = path instanceof File ? path.getName() : path.toString()
def sxe = new SeasonEpisodeMatcher(new SeasonEpisodeMatcher.SeasonEpisodeFilter(30, 50, 1000)).match(input)
return sxe == null || sxe.isEmpty() ? null : sxe[0]
}
def detectSeriesName(files) {
def names = ReleaseInfo.detectSeriesNames(files.findAll { it.isVideo() || it.isSubtitle() })
return names == null || names.isEmpty() ? null : names[0]
}
// CLI bindings // CLI bindings
def rename(args) { args = _defaults(args) def rename(args) { args = _defaults(args)

View File

@ -7,6 +7,7 @@ import static net.sourceforge.filebot.web.WebRequest.*;
import static net.sourceforge.tuned.XPathUtilities.*; import static net.sourceforge.tuned.XPathUtilities.*;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URI; import java.net.URI;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
@ -30,6 +31,8 @@ import org.w3c.dom.Node;
import net.sf.ehcache.CacheManager; import net.sf.ehcache.CacheManager;
import net.sourceforge.filebot.ResourceManager; import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.web.TheTVDBClient.BannerDescriptor.BannerProperty;
import net.sourceforge.tuned.FileUtilities;
public class TheTVDBClient extends AbstractEpisodeListProvider { public class TheTVDBClient extends AbstractEpisodeListProvider {
@ -367,21 +370,11 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
* *
* @see http://thetvdb.com/wiki/index.php/API:banners.xml * @see http://thetvdb.com/wiki/index.php/API:banners.xml
*/ */
public Map<BannerProperty, Object> getBanner(TheTVDBSearchResult series, String bannerType, String bannerType2, Integer season, String language) throws Exception { public BannerDescriptor getBanner(TheTVDBSearchResult series, String bannerType, String bannerType2, Integer season, Locale locale) throws Exception {
// build selector
Map<BannerProperty, Object> selector = new EnumMap<BannerProperty, Object>(BannerProperty.class);
if (bannerType != null)
selector.put(BannerProperty.BannerType, bannerType);
if (bannerType2 != null)
selector.put(BannerProperty.BannerType2, bannerType2);
if (season != null)
selector.put(BannerProperty.Season, new Double(season));
if (language != null)
selector.put(BannerProperty.Language, language);
// search for a banner matching the selector // search for a banner matching the selector
for (Map<BannerProperty, Object> it : getBannerDescriptor(series.seriesId)) { for (BannerDescriptor it : getBannerList(series.seriesId)) {
if (it.entrySet().containsAll(selector.entrySet())) { if ((bannerType == null || it.getBannerType().equalsIgnoreCase(bannerType)) && (bannerType2 == null || it.getBannerType2().equalsIgnoreCase(bannerType2)) && (season == null || it.getSeason().equals(season))
&& ((locale == null && it.getLocale().getLanguage().equals("en")) || it.getLocale().getLanguage().equals(locale.getLanguage()))) {
return it; return it;
} }
} }
@ -390,15 +383,18 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
} }
public List<Map<BannerProperty, Object>> getBannerDescriptor(int seriesid) throws Exception { public List<BannerDescriptor> getBannerList(int seriesid) throws Exception {
Document dom = getDocument(getResource(MirrorType.XML, "/api/" + apikey + "/series/" + seriesid + "/banners.xml")); Document dom = getDocument(getResource(MirrorType.XML, "/api/" + apikey + "/series/" + seriesid + "/banners.xml"));
List<Node> nodes = selectNodes("//Banner", dom); List<Node> nodes = selectNodes("//Banner", dom);
List<Map<BannerProperty, Object>> banners = new ArrayList<Map<BannerProperty, Object>>(); List<BannerDescriptor> banners = new ArrayList<BannerDescriptor>();
for (Node node : nodes) { for (Node node : nodes) {
try { try {
EnumMap<BannerProperty, Object> item = new EnumMap<BannerProperty, Object>(BannerProperty.class); EnumMap<BannerProperty, String> item = new EnumMap<BannerProperty, String>(BannerProperty.class);
// insert banner mirror
item.put(BannerProperty.BannerMirror, getResource(MirrorType.BANNER, "/banners/").toString());
// copy values from xml // copy values from xml
for (BannerProperty key : BannerProperty.values()) { for (BannerProperty key : BannerProperty.values()) {
@ -408,21 +404,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
} }
} }
// parse numbers banners.add(new BannerDescriptor(item));
for (BannerProperty key : BannerProperty.numbers()) {
if (item.get(key) != null) {
item.put(key, new Double(item.get(key).toString()));
}
}
// resolve relative urls
for (BannerProperty key : BannerProperty.urls()) {
if (item.get(key) != null) {
item.put(key, getResource(MirrorType.BANNER, "/banners/" + item.get(key)));
}
}
banners.add(item);
} catch (Exception e) { } catch (Exception e) {
// log and ignore // log and ignore
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Invalid banner descriptor", e); Logger.getLogger(getClass().getName()).log(Level.WARNING, "Invalid banner descriptor", e);
@ -433,27 +415,110 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
} }
public static enum BannerProperty { public static class BannerDescriptor {
id,
BannerPath,
BannerType,
BannerType2,
Season,
Colors,
Language,
Rating,
RatingCount,
SeriesName,
ThumbnailPath,
VignettePath;
public static BannerProperty[] numbers() { public static enum BannerProperty {
return new BannerProperty[] { id, Season, Rating, RatingCount }; id,
BannerMirror,
BannerPath,
BannerType,
BannerType2,
Season,
Colors,
Language,
Rating,
RatingCount,
SeriesName,
ThumbnailPath,
VignettePath
} }
public static BannerProperty[] urls() { private EnumMap<BannerProperty, String> fields;
return new BannerProperty[] { BannerPath, ThumbnailPath, VignettePath };
protected BannerDescriptor(Map<BannerProperty, String> fields) {
this.fields = new EnumMap<BannerProperty, String>(fields);
}
public URL getMirrorUrl() throws MalformedURLException {
return new URL(fields.get(BannerProperty.BannerMirror));
}
public URL getUrl() throws MalformedURLException {
return new URL(getMirrorUrl(), fields.get(BannerProperty.BannerPath));
}
public String getExtension() {
return FileUtilities.getExtension(fields.get(BannerProperty.BannerPath));
}
public int getId() {
return Integer.parseInt(fields.get(BannerProperty.id));
}
public String getBannerType() {
return fields.get(BannerProperty.BannerType);
}
public String getBannerType2() {
return fields.get(BannerProperty.BannerType2);
}
public Integer getSeason() {
try {
return new Integer(fields.get(BannerProperty.Season));
} catch (NumberFormatException e) {
return null;
}
}
public String getColors() {
return fields.get(BannerProperty.Colors);
}
public Locale getLocale() {
return new Locale(fields.get(BannerProperty.Language));
}
public double getRating() {
return Double.parseDouble(fields.get(BannerProperty.Rating));
}
public int getRatingCount() {
return Integer.parseInt(fields.get(BannerProperty.RatingCount));
}
public boolean hasSeriesName() {
return Boolean.parseBoolean(fields.get(BannerProperty.SeriesName));
}
public URL getThumbnailUrl() throws MalformedURLException {
return new URL(getMirrorUrl(), fields.get(BannerProperty.ThumbnailPath));
}
public URL getVignetteUrl() throws MalformedURLException {
return new URL(getMirrorUrl(), fields.get(BannerProperty.VignettePath));
}
@Override
public String toString() {
return fields.toString();
} }
} }

View File

@ -4,18 +4,16 @@ package net.sourceforge.filebot.web;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.net.URL;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import net.sf.ehcache.CacheManager; import net.sf.ehcache.CacheManager;
import net.sourceforge.filebot.web.TheTVDBClient.BannerProperty; import net.sourceforge.filebot.web.TheTVDBClient.BannerDescriptor;
import net.sourceforge.filebot.web.TheTVDBClient.MirrorType; import net.sourceforge.filebot.web.TheTVDBClient.MirrorType;
import net.sourceforge.filebot.web.TheTVDBClient.TheTVDBSearchResult; import net.sourceforge.filebot.web.TheTVDBClient.TheTVDBSearchResult;
@ -150,24 +148,24 @@ public class TheTVDBClientTest {
@Test @Test
public void getBanner() throws Exception { public void getBanner() throws Exception {
Map<BannerProperty, Object> banner = thetvdb.getBanner(new TheTVDBSearchResult("Buffy the Vampire Slayer", 70327), "season", "seasonwide", 7, "en"); BannerDescriptor banner = thetvdb.getBanner(new TheTVDBSearchResult("Buffy the Vampire Slayer", 70327), "season", "seasonwide", 7, Locale.ENGLISH);
assertEquals(857660, (Double) banner.get(BannerProperty.id), 0); assertEquals(857660, banner.getId(), 0);
assertEquals("season", banner.get(BannerProperty.BannerType)); assertEquals("season", banner.getBannerType());
assertEquals("seasonwide", banner.get(BannerProperty.BannerType2)); assertEquals("seasonwide", banner.getBannerType2());
assertEquals("http://thetvdb.com/banners/seasonswide/70327-7.jpg", banner.get(BannerProperty.BannerPath).toString()); assertEquals("http://thetvdb.com/banners/seasonswide/70327-7.jpg", banner.getUrl().toString());
assertEquals(99712, WebRequest.fetch((URL) banner.get(BannerProperty.BannerPath)).remaining(), 0); assertEquals(99712, WebRequest.fetch(banner.getUrl()).remaining(), 0);
} }
@Test @Test
public void getBannerDescriptor() throws Exception { public void getBannerList() throws Exception {
List<Map<BannerProperty, Object>> banners = thetvdb.getBannerDescriptor(70327); List<BannerDescriptor> banners = thetvdb.getBannerList(70327);
assertEquals(106, banners.size()); assertEquals(106, banners.size());
assertEquals("fanart", banners.get(0).get(BannerProperty.BannerType)); assertEquals("fanart", banners.get(0).getBannerType());
assertEquals("1280x720", banners.get(0).get(BannerProperty.BannerType2)); assertEquals("1280x720", banners.get(0).getBannerType2());
assertEquals(486993, WebRequest.fetch((URL) banners.get(0).get(BannerProperty.BannerPath)).remaining(), 0); assertEquals(486993, WebRequest.fetch(banners.get(0).getUrl()).remaining(), 0);
} }

View File

@ -0,0 +1,52 @@
// filebot -script "http://filebot.sourceforge.net/data/shell/banners.groovy" -trust-script /path/to/media/
/*
* Fetch series and season banners for all tv shows
*/
import static net.sourceforge.filebot.WebServices.*
def fetchBanner(dir, series, bannerType, bannerType2, season = null) {
def name = "$series $bannerType ${season ? 'S'+season : 'all'} $bannerType2".space('.')
// select and fetch banner
def banner = TheTVDB.getBanner(series, bannerType, bannerType2, season, Locale.ENGLISH)
if (banner == null) {
println "Banner not found: $name"
return;
}
println "Fetching $name"
banner.url.saveAs(new File(dir, name + "." + banner.extension))
}
def fetchSeriesBanners(dir, series, seasons) {
println "Fetch banners for $series / Season $seasons"
// fetch series banner
fetchBanner(dir, series, "series", "graphical")
// fetch season banners
seasons.each { s ->
fetchBanner(dir, series, "season", "season", s)
fetchBanner(dir, series, "season", "seasonwide", s)
}
}
args.eachMediaFolder() { dir ->
println "Processing $dir"
def videoFiles = dir.listFiles{ it.isVideo() }
def seriesName = detectSeriesName(videoFiles)
def seasons = videoFiles.findResults { guessEpisodeNumber(it)?.season }.unique()
def options = TheTVDB.search(seriesName)
if (options.isEmpty()) {
println "TV Series not found: $name"
return;
}
fetchSeriesBanners(dir, options[0], seasons)
}

View File

@ -0,0 +1,18 @@
// filebot -script "http://filebot.sourceforge.net/data/shell/cleaner.groovy" -trust-script /path/to/media/
/*
* Delete orphaned "clutter" files like nfo, jpg, etc
*/
def isClutter(file) {
return file.hasExtension("nfo", "txt", "jpg", "jpeg")
}
// delete clutter files in orphaned media folders
args.getFiles{ isClutter(it) && !it.dir.hasFile{ it.isVideo() }}.each {
println "Delete file $it: " + it.delete()
}
// delete empty folders but exclude roots
args.getFolders{ it.getFiles().isEmpty() && !args.contains(it) }.each {
println "Delete dir $it: " + it.deleteDir()
}

View File

@ -94,7 +94,7 @@
<h3>Script Repository</h3> <h3>Script Repository</h3>
<div class="description"> <div class="description">
Find scripts for common tasks <a href="http://filebot.sourceforge.net/data/shell/" target="_blank">here</a>. You can just use these scripts straight Find scripts for common tasks <a href="http://filebot.sourceforge.net/forums/viewtopic.php?f=4&t=5#p5">here</a>. You can just use these scripts straight
away or as a reference for building your own more advanced scripts. If you wrote a really useful script away or as a reference for building your own more advanced scripts. If you wrote a really useful script
please <a href="http://filebot.sourceforge.net/forums/viewtopic.php?f=4&t=5">share it with us</a>. please <a href="http://filebot.sourceforge.net/forums/viewtopic.php?f=4&t=5">share it with us</a>.
</div> </div>