* added fetch banner script
* refactored thetvdb banner api
This commit is contained in:
parent
00b1947fd5
commit
1a43b7c5fd
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue