* 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.isSubtitle = { _types.getFilter("subtitle").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) }
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) }
List.metaClass.mapByFolder = { mapByFolder(delegate) }
List.metaClass.mapByExtension = { mapByExtension(delegate) }
String.metaClass.getExtension = { getExtension(delegate) }
String.metaClass.hasExtension = { String... ext -> hasExtension(delegate, ext) }
// 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) }
// create watch service and setup callback
def watchService = new FolderWatchService() {
def watchService = new FolderWatchService(true) {
@Override
def void processCommitSet(File[] fileset) {
def void processCommitSet(File[] fileset, File dir) {
callback(fileset.toList())
}
}
// collect updates for 5 minutes and then batch process
watchService.setCommitDelay(5 * 60 * 1000)
watchService.setCommitPerFolder(true)
// start watching given files
folders.each { watchService.watch(it) }
folders.each { dir -> _guarded { watchService.watchFolder(dir) } }
return watchService
}
@ -99,6 +103,22 @@ File.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
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 java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
@ -30,6 +31,8 @@ import org.w3c.dom.Node;
import net.sf.ehcache.CacheManager;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.web.TheTVDBClient.BannerDescriptor.BannerProperty;
import net.sourceforge.tuned.FileUtilities;
public class TheTVDBClient extends AbstractEpisodeListProvider {
@ -367,21 +370,11 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
*
* @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 {
// 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);
public BannerDescriptor getBanner(TheTVDBSearchResult series, String bannerType, String bannerType2, Integer season, Locale locale) throws Exception {
// search for a banner matching the selector
for (Map<BannerProperty, Object> it : getBannerDescriptor(series.seriesId)) {
if (it.entrySet().containsAll(selector.entrySet())) {
for (BannerDescriptor it : getBannerList(series.seriesId)) {
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;
}
}
@ -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"));
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) {
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
for (BannerProperty key : BannerProperty.values()) {
@ -408,21 +404,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
}
// parse numbers
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);
banners.add(new BannerDescriptor(item));
} catch (Exception e) {
// log and ignore
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Invalid banner descriptor", e);
@ -433,27 +415,110 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
}
public static enum BannerProperty {
id,
BannerPath,
BannerType,
BannerType2,
Season,
Colors,
Language,
Rating,
RatingCount,
SeriesName,
ThumbnailPath,
VignettePath;
public static class BannerDescriptor {
public static BannerProperty[] numbers() {
return new BannerProperty[] { id, Season, Rating, RatingCount };
public static enum BannerProperty {
id,
BannerMirror,
BannerPath,
BannerType,
BannerType2,
Season,
Colors,
Language,
Rating,
RatingCount,
SeriesName,
ThumbnailPath,
VignettePath
}
public static BannerProperty[] urls() {
return new BannerProperty[] { BannerPath, ThumbnailPath, VignettePath };
private EnumMap<BannerProperty, String> fields;
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 java.net.URL;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
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.TheTVDBSearchResult;
@ -150,24 +148,24 @@ public class TheTVDBClientTest {
@Test
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("season", banner.get(BannerProperty.BannerType));
assertEquals("seasonwide", banner.get(BannerProperty.BannerType2));
assertEquals("http://thetvdb.com/banners/seasonswide/70327-7.jpg", banner.get(BannerProperty.BannerPath).toString());
assertEquals(99712, WebRequest.fetch((URL) banner.get(BannerProperty.BannerPath)).remaining(), 0);
assertEquals(857660, banner.getId(), 0);
assertEquals("season", banner.getBannerType());
assertEquals("seasonwide", banner.getBannerType2());
assertEquals("http://thetvdb.com/banners/seasonswide/70327-7.jpg", banner.getUrl().toString());
assertEquals(99712, WebRequest.fetch(banner.getUrl()).remaining(), 0);
}
@Test
public void getBannerDescriptor() throws Exception {
List<Map<BannerProperty, Object>> banners = thetvdb.getBannerDescriptor(70327);
public void getBannerList() throws Exception {
List<BannerDescriptor> banners = thetvdb.getBannerList(70327);
assertEquals(106, banners.size());
assertEquals("fanart", banners.get(0).get(BannerProperty.BannerType));
assertEquals("1280x720", banners.get(0).get(BannerProperty.BannerType2));
assertEquals(486993, WebRequest.fetch((URL) banners.get(0).get(BannerProperty.BannerPath)).remaining(), 0);
assertEquals("fanart", banners.get(0).getBannerType());
assertEquals("1280x720", banners.get(0).getBannerType2());
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>
<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
please <a href="http://filebot.sourceforge.net/forums/viewtopic.php?f=4&t=5">share it with us</a>.
</div>