+ 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:
parent
374206480e
commit
cfccf90c72
83
build.xml
83
build.xml
|
@ -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">
|
||||
|
|
|
@ -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 "$@"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
Binary file not shown.
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
package net.sourceforge.filebot.archive;
|
||||
|
||||
|
||||
import java.io.*;
|
||||
|
||||
|
||||
public interface ExtractOutProvider {
|
||||
|
||||
OutputStream getStream(File archivePath) throws IOException;
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue