+ support extracting archives (zip, rar, 7z, ...)

* added 7-Zip-JBinding libs and native dependencies
* added CLI option -extract and make it available in scripting environment
* allow --output to be used as output folder in -rename CLI call
This commit is contained in:
Reinhard Pointner 2012-02-26 12:58:16 +00:00
parent 374206480e
commit cfccf90c72
26 changed files with 561 additions and 59 deletions

View File

@ -6,7 +6,9 @@
<property name="title" value="${application.name}" />
<property name="version" value="${application.version}" />
<tstamp><format property="today" pattern="yyyy-MM-dd" /></tstamp>
<tstamp>
<format property="today" pattern="yyyy-MM-dd" />
</tstamp>
<!-- define source dirs -->
<property name="dir.source" location="${basedir}/source" />
@ -66,64 +68,64 @@
<include name="org/apache/**" />
<include name="org/w3c/dom/html/**" />
</zipfileset>
<zipfileset src="${dir.lib}/nekohtml.jar">
<include name="org/cyberneko/html/**" />
</zipfileset>
<zipfileset src="${dir.lib}/json-simple.jar">
<include name="org/json/simple/**" />
</zipfileset>
<zipfileset src="${dir.lib}/simmetrics.jar">
<include name="uk/ac/shef/wit/simmetrics/**" />
</zipfileset>
<zipfileset src="${dir.lib}/glazedlists.jar">
<include name="ca/odell/glazedlists/**" />
</zipfileset>
<zipfileset src="${dir.lib}/miglayout.jar">
<include name="net/miginfocom/**" />
</zipfileset>
<zipfileset src="${dir.lib}/xmlrpc.jar">
<include name="redstone/xmlrpc/**" />
</zipfileset>
<zipfileset src="${dir.lib}/args4j.jar">
<include name="org/kohsuke/args4j/**" />
</zipfileset>
<zipfileset src="${dir.lib}/ehcache.jar">
<include name="net/sf/ehcache/**" />
<include name="ehcache-failsafe.xml" />
<include name="ehcache-version.properties" />
</zipfileset>
<zipfileset src="${dir.lib}/slf4j.jar">
<include name="org/slf4j/**" />
</zipfileset>
<zipfileset src="${dir.lib}/slf4j-jdk.jar">
<include name="org/slf4j/**" />
</zipfileset>
<zipfileset src="${dir.lib}/commons-io.jar">
<include name="org/apache/commons/io/**" />
</zipfileset>
<zipfileset src="${dir.lib}/jna.jar">
<!-- include classes and native libraries -->
<include name="com/sun/jna/**" />
</zipfileset>
<zipfileset src="${dir.lib}/groovy.jar">
<include name="groovy*/**" />
<include name="org/codehaus/groovy/**" />
<include name="META-INF/dgminfo" />
</zipfileset>
<zipfileset src="${dir.lib}/icu4j.jar">
<include name="com/ibm/icu/**" />
</zipfileset>
@ -131,11 +133,11 @@
<zipfileset src="${dir.lib}/sublight-ws.jar">
<include name="net/sublight/webservice/**" />
</zipfileset>
<zipfileset src="${dir.lib}/junrar-custom.jar">
<include name="de/innosystec/unrar/**" />
</zipfileset>
<zipfileset src="${dir.lib}/jgat-custom.jar">
<include name="com/dmurph/tracking/**" />
</zipfileset>
@ -146,16 +148,16 @@
<target name="appbundle" depends="fatjar" description="Build an OSX application bundle">
<taskdef name="jarbundler" classname="net.sourceforge.jarbundler.JarBundler" classpath="${dir.lib}/build/jarbundler.jar" />
<copy tofile="${dir.dist}/appbundle/FileBot.jar" file="${path.fatjar}" />
<!-- build app bundle folder and add native libs -->
<jarbundler dir="${dir.dist}" name="${title}" version="${version}" build="${svn.revision}" icon="${dir.installer}/appbundle/icon.icns" bundleid="net.sourceforge.filebot" jar="${dir.dist}/appbundle/FileBot.jar" stubfile="${dir.installer}/appbundle/JavaApplicationStub" workingdirectory="$APP_PACKAGE/Contents/Resources/Java" mainclass="net.sourceforge.filebot.Main" jvmversion="1.6+" vmoptions="-Xmx256m">
<javaproperty name="application.deployment" value="app"/>
<javaproperty name="application.deployment" value="app" />
</jarbundler>
<!-- shell scripts -->
<copy todir="${dir.dist}/${title}.app/Contents/MacOS" file="${dir.installer}/appbundle/filebot" />
<copy todir="${dir.dist}/${title}.app/Contents/MacOS" file="${dir.installer}/appbundle/install.sh" />
<copy todir="${dir.dist}/${title}.app/Contents/Resources/Java">
<fileset dir="${dir.lib}/native/mac-x86_64" includes="*.dylib" />
</copy>
@ -163,14 +165,15 @@
<!-- application bundle folder as .tar.gz -->
<tar destfile="${path.appbundle.tar.gz}" compression="gzip">
<tarfileset dir="${dir.dist}" includes="${title}.app/**" excludes="**/MacOS/**" />
<tarfileset dir="${dir.dist}" includes="${title}.app/**/MacOS/**" filemode="755" /> <!-- IMPORTANT application stub must be executable!! -->
<tarfileset dir="${dir.dist}" includes="${title}.app/**/MacOS/**" filemode="755" />
<!-- IMPORTANT application stub must be executable!! -->
</tar>
</target>
<target name="deb" depends="fatjar" description="Build debian package for i386 and amd64">
<taskdef resource="ant_deb_task.properties" classpath="${dir.lib}/build/ant-deb.jar" />
<antcall target="deb-arch">
<param name="arch" value="i386" />
</antcall>
@ -204,31 +207,33 @@
<target name="msi-arch">
<property name="mediainfo" location="${dir.lib}/native/win32-${arch}/MediaInfo.dll" />
<property name="lib7z_binding" location="${dir.lib}/native/win32-${arch}/lib7z-JBinding.dll" />
<property name="lib7z_gcc" location="${dir.lib}/native/win32-${arch}/lib7z-gcc.dll" />
<property name="installer" location="${dir.dist}/FileBot_${version}_${arch}.msi" />
<exec executable="candle.exe" dir="${dir.installer}/msi" failonerror="true">
<arg line="filebot-wix.xml -out ${dir.dist}/msi.wixobj -arch ${arch} -dreleaseversion=${version} -dfatjar=${path.fatjar} -dmediainfo=${mediainfo}" />
<arg line="filebot-wix.xml -out ${dir.dist}/msi.wixobj -arch ${arch} -dreleaseversion=${version} -dfatjar=${path.fatjar} -dmediainfo=${mediainfo} -dlib7z_binding=${lib7z_binding} -dlib7z_gcc=${lib7z_gcc}" />
</exec>
<exec executable="light.exe" dir="${dir.installer}/msi" failonerror="true">
<arg line="${dir.dist}/msi.wixobj -sval -ext WixUIExtension -out ${installer}" />
</exec>
</target>
<target name="portable" depends="fatjar" description="Portable application package">
<mkdir dir="${dir.dist}/portable"/>
<mkdir dir="${dir.dist}/portable" />
<copy file="${path.fatjar}" tofile="${dir.dist}/portable/FileBot.jar" />
<copy todir="${dir.dist}/portable">
<fileset dir="${dir.installer}/portable" includes="*.exe, *.ini, *.sh" />
</copy>
<zip destfile="${dir.dist}/FileBot_${version}-portable.zip">
<zipfileset dir="${dir.dist}/portable" includes="*.jar, *.exe, *.ini" />
<zipfileset dir="${dir.dist}/portable" includes="*.sh" filemode="755" />
</zip>
</target>
<target name="webstart" depends="jar" description="Build and compress jars used for webstart deployment">
<!-- create dirs -->
<mkdir dir="${dir.dist}/webstart" />
@ -248,7 +253,7 @@
<jar destfile="${dir.dist}/webstart/jna.jar">
<zipfileset src="${dir.lib}/jna.jar" includes="**/*.class" />
</jar>
<!-- create mediainfo jar as seperate jar and use as trigger for lazy loading the native libs -->
<jar destfile="${dir.dist}/webstart/mediainfo.jar">
<fileset dir="${dir.build}" includes="net/sourceforge/filebot/mediainfo/**" />
@ -292,7 +297,7 @@
<signjar alias="filebot" keystore="filebot.keystore" storepass="secret">
<fileset id="signjar" dir="${dir.dist}/webstart" includes="**/*.jar" />
</signjar>
<!-- pack200 all jars -->
<apply executable="pack200" dest="${dir.dist}/webstart">
<!-- workaround for bug 6575373, see http://bugs.sun.com/view_bug.do?bug_id=6575373 -->
@ -383,11 +388,17 @@
<target name="svn-update">
<typedef resource="org/tigris/subversion/svnant/svnantlib.xml">
<classpath><fileset dir="${dir.lib}/build" includes="*.jar" /></classpath>
<classpath>
<fileset dir="${dir.lib}/build" includes="*.jar" />
</classpath>
</typedef>
<svnSetting id="svn.settings" svnkit="true" javahl="false" />
<svn refid="svn.settings"><update dir="${dir.source}" revision="HEAD" recurse="false" /></svn>
<svn refid="svn.settings"><status path="${dir.source}" revisionProperty="svn.revision" /></svn>
<svn refid="svn.settings">
<update dir="${dir.source}" revision="HEAD" recurse="false" />
</svn>
<svn refid="svn.settings">
<status path="${dir.source}" revisionProperty="svn.revision" />
</svn>
<echo>Revision: ${svn.revision}</echo>
</target>
@ -419,7 +430,7 @@
<scp todir="${sf.user}:${sf.password}@${deploy.release}" trust="yes" verbose="true">
<fileset dir="${dir.dist}/release" includes="**/*.jar" />
</scp>
<!-- deploy portable application package -->
<sleep seconds="5" />
<scp todir="${sf.user}:${sf.password}@${deploy.release}" trust="yes" verbose="true">

View File

@ -1,2 +1,2 @@
#!/bin/bash
java -Dapplication.deployment=deb -Djna.library.path=/usr/share/filebot -Xmx256m -jar /usr/share/filebot/FileBot.jar "$@"
java -Dapplication.deployment=deb -Djna.library.path=/usr/share/filebot -Djava.library.path=/usr/share/filebot -Xmx256m -jar /usr/share/filebot/FileBot.jar "$@"

View File

@ -32,6 +32,8 @@
<Component Id='ApplicationBase' Guid='9E365344-A00C-45DE-A2A4-266412C3D06E'>
<File Id='FileBot.jar' Name='FileBot.jar' Source='$(var.fatjar)' KeyPath='yes' />
<File Id='MediaInfo.dll' Name='MediaInfo.dll' Source='$(var.mediainfo)' />
<File Id='lib7z_JBinding.dll' Name='7z-JBinding.dll' Source='$(var.lib7z_binding)' />
<File Id='lib7z_gcc.dll' Name='7z-gcc.dll' Source='$(var.lib7z_gcc)' />
</Component>
</Directory>
</Directory>

View File

@ -4,4 +4,4 @@ while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
dir_bin="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
# WARNING: NOT TESTED / HERE THERE BE DRAGONS
java -Dapplication.deployment=portable "-Dapplication.dir=$dir_bin" "-Duser.home=$dir_bin" "-Djna.library.path=$dir_bin" -Djava.util.prefs.PreferencesFactory=net.sourceforge.tuned.prefs.FilePreferencesFactory -Dnet.sourceforge.tuned.prefs.file=prefs.properties -Xmx256m -jar "$dir_app/FileBot.jar" "$@"
java -Dapplication.deployment=portable "-Dapplication.dir=$dir_bin" "-Duser.home=$dir_bin" "-Djna.library.path=$dir_bin" "-Djava.library.path=$dir_bin" -Djava.util.prefs.PreferencesFactory=net.sourceforge.tuned.prefs.FilePreferencesFactory -Dnet.sourceforge.tuned.prefs.file=prefs.properties -Xmx256m -jar "$dir_app/FileBot.jar" "$@"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
lib/sevenzipjbinding.jar Normal file

Binary file not shown.

View File

@ -0,0 +1,135 @@
package net.sourceforge.filebot.archive;
import static org.apache.commons.io.FilenameUtils.*;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import com.sun.jna.Platform;
import net.sf.sevenzipjbinding.ArchiveFormat;
import net.sf.sevenzipjbinding.IInStream;
import net.sf.sevenzipjbinding.ISevenZipInArchive;
import net.sf.sevenzipjbinding.PropID;
import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.SevenZipException;
public class Archive implements Closeable {
static {
// initialize 7z-JBinding native libs
try {
if (Platform.isWindows()) {
System.loadLibrary("lib7z-gcc");
}
System.loadLibrary("lib7z-JBinding");
SevenZip.initLoadedLibraries();
} catch (Throwable e) {
Logger.getLogger(Archive.class.getName()).warning("Failed to load 7z-JBinding");
}
}
private ISevenZipInArchive inArchive;
private Closeable openVolume;
public Archive(File file) throws SevenZipException, IOException {
if (!file.exists()) {
throw new FileNotFoundException(file.getAbsolutePath());
}
ArchiveOpenVolumeCallback openVolumeCallback = new ArchiveOpenVolumeCallback();
IInStream inStream = openVolumeCallback.getStream(file.getAbsolutePath());
inArchive = SevenZip.openInArchive(null, inStream, openVolumeCallback);
openVolume = openVolumeCallback;
}
public int itemCount() throws SevenZipException {
return inArchive.getNumberOfItems();
}
public Map<PropID, Object> getItem(int index) throws SevenZipException {
Map<PropID, Object> item = new EnumMap<PropID, Object>(PropID.class);
for (PropID prop : PropID.values()) {
Object value = inArchive.getProperty(index, prop);
if (value != null) {
item.put(prop, value);
}
}
return item;
}
public List<File> listFiles() throws SevenZipException {
List<File> paths = new ArrayList<File>();
for (int i = 0; i < inArchive.getNumberOfItems(); i++) {
boolean isFolder = (Boolean) inArchive.getProperty(i, PropID.IS_FOLDER);
if (!isFolder) {
String path = (String) inArchive.getProperty(i, PropID.PATH);
paths.add(new File(path));
}
}
return paths;
}
public void extract(ExtractOutProvider outputMapper) throws SevenZipException {
inArchive.extract(null, false, new ExtractCallback(inArchive, outputMapper));
}
@Override
public void close() throws IOException {
try {
inArchive.close();
} catch (SevenZipException e) {
throw new IOException(e);
} finally {
openVolume.close();
}
}
public static final FileFilter VOLUME_ONE_FILTER = new FileFilter() {
private Pattern exclude = Pattern.compile("[.]r[0-9]+$|[.]part[0-9]*[2-9][.]rar$|[.][0-9]*[2-9]$", Pattern.CASE_INSENSITIVE);
@Override
public boolean accept(File path) {
if (exclude.matcher(path.getName()).find()) {
return false;
}
String ext = getExtension(path.getName());
for (ArchiveFormat it : ArchiveFormat.values()) {
if (it.getMethodName().equalsIgnoreCase(ext)) {
return true;
}
}
return false;
}
};
}

View File

@ -0,0 +1,109 @@
package net.sourceforge.filebot.archive;
import java.io.*;
import java.util.*;
import net.sf.sevenzipjbinding.*;
import net.sf.sevenzipjbinding.impl.*;
class ArchiveOpenVolumeCallback implements IArchiveOpenVolumeCallback, IArchiveOpenCallback, Closeable {
/**
* Cache for opened file streams
*/
private Map<String, RandomAccessFile> openedRandomAccessFileList = new HashMap<String, RandomAccessFile>();
/**
* Name of the last volume returned by {@link #getStream(String)}
*/
private String name;
/**
* This method should at least provide the name of the last
* opened volume (propID=PropID.NAME).
*
* @see IArchiveOpenVolumeCallback#getProperty(PropID)
*/
public Object getProperty(PropID propID) throws SevenZipException {
switch (propID) {
case NAME:
return name;
}
return null;
}
/**
* The name of the required volume will be calculated out of the
* name of the first volume and a volume index. In case of RAR file,
* the substring ".partNN." in the name of the volume file will
* indicate a volume with id NN. For example:
* <ul>
* <li>test.rar - single part archive or multi-part archive with a single volume</li>
* <li>test.part23.rar - 23-th part of a multi-part archive</li>
* <li>test.part001.rar - first part of a multi-part archive. "00" indicates, that at least 100 volumes must exist.</li>
* </ul>
*/
public IInStream getStream(String filename) throws SevenZipException {
try {
// We use caching of opened streams, so check cache first
RandomAccessFile randomAccessFile = openedRandomAccessFileList.get(filename);
if (randomAccessFile != null) { // Cache hit.
// Move the file pointer back to the beginning
// in order to emulating new stream
randomAccessFile.seek(0);
// Save current volume name in case getProperty() will be called
name = filename;
return new RandomAccessFileInStream(randomAccessFile);
}
// Nothing useful in cache. Open required volume.
randomAccessFile = new RandomAccessFile(filename, "r");
// Put new stream in the cache
openedRandomAccessFileList.put(filename, randomAccessFile);
// Save current volume name in case getProperty() will be called
name = filename;
return new RandomAccessFileInStream(randomAccessFile);
} catch (FileNotFoundException fileNotFoundException) {
// Required volume doesn't exist. This happens if the volume:
// 1. never exists. 7-Zip doesn't know how many volumes should
// exist, so it have to try each volume.
// 2. should be there, but doesn't. This is an error case.
// Since normal and error cases are possible,
// we can't throw an error message
return null; // We return always null in this case
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Close all opened streams
*/
public void close() throws IOException {
for (RandomAccessFile file : openedRandomAccessFileList.values()) {
file.close();
}
}
@Override
public void setCompleted(Long files, Long bytes) throws SevenZipException {
}
@Override
public void setTotal(Long files, Long bytes) throws SevenZipException {
}
}

View File

@ -0,0 +1,85 @@
package net.sourceforge.filebot.archive;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import net.sf.sevenzipjbinding.ExtractAskMode;
import net.sf.sevenzipjbinding.ExtractOperationResult;
import net.sf.sevenzipjbinding.IArchiveExtractCallback;
import net.sf.sevenzipjbinding.ISequentialOutStream;
import net.sf.sevenzipjbinding.ISevenZipInArchive;
import net.sf.sevenzipjbinding.PropID;
import net.sf.sevenzipjbinding.SevenZipException;
class ExtractCallback implements IArchiveExtractCallback {
private ISevenZipInArchive inArchive;
private ExtractOutProvider extractOut;
private ExtractOutStream output = null;
public ExtractCallback(ISevenZipInArchive inArchive, ExtractOutProvider extractOut) {
this.inArchive = inArchive;
this.extractOut = extractOut;
}
public ISequentialOutStream getStream(int index, ExtractAskMode extractAskMode) throws SevenZipException {
if (extractAskMode != ExtractAskMode.EXTRACT) {
return null;
}
boolean isFolder = (Boolean) inArchive.getProperty(index, PropID.IS_FOLDER);
if (isFolder) {
return null;
}
String path = (String) inArchive.getProperty(index, PropID.PATH);
try {
OutputStream target = extractOut.getStream(new File(path));
if (target == null) {
return null;
}
output = new ExtractOutStream(target);
return output;
} catch (IOException e) {
throw new SevenZipException(e);
}
}
public void prepareOperation(ExtractAskMode extractAskMode) throws SevenZipException {
}
public void setOperationResult(ExtractOperationResult extractOperationResult) throws SevenZipException {
if (output != null) {
try {
output.close();
} catch (IOException e) {
throw new SevenZipException(e);
} finally {
output = null;
}
}
if (extractOperationResult != ExtractOperationResult.OK) {
throw new SevenZipException("Extraction Error: " + extractOperationResult);
}
}
public void setCompleted(long completeValue) throws SevenZipException {
}
public void setTotal(long total) throws SevenZipException {
}
}

View File

@ -0,0 +1,12 @@
package net.sourceforge.filebot.archive;
import java.io.*;
public interface ExtractOutProvider {
OutputStream getStream(File archivePath) throws IOException;
}

View File

@ -0,0 +1,36 @@
package net.sourceforge.filebot.archive;
import java.io.*;
import net.sf.sevenzipjbinding.*;
class ExtractOutStream implements ISequentialOutStream, Closeable {
private OutputStream out;
public ExtractOutStream(OutputStream out) {
this.out = out;
}
@Override
public int write(byte[] data) throws SevenZipException {
try {
out.write(data);
} catch (IOException e) {
throw new SevenZipException(e);
}
return data.length; // return amount of proceed data
}
@Override
public void close() throws IOException {
out.close();
}
}

View File

@ -0,0 +1,40 @@
package net.sourceforge.filebot.archive;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class FileMapper implements ExtractOutProvider {
private File outputDir;
private boolean flatten;
public FileMapper(File outputDir, boolean flatten) {
this.outputDir = outputDir;
this.flatten = flatten;
};
public File getOutputFile(File entry) {
return new File(outputDir, flatten ? entry.getName() : entry.getPath());
}
@Override
public OutputStream getStream(File entry) throws IOException {
File outputFile = getOutputFile(entry);
File outputFolder = outputFile.getParentFile();
// create parent folder if necessary
if (!outputFolder.isDirectory() && !outputFolder.mkdirs()) {
throw new IOException("Failed to create folder: " + outputFolder);
}
return new FileOutputStream(outputFile);
}
}

View File

@ -52,10 +52,10 @@ public class ArgumentBean {
@Option(name = "-check", usage = "Create/Check verification file", metaVar = "fileset")
public boolean check;
@Option(name = "--output", usage = "Output options", metaVar = "[sfv, md5, sha1] or [srt]")
@Option(name = "--output", usage = "Output path / format", metaVar = "Output options")
public String output;
@Option(name = "--encoding", usage = "Character encoding", metaVar = "[UTF-8, windows-1252, GB18030, etc]")
@Option(name = "--encoding", usage = "Output character encoding", metaVar = "[UTF-8, windows-1252, GB18030, etc]")
public String encoding;
@Option(name = "-list", usage = "Fetch episode list")
@ -64,6 +64,9 @@ public class ArgumentBean {
@Option(name = "-mediainfo", usage = "Get media info")
public boolean mediaInfo = false;
@Option(name = "-extract", usage = "Extract archives")
public boolean extract = false;
@Option(name = "-script", usage = "Run Groovy script", metaVar = "robot.groovy")
public String script = null;
@ -93,7 +96,7 @@ public class ArgumentBean {
public boolean runCLI() {
return rename || getSubtitles || getMissingSubtitles || check || list || mediaInfo || script != null;
return rename || getSubtitles || getMissingSubtitles || check || list || mediaInfo || extract || script != null;
}

View File

@ -8,8 +8,8 @@ import static net.sourceforge.tuned.FileUtilities.*;
import java.io.File;
import java.security.AccessController;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.logging.Level;
import javax.script.Bindings;
@ -60,7 +60,11 @@ public class ArgumentProcessor {
// execute CLI operations
if (args.script == null) {
// file operations
Set<File> files = new LinkedHashSet<File>(args.getFiles(true));
Collection<File> files = new LinkedHashSet<File>(args.getFiles(true));
if (args.extract) {
files.addAll(cli.extract(files, args.output));
}
if (args.getSubtitles) {
files.addAll(cli.getSubtitles(files, args.query, args.lang, args.output, args.encoding, !args.nonStrict));
@ -69,7 +73,7 @@ public class ArgumentProcessor {
}
if (args.rename) {
cli.rename(files, args.query, args.format, args.db, args.order, args.lang, !args.nonStrict);
cli.rename(files, args.query, args.output, args.format, args.db, args.order, args.lang, !args.nonStrict);
}
if (args.check) {

View File

@ -9,7 +9,7 @@ import java.util.List;
public interface CmdlineInterface {
List<File> rename(Collection<File> files, String query, String format, String db, String sortOrder, String lang, boolean strict) throws Exception;
List<File> rename(Collection<File> files, String query, String output, String format, String db, String sortOrder, String lang, boolean strict) throws Exception;
List<File> getSubtitles(Collection<File> files, String query, String lang, String output, String encoding, boolean strict) throws Exception;
@ -29,4 +29,7 @@ public interface CmdlineInterface {
String getMediaInfo(File file, String format) throws Exception;
List<File> extract(Collection<File> files, String output) throws Exception;
}

View File

@ -42,6 +42,8 @@ import java.util.concurrent.Future;
import net.sourceforge.filebot.Analytics;
import net.sourceforge.filebot.MediaTypes;
import net.sourceforge.filebot.WebServices;
import net.sourceforge.filebot.archive.Archive;
import net.sourceforge.filebot.archive.FileMapper;
import net.sourceforge.filebot.format.ExpressionFormat;
import net.sourceforge.filebot.format.MediaBindingBean;
import net.sourceforge.filebot.hash.HashType;
@ -78,9 +80,10 @@ import net.sourceforge.tuned.FileUtilities.FolderFilter;
public class CmdlineOperations implements CmdlineInterface {
@Override
public List<File> rename(Collection<File> files, String query, String expression, String db, String sortOrder, String languageName, boolean strict) throws Exception {
public List<File> rename(Collection<File> files, String query, String output, String expression, String db, String sortOrder, String languageName, boolean strict) throws Exception {
ExpressionFormat format = (expression != null) ? new ExpressionFormat(expression) : null;
Locale locale = getLanguage(languageName).toLocale();
File outputDir = (output != null && output.length() > 0) ? new File(output) : null;
List<File> mediaFiles = filter(files, VIDEO_FILES, SUBTITLE_FILES);
if (mediaFiles.isEmpty()) {
@ -89,12 +92,12 @@ public class CmdlineOperations implements CmdlineInterface {
if (getEpisodeListProvider(db) != null) {
// tv series mode
return renameSeries(files, query, format, getEpisodeListProvider(db), SortOrder.forName(sortOrder), locale, strict);
return renameSeries(files, query, outputDir, format, getEpisodeListProvider(db), SortOrder.forName(sortOrder), locale, strict);
}
if (getMovieIdentificationService(db) != null) {
// movie mode
return renameMovie(files, query, format, getMovieIdentificationService(db), locale, strict);
return renameMovie(files, query, outputDir, format, getMovieIdentificationService(db), locale, strict);
}
// auto-determine mode
@ -125,14 +128,14 @@ public class CmdlineOperations implements CmdlineInterface {
CLILogger.finest(format("Filename pattern: [%.02f] SxE, [%.02f] CWS", sxe / max, cws / max));
if (sxe >= (max * 0.65) || cws >= (max * 0.65)) {
return renameSeries(files, query, format, getEpisodeListProviders()[0], SortOrder.forName(sortOrder), locale, strict); // use default episode db
return renameSeries(files, query, outputDir, format, getEpisodeListProviders()[0], SortOrder.forName(sortOrder), locale, strict); // use default episode db
} else {
return renameMovie(files, query, format, getMovieIdentificationServices()[0], locale, strict); // use default movie db
return renameMovie(files, query, outputDir, format, getMovieIdentificationServices()[0], locale, strict); // use default movie db
}
}
public List<File> renameSeries(Collection<File> files, String query, ExpressionFormat format, EpisodeListProvider db, SortOrder sortOrder, Locale locale, boolean strict) throws Exception {
public List<File> renameSeries(Collection<File> files, String query, File outputDir, ExpressionFormat format, EpisodeListProvider db, SortOrder sortOrder, Locale locale, boolean strict) throws Exception {
CLILogger.config(format("Rename episodes using [%s]", db.getName()));
List<File> mediaFiles = filter(files, VIDEO_FILES, SUBTITLE_FILES);
@ -179,7 +182,7 @@ public class CmdlineOperations implements CmdlineInterface {
File file = match.getValue();
Episode episode = match.getCandidate();
String newName = (format != null) ? format.format(new MediaBindingBean(episode, file)) : validateFileName(EpisodeFormat.SeasonEpisode.format(episode));
File newFile = new File(newName + "." + getExtension(file));
File newFile = new File(outputDir, newName + "." + getExtension(file));
if (isInvalidFilePath(newFile)) {
CLILogger.config("Stripping invalid characters from new name: " + newName);
@ -264,7 +267,7 @@ public class CmdlineOperations implements CmdlineInterface {
}
public List<File> renameMovie(Collection<File> files, String query, ExpressionFormat format, MovieIdentificationService service, Locale locale, boolean strict) throws Exception {
public List<File> renameMovie(Collection<File> files, String query, File outputDir, ExpressionFormat format, MovieIdentificationService service, Locale locale, boolean strict) throws Exception {
CLILogger.config(format("Rename movies using [%s]", service.getName()));
// handle movie files
@ -910,4 +913,30 @@ public class CmdlineOperations implements CmdlineInterface {
return format.format(new MediaBindingBean(file, file));
}
@Override
public List<File> extract(Collection<File> files, String output) throws Exception {
// only keep single-volume archives or first part of multi-volume archives
List<File> archiveFiles = filter(files, Archive.VOLUME_ONE_FILTER);
List<File> extractedFiles = new ArrayList<File>();
for (File file : archiveFiles) {
Archive archive = new Archive(file);
File outputFolder = (output != null) ? new File(output).getAbsoluteFile() : new File(file.getParentFile(), getNameWithoutExtension(file.getName()));
CLILogger.info(String.format("Extract archive [%s] to [%s]", file.getName(), outputFolder));
FileMapper outputMapper = new FileMapper(outputFolder, false);
List<File> entries = archive.listFiles();
for (File entry : entries) {
extractedFiles.add(outputMapper.getOutputFile(entry));
}
CLILogger.finest("Extract files " + entries);
archive.extract(outputMapper);
}
return extractedFiles;
}
}

View File

@ -61,11 +61,13 @@ import java.nio.ByteBuffer
import java.nio.charset.Charset
import static net.sourceforge.filebot.web.WebRequest.*
URL.metaClass.get = { readAll(getReader(delegate.openConnection())) }
URL.metaClass.getText = { readAll(getReader(delegate.openConnection())) }
URL.metaClass.getHtml = { new XmlParser(new org.cyberneko.html.parsers.SAXParser()).parseText(delegate.getText()) }
URL.metaClass.fetch = { fetch(delegate) }
URL.metaClass.getHtml = { new XmlParser(new org.cyberneko.html.parsers.SAXParser()).parseText(readAll(getReader(delegate.openConnection()))) }
ByteBuffer.metaClass.getHtml = { csn = "utf-8" -> new XmlParser(new org.cyberneko.html.parsers.SAXParser()).parseText(Charset.forName(csn).decode(delegate.duplicate()).toString()) }
ByteBuffer.metaClass.getText = { csn = "utf-8" -> Charset.forName(csn).decode(delegate.duplicate()).toString() }
ByteBuffer.metaClass.getHtml = { csn = "utf-8" -> new XmlParser(new org.cyberneko.html.parsers.SAXParser()).parseText(delegate.getText(csn)) }
URL.metaClass.get = { delegate.getText() }
URL.metaClass.post = { Map parameters -> post(delegate.openConnection(), parameters) }
URL.metaClass.post = { byte[] data, contentType = 'application/octet-stream' -> post(delegate.openConnection(), data, contentType) }
URL.metaClass.post = { String text, csn = 'utf-8' -> delegate.post(text.getBytes(csn), 'text/plain') }
@ -212,6 +214,12 @@ def compute(args) { args = _defaults(args)
}
}
def extract(args) { args = _defaults(args)
synchronized (_cli) {
_guarded { _cli.extract(_files(args), args.output) }
}
}
def fetchEpisodeList(args) { args = _defaults(args)
synchronized (_cli) {
_guarded { _cli.fetchEpisodeList(args.query, args.format, args.db, args.order, args.lang) }

View File

@ -30,7 +30,10 @@
<extension>zip</extension>
</type>
<type name="archive/rar">
<extension>rar</extension>
<extension>rar</extension>
</type>
<type name="archive/7z">
<extension>7z</extension>
</type>
<type name="archive/gzip">
<extension>gzip</extension>
@ -39,6 +42,9 @@
<type name="archive/tar">
<extension>tar</extension>
</type>
<type name="archive/bzip2">
<extension>bzip2</extension>
</type>
<!-- Audio -->
<type name="audio/mp3">
@ -119,7 +125,7 @@
<extension>sami</extension>
</type>
<type name="subtitle/VobSub">
<extension>sub</extension>
<extension>sub</extension>
<extension>idx</extension>
</type>
</media-types>

View File

@ -133,6 +133,10 @@
</p>
<h3 id="scripting">Scripting</h3>
<p>
<code><span class="cmd">filebot</span> <span class="option">-extract</span> <span class="argument">"300.part01.rar"</span> <span class="option">--output</span> <span class="argument">path/to/folder</span></code></code>
Extract files from single-volume or multi-volume archives (e.g. rar).
</p>
<p>
<code><span class="cmd">filebot</span> <span class="option">-list</span> <span class="option">--db</span> <span class="argument">thetvdb</span> <span class="option">--q</span> <span class="argument">Dexter</span> <span class="option">--format</span> <span class="argument">"{s}x{e.pad(2)} {t}"</span></code>
Fetch episode list and print to console.
</p>
@ -223,6 +227,11 @@
<td>create/check verification file</td>
<td>folder or sfv file</td>
</tr>
<tr>
<td>-extract</td>
<td>extract archives</td>
<td>folder or archive file</td>
</tr>
<tr>
<td>--output</td>
<td>output format and/or path</td>

View File

@ -129,13 +129,14 @@
<div class="documentation">
<h4>Rename media files</h4>
<pre><span class="return">File[]</span> <span class="method">rename</span>(<span class="property">folder</span>|<span class="property">file</span>, <span class="property">query</span>, <span class="property">format</span>, <span class="property">db</span>, <span class="property">lang</span>, <span class="property">strict</span>)</pre>
<pre><span class="return">File[]</span> <span class="method">rename</span>(<span class="property">folder</span>|<span class="property">file</span>, <span class="property">query</span>, <span class="property">output</span>, <span class="property">format</span>, <span class="property">db</span>, <span class="property">lang</span>, <span class="property">strict</span>)</pre>
<div class="text">Match files with episode/movie data and rename according to given naming scheme.</div>
<dl>
<dt>Parameters:</dt>
<dd><span class="property">folder</span> - process media files in this folder</dd>
<dd><span class="property">file</span> - process these media files</dd>
<dd><span class="property">query</span> - force series/movie name, auto-detect if not set</dd>
<dd><span class="property">output</span> - output folder, if format expression is not absolute</dd>
<dd><span class="property">format</span> - episode/movie naming scheme</dd>
<dd><span class="property">db</span> - episode/movie database</dd>
<dd><span class="property">lang</span> - preferred language for episode/movie titles</dd>
@ -186,6 +187,15 @@
<div class="documentation">
<h4>Other</h4>
<pre><span class="return">File[]</span> <span class="method">extract</span>(<span class="property">folder</span>|<span class="property">file</span>, <span class="property">output</span>)</pre>
<div class="text">Extract files from single-volume or multi-volume archives (.zip, .rar, .7z, etc).</div>
<dl>
<dt>Parameters:</dt>
<dd><span class="property">folder</span> - extract all archives that are in this folder</dd>
<dd><span class="property">file</span> - extract this archive</dd>
<dd><span class="property">output</span> - output folder, defaults to archive path</dd>
</dl>
<hr/>
<pre><span class="return">String[]</span> <span class="method">fetchEpisodeList</span>(<span class="property">query</span>, <span class="property">format</span>, <span class="property">db</span>, <span class="property">lang</span>)</pre>
<div class="text">Fetch episode data for the given tv show and format episode names.</div>
<dl>