mirror of
https://github.com/mitb-archive/filebot
synced 2024-12-25 09:18:51 -05:00
* 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.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)
|
||||||
|
@ -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,8 +415,11 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class BannerDescriptor {
|
||||||
|
|
||||||
public static enum BannerProperty {
|
public static enum BannerProperty {
|
||||||
id,
|
id,
|
||||||
|
BannerMirror,
|
||||||
BannerPath,
|
BannerPath,
|
||||||
BannerType,
|
BannerType,
|
||||||
BannerType2,
|
BannerType2,
|
||||||
@ -445,15 +430,95 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
|
|||||||
RatingCount,
|
RatingCount,
|
||||||
SeriesName,
|
SeriesName,
|
||||||
ThumbnailPath,
|
ThumbnailPath,
|
||||||
VignettePath;
|
VignettePath
|
||||||
|
|
||||||
public static BannerProperty[] numbers() {
|
|
||||||
return new BannerProperty[] { id, Season, Rating, RatingCount };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
52
website/data/shell/banners.groovy
Normal file
52
website/data/shell/banners.groovy
Normal 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)
|
||||||
|
}
|
18
website/data/shell/cleaner.groovy
Normal file
18
website/data/shell/cleaner.groovy
Normal 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()
|
||||||
|
}
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user