From ad0e0e28026373a440aed7aaa6df3e12672176a2 Mon Sep 17 00:00:00 2001 From: Reinhard Pointner Date: Thu, 17 Apr 2014 19:52:23 +0000 Subject: [PATCH] * ScriptShell rewrite nearing completion --- source/net/sourceforge/filebot/History.java | 17 + .../filebot/cli/ScriptShell.properties | 2 +- .../filebot/cli/ScriptShellBaseClass.java | 134 ++++++- .../filebot/cli/ScriptShellMethods.java | 337 ++++++++++++++++++ .../filebot/util/FileUtilities.java | 13 +- 5 files changed, 488 insertions(+), 15 deletions(-) diff --git a/source/net/sourceforge/filebot/History.java b/source/net/sourceforge/filebot/History.java index 972c64e0..c45da96b 100644 --- a/source/net/sourceforge/filebot/History.java +++ b/source/net/sourceforge/filebot/History.java @@ -9,7 +9,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -177,6 +179,21 @@ public class History { return sequences.hashCode(); } + public Map getRenameMap() { + Map map = new LinkedHashMap(); + for (History.Sequence seq : this.sequences()) { + for (History.Element elem : seq.elements()) { + File to = new File(elem.to()); + if (!to.isAbsolute()) { + to = new File(elem.dir(), elem.to()); + } + File from = new File(elem.dir(), elem.from()); + map.put(from, to); + } + } + return map; + } + public static void exportHistory(History history, OutputStream output) { try { Marshaller marshaller = JAXBContext.newInstance(History.class).createMarshaller(); diff --git a/source/net/sourceforge/filebot/cli/ScriptShell.properties b/source/net/sourceforge/filebot/cli/ScriptShell.properties index fb340425..749e4fe4 100644 --- a/source/net/sourceforge/filebot/cli/ScriptShell.properties +++ b/source/net/sourceforge/filebot/cli/ScriptShell.properties @@ -1,3 +1,3 @@ scriptBaseClass: net.sourceforge.filebot.cli.ScriptShellBaseClass starImport: net.sourceforge.filebot, net.sourceforge.filebot.hash, net.sourceforge.filebot.media, net.sourceforge.filebot.mediainfo, net.sourceforge.filebot.similarity, net.sourceforge.filebot.subtitle, net.sourceforge.filebot.torrent, net.sourceforge.filebot.web, net.sourceforge.filebot.util, groovy.io, groovy.xml, groovy.json, org.jsoup, java.nio.file, java.nio.file.attribute, java.util.regex -starStaticImport: net.sourceforge.filebot.format.ExpressionFormatFunctions, net.sourceforge.filebot.WebServices, java.nio.file.Files +starStaticImport: net.sourceforge.filebot.WebServices, net.sourceforge.filebot.media.MediaDetection, net.sourceforge.filebot.format.ExpressionFormatFunctions \ No newline at end of file diff --git a/source/net/sourceforge/filebot/cli/ScriptShellBaseClass.java b/source/net/sourceforge/filebot/cli/ScriptShellBaseClass.java index 2e9d5b53..61604ca9 100644 --- a/source/net/sourceforge/filebot/cli/ScriptShellBaseClass.java +++ b/source/net/sourceforge/filebot/cli/ScriptShellBaseClass.java @@ -1,21 +1,42 @@ package net.sourceforge.filebot.cli; +import static java.util.Collections.*; import static net.sourceforge.filebot.Settings.*; import static net.sourceforge.filebot.cli.CLILogging.*; import groovy.lang.Closure; import groovy.lang.MissingPropertyException; import groovy.lang.Script; +import groovy.xml.MarkupBuilder; import java.io.Console; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.StringWriter; +import java.net.Socket; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.Map.Entry; import java.util.logging.Logger; import javax.script.Bindings; import javax.script.SimpleBindings; -import net.sourceforge.filebot.MediaTypes; +import net.sourceforge.filebot.HistorySpooler; +import net.sourceforge.filebot.Settings; +import net.sourceforge.filebot.WebServices; import net.sourceforge.filebot.format.AssociativeScriptObject; +import net.sourceforge.filebot.media.MediaDetection; +import net.sourceforge.filebot.media.MetaAttributes; import net.sourceforge.filebot.util.FileUtilities; +import net.sourceforge.filebot.web.Movie; + +import com.sun.jna.Platform; public abstract class ScriptShellBaseClass extends Script { @@ -107,7 +128,7 @@ public abstract class ScriptShellBaseClass extends Script { // define global variable: _def public Map get_def() { - return getApplicationArguments().defines; + return unmodifiableMap(getApplicationArguments().defines); } // define global variable: _system @@ -120,13 +141,21 @@ public abstract class ScriptShellBaseClass extends Script { return new AssociativeScriptObject(System.getenv()); } - // define global variable: _types - public MediaTypes get_types() { - return MediaTypes.getDefault(); + // Complete or session rename history + public Map getRenameLog() throws IOException { + return getRenameLog(false); } - // define global variable: _log - public Logger get_log() { + public Map getRenameLog(boolean complete) throws IOException { + if (complete) { + return HistorySpooler.getInstance().getCompleteHistory().getRenameMap(); + } else { + return HistorySpooler.getInstance().getSessionHistory().getRenameMap(); + } + } + + // define global variable: log + public Logger getLog() { return CLILogger; } @@ -140,4 +169,95 @@ public abstract class ScriptShellBaseClass extends Script { return null; } + public String detectSeriesName(Object files) throws Exception { + List names = MediaDetection.detectSeriesNames(FileUtilities.asFileList(files), true, false, Locale.ENGLISH); + return names.isEmpty() ? null : names.get(0); + } + + public Movie detectMovie(File file, boolean strict) { + // 1. xattr + try { + return (Movie) new MetaAttributes(file).getObject(); + } catch (Exception e) { + // ignore and move on + } + + // 2. perfect filename match + try { + List names = new ArrayList(); + for (File it : FileUtilities.listPathTail(file, 4, true)) { + names.add(it.getName()); + } + return MediaDetection.matchMovieName(names, true, 0).get(0); + } catch (Exception e) { + // ignore and move on + } + + // 3. run full-fledged movie detection + try { + return MediaDetection.detectMovie(file, WebServices.OpenSubtitles, WebServices.TheMovieDB, Locale.ENGLISH, strict).get(0); + } catch (Exception e) { + // ignore and fail + } + + return null; + } + + public int execute(Object... args) throws Exception { + List cmd = new ArrayList(); + + if (Platform.isWindows()) { + // normalize file separator for windows and run with cmd so any executable in PATH will just work + cmd.add("cmd"); + cmd.add("/c"); + } else if (args.length == 1) { + // make unix shell parse arguments + cmd.add("sh"); + cmd.add("-c"); + } + + for (Object it : args) { + cmd.add(it.toString()); + } + + ProcessBuilder process = new ProcessBuilder(cmd).inheritIO(); + return process.start().waitFor(); + } + + public String XML(Closure buildClosure) { + StringWriter out = new StringWriter(); + MarkupBuilder builder = new MarkupBuilder(out); + buildClosure.rehydrate(buildClosure.getDelegate(), builder, builder).call(); // call closure in MarkupBuilder context + return out.toString(); + } + + public void telnet(String host, int port, Closure handler) throws IOException { + try (Socket socket = new Socket(host, port)) { + handler.call(new PrintStream(socket.getOutputStream(), true, "UTF-8"), new InputStreamReader(socket.getInputStream(), "UTF-8")); + } + } + + private enum OptionName { + action, conflict, query, filter, format, db, order, lang, output, encoding, strict + } + + private Map withDefaultOptions(Map map) throws Exception { + Map options = new EnumMap(OptionName.class); + + for (Entry it : map.entrySet()) { + options.put(OptionName.valueOf(it.getKey()), it.getValue()); + } + + ArgumentBean defaultValues = Settings.getApplicationArguments(); + for (OptionName missing : EnumSet.complementOf(EnumSet.copyOf(options.keySet()))) { + if (missing == OptionName.strict) { + options.put(missing, !defaultValues.nonStrict); + } else { + Object value = defaultValues.getClass().getField(missing.name()).get(defaultValues); + options.put(missing, value); + } + } + + return options; + } } diff --git a/source/net/sourceforge/filebot/cli/ScriptShellMethods.java b/source/net/sourceforge/filebot/cli/ScriptShellMethods.java index 44dc3e57..3d7d9e6f 100644 --- a/source/net/sourceforge/filebot/cli/ScriptShellMethods.java +++ b/source/net/sourceforge/filebot/cli/ScriptShellMethods.java @@ -1,5 +1,342 @@ package net.sourceforge.filebot.cli; +import static java.util.Arrays.*; +import static java.util.Collections.*; +import static net.sourceforge.filebot.MediaTypes.*; +import groovy.lang.Closure; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import net.sourceforge.filebot.MediaTypes; +import net.sourceforge.filebot.MetaAttributeView; +import net.sourceforge.filebot.media.MediaDetection; +import net.sourceforge.filebot.media.MetaAttributes; +import net.sourceforge.filebot.similarity.NameSimilarityMetric; +import net.sourceforge.filebot.similarity.Normalization; +import net.sourceforge.filebot.similarity.SimilarityMetric; +import net.sourceforge.filebot.util.FileUtilities; +import net.sourceforge.filebot.web.WebRequest; + +import org.codehaus.groovy.runtime.DefaultGroovyMethods; + +import com.cedarsoftware.util.io.JsonReader; +import com.cedarsoftware.util.io.JsonWriter; + public class ScriptShellMethods { + public static File resolve(File self, Object name) { + return new File(self, name.toString()); + } + + public static File resolveSibling(File self, Object name) { + return new File(self.getParentFile(), name.toString()); + } + + public static List listFiles(File self, Closure closure) { + File[] files = self.listFiles(); + if (files == null) + return emptyList(); + + return (List) DefaultGroovyMethods.findAll(asList(files), closure); + } + + public static boolean isVideo(File self) { + return VIDEO_FILES.accept(self); + } + + public static boolean isAudio(File self) { + return AUDIO_FILES.accept(self); + } + + public static boolean isSubtitle(File self) { + return SUBTITLE_FILES.accept(self); + } + + public static boolean isVerification(File self) { + return VERIFICATION_FILES.accept(self); + } + + public static boolean isArchive(File self) { + return ARCHIVE_FILES.accept(self); + } + + public static boolean isDisk(File self) throws Exception { + // check disk folder + if (self.isDirectory() && MediaDetection.isDiskFolder(self)) + return true; + + // check disk image + if (self.isFile() && MediaTypes.getDefaultFilter("video/iso").accept(self) && MediaDetection.isVideoDiskFile(self)) + return true; + + return false; + } + + public static File getDir(File self) { + return self.getParentFile(); + } + + public static boolean hasFile(File self, Closure closure) { + File[] files = self.listFiles(); + if (files == null) + return false; + + return DefaultGroovyMethods.find(asList(files), closure) != null; + } + + public static List getFiles(File self) { + return getFiles(self, null); + } + + public static List getFiles(File self, Closure closure) { + return getFiles(singletonList(self), closure); + } + + public static List getFiles(Collection self) { + return getFiles(self, null); + } + + public static List getFiles(Collection self, Closure closure) { + final List roots = FileUtilities.asFileList(self.toArray()); + + List files = FileUtilities.listFiles(roots); + if (closure != null) { + files = (List) DefaultGroovyMethods.findAll(files, closure); + } + + return FileUtilities.sortByUniquePath(files); + } + + public static List getFolders(File self) { + return getFolders(self, null); + } + + public static List getFolders(File self, Closure closure) { + return getFolders(singletonList(self), closure); + } + + public static List getFolders(Collection self) { + return getFolders(self, null); + } + + public static List getFolders(Collection self, Closure closure) { + final List roots = FileUtilities.asFileList(self.toArray()); + + List folders = FileUtilities.listFolders(roots); + if (closure != null) { + folders = (List) DefaultGroovyMethods.findAll(folders, closure); + } + + return FileUtilities.sortByUniquePath(folders); + } + + public static List getMediaFolders(File self) throws IOException { + final List mediaFolders = new ArrayList(); + + Files.walkFileTree(self.toPath(), new SimpleFileVisitor() { + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + File folder = dir.toFile(); + + if (FileUtilities.filter(asList(folder.listFiles()), VIDEO_FILES).size() > 0 || MediaDetection.isDiskFolder(folder)) { + mediaFolders.add(folder); + return FileVisitResult.SKIP_SUBTREE; + } + + return FileVisitResult.CONTINUE; + } + }); + + return FileUtilities.sortByUniquePath(mediaFolders); + } + + public static String getNameWithoutExtension(File self) { + return FileUtilities.getNameWithoutExtension(self.getName()); + } + + public static String getNameWithoutExtension(String self) { + return FileUtilities.getNameWithoutExtension(self); + } + + public static String getExtension(File self) { + return FileUtilities.getExtension(self); + } + + public static String getExtension(String self) { + return FileUtilities.getExtension(self); + } + + public static boolean hasExtension(File self, String... extensions) { + return FileUtilities.hasExtension(self, extensions); + } + + public static boolean hasExtension(String self, String... extensions) { + return FileUtilities.hasExtension(self, extensions); + } + + public static boolean isDerived(File self, File other) { + return FileUtilities.isDerived(self, other); + } + + public static File validateFileName(File self) { + return FileUtilities.validateFileName(self); + } + + public static String validateFileName(String self) { + return FileUtilities.validateFileName(self); + } + + public static File validateFilePath(File self) { + return FileUtilities.validateFilePath(self); + } + + public static File moveTo(File self, File destination) throws IOException { + return FileUtilities.moveRename(self, destination); + } + + public static File copyAs(File self, File destination) throws IOException { + return FileUtilities.copyAs(self, destination); + } + + public static File copyTo(File self, File destination) throws IOException { + return FileUtilities.copyAs(self, new File(destination, self.getName())); + } + + public static File relativize(File self, File other) throws IOException { + return self.getCanonicalFile().toPath().relativize(other.getCanonicalFile().toPath()).toFile(); + } + + public static Map> mapByFolder(Collection files) { + return FileUtilities.mapByFolder(FileUtilities.asFileList(files)); + } + + public static Map> mapByExtension(Collection files) { + return FileUtilities.mapByExtension(FileUtilities.asFileList(files)); + } + + public static String normalizePunctuation(String self) { + return Normalization.normalizePunctuation(self); + } + + // Web and File IO helpers + + public static ByteBuffer fetch(URL self) throws IOException { + return WebRequest.fetch(self); + } + + public static String getText(ByteBuffer self) { + return Charset.forName("UTF-8").decode(self.duplicate()).toString(); + } + + public static ByteBuffer post(URL self, Map parameters, Map requestParameters) throws IOException { + return WebRequest.post(self, parameters, requestParameters); + } + + public static ByteBuffer post(URL self, String text, Map requestParameters) throws IOException { + return WebRequest.post(self, text.getBytes("UTF-8"), "text/plain", requestParameters); + } + + public static File saveAs(ByteBuffer self, File file) throws IOException { + // resolve relative paths + file = file.getAbsoluteFile(); + + // make sure parent folders exist + file.getParentFile().mkdirs(); + + return FileUtilities.writeFile(self, file); + } + + public static File saveAs(String self, File file) throws IOException { + return saveAs(Charset.forName("UTF-8").encode(self), file); + } + + public static File saveAs(URL self, File file) throws IOException { + // resolve relative paths + file = file.getAbsoluteFile(); + + // make sure parent folders exist + file.getParentFile().mkdirs(); + + org.apache.commons.io.FileUtils.copyURLToFile(self, file); + return file; + } + + public static String objectToJson(Object self) throws IOException { + return JsonWriter.objectToJson(self); + } + + public static Object jsonToObject(String self) throws IOException { + return JsonReader.jsonToJava(self); + } + + public static FolderWatchService watch(File self, final Closure callback) throws IOException { + FolderWatchService watchService = new FolderWatchService(true) { + + @Override + public void processCommitSet(File[] files, File dir) { + callback.call(asList(files)); + } + }; + + // collect updates for 500 ms and then batch process + watchService.setCommitDelay(500); + watchService.setCommitPerFolder(true); + + // start watching the given folder + watchService.watchFolder(self); + + return watchService; + } + + public static float getSimilarity(String self, String other) { + return new NameSimilarityMetric().getSimilarity(self, other); + } + + public static Collection getSimilarity(Collection self, final Object prime, final Closure toStringFunction) { + final SimilarityMetric metric = new NameSimilarityMetric(); + List values = new ArrayList(self); + Collections.sort(values, new Comparator() { + + @Override + public int compare(Object o1, Object o2) { + String s1 = toStringFunction != null ? toStringFunction.call(o1) : o1.toString(); + String s2 = toStringFunction != null ? toStringFunction.call(o2) : o2.toString(); + + return Float.compare(metric.getSimilarity(s2, prime), metric.getSimilarity(s1, prime)); + } + }); + return values; + } + + public static MetaAttributeView getXattr(File self) { + try { + return new MetaAttributeView(self); + } catch (Exception e) { + return null; + } + } + + public static Object getMetadata(File self) { + try { + return new MetaAttributes(self); + } catch (Exception e) { + return null; + } + } } diff --git a/source/net/sourceforge/filebot/util/FileUtilities.java b/source/net/sourceforge/filebot/util/FileUtilities.java index c89cbd11..8e4b54aa 100644 --- a/source/net/sourceforge/filebot/util/FileUtilities.java +++ b/source/net/sourceforge/filebot/util/FileUtilities.java @@ -158,14 +158,11 @@ public final class FileUtilities { return text.toString(); } - public static void writeFile(ByteBuffer data, File destination) throws IOException { - FileChannel fileChannel = new FileOutputStream(destination).getChannel(); - - try { - fileChannel.write(data); - } finally { - fileChannel.close(); + public static File writeFile(ByteBuffer data, File destination) throws IOException { + try (FileOutputStream stream = new FileOutputStream(destination); FileChannel channel = stream.getChannel()) { + channel.write(data); } + return destination; } public static List readCSV(InputStream source, String charsetName, String separatorPattern) { @@ -561,6 +558,8 @@ public final class FileUtilities { files.add((File) it); } else if (it instanceof Path) { files.add(((Path) it).toFile()); + } else if (it instanceof Collection) { + files.addAll(asFileList(it)); // flatten object structure } } return files;