* save metadata as extended file attributes for each file
* set file creation date to episode/movie release date
This commit is contained in:
parent
0b4b353912
commit
2d3b6cf3a4
|
@ -80,6 +80,10 @@
|
|||
<include name="org/json/simple/**" />
|
||||
</zipfileset>
|
||||
|
||||
<zipfileset src="${dir.lib}/json-io.jar">
|
||||
<include name="com/cedarsoftware/util/io/**" />
|
||||
</zipfileset>
|
||||
|
||||
<zipfileset src="${dir.lib}/simmetrics.jar">
|
||||
<include name="uk/ac/shef/wit/simmetrics/**" />
|
||||
</zipfileset>
|
||||
|
@ -203,6 +207,8 @@
|
|||
<!-- 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="unixfs" value="false" />
|
||||
<javaproperty name="useExtendedFileAttributes" value="true" />
|
||||
<javaproperty name="sun.net.client.defaultConnectTimeout" value="10000" />
|
||||
<javaproperty name="sun.net.client.defaultReadTimeout" value="60000" />
|
||||
</jarbundler>
|
||||
|
|
|
@ -4,4 +4,4 @@ while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
|
|||
dir_bin="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
|
||||
dir_app=$dir_bin/../Resources/Java
|
||||
java -Dunixfs=false -Djava.awt.headless=true -Xmx256m -Dapplication.deployment=app "-Djna.library.path=$dir_app" "-Djava.library.path=$dir_app" -Dsun.net.client.defaultConnectTimeout=5000 -Dsun.net.client.defaultReadTimeout=25000 -jar "$dir_app/FileBot.jar" "$@"
|
||||
java -Xmx256m -Djava.awt.headless=true -Dunixfs=false -DuseExtendedFileAttributes=true -Dapplication.deployment=app "-Djna.library.path=$dir_app" "-Djava.library.path=$dir_app" -Dsun.net.client.defaultConnectTimeout=5000 -Dsun.net.client.defaultReadTimeout=25000 -jar "$dir_app/FileBot.jar" "$@"
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#!/bin/bash
|
||||
java -Xmx256m -Dapplication.deployment=deb -Dapplication.dir=$HOME/.filebot -Djava.io.tmpdir=$HOME/.filebot/temp -Djna.library.path=/usr/share/filebot -Djava.library.path=/usr/share/filebot -Dsun.net.client.defaultConnectTimeout=10000 -Dsun.net.client.defaultReadTimeout=60000 -jar /usr/share/filebot/FileBot.jar "$@"
|
||||
java -Xmx256m -Dunixfs=false -DuseExtendedFileAttributes=true -Dapplication.deployment=deb -Dapplication.dir=$HOME/.filebot -Djava.io.tmpdir=$HOME/.filebot/temp -Djna.library.path=/usr/share/filebot -Djava.library.path=/usr/share/filebot -Dsun.net.client.defaultConnectTimeout=10000 -Dsun.net.client.defaultReadTimeout=60000 -jar /usr/share/filebot/FileBot.jar "$@"
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#!/bin/sh
|
||||
java -Dapplication.deployment=ipkg -Dapplication.dir=$HOME/.filebot -Djava.io.tmpdir=$HOME/.filebot/temp -Djna.library.path=/usr/share/filebot -Djava.library.path=/usr/share/filebot -Dsun.net.client.defaultConnectTimeout=10000 -Dsun.net.client.defaultReadTimeout=60000 -jar /usr/share/filebot/FileBot.jar "$@"
|
||||
java -Dunixfs=false -DuseExtendedFileAttributes=false -Dapplication.deployment=ipkg -Dapplication.dir=$HOME/.filebot -Djava.io.tmpdir=$HOME/.filebot/temp -Djna.library.path=/usr/share/filebot -Djava.library.path=/usr/share/filebot -Dsun.net.client.defaultConnectTimeout=10000 -Dsun.net.client.defaultReadTimeout=60000 -jar /usr/share/filebot/FileBot.jar "$@"
|
|
@ -1,2 +1,2 @@
|
|||
@ECHO OFF
|
||||
java -Dapplication.deployment=msi -Dapplication.dir="%APPDATA%\FileBot" -Dsun.net.client.defaultConnectTimeout=5000 -Dsun.net.client.defaultReadTimeout=25000 -Xmx256m -jar "%~dp0FileBot.jar" %*
|
||||
java -Xmx256m -DuseExtendedFileAttributes=true -Dapplication.deployment=msi -Dapplication.dir="%APPDATA%\FileBot" -Dsun.net.client.defaultConnectTimeout=5000 -Dsun.net.client.defaultReadTimeout=25000 -jar "%~dp0FileBot.jar" %*
|
||||
|
|
|
@ -12,6 +12,9 @@
|
|||
# use native shell for move/copy operations
|
||||
-DuseNativeShell=true
|
||||
|
||||
# use NTFS extended attributes for storing metadata
|
||||
-DuseExtendedFileAttributes=true
|
||||
|
||||
# memory settings
|
||||
-Xmx256m
|
||||
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
# do not use native shell for move/copy operations
|
||||
-DuseNativeShell=false
|
||||
|
||||
# use NTFS extended attributes for storing metadata
|
||||
-DuseExtendedFileAttributes=true
|
||||
|
||||
# memory settings
|
||||
-Xms64m
|
||||
-Xmx512m
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
# do not use native shell for move/copy operations
|
||||
-DuseNativeShell=false
|
||||
|
||||
# use NTFS extended attributes for storing metadata
|
||||
-DuseExtendedFileAttributes=true
|
||||
|
||||
# memory settings
|
||||
-Xmx256m
|
||||
|
||||
|
|
|
@ -3,4 +3,4 @@ SOURCE="${BASH_SOURCE[0]}"
|
|||
while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
|
||||
dir_bin="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
|
||||
java -Xmx256m -Dapplication.deployment=portable "-Dapplication.dir=$dir_bin" "-Djava.io.tmpdir=$dir_bin/temp" "-Duser.home=$dir_bin" "-Djna.library.path=$dir_bin" "-Djava.library.path=$dir_bin" -Dsun.net.client.defaultConnectTimeout=10000 -Dsun.net.client.defaultReadTimeout=60000 -Djava.util.prefs.PreferencesFactory=net.sourceforge.tuned.prefs.FilePreferencesFactory -Dnet.sourceforge.tuned.prefs.file=prefs.properties -jar "$dir_bin/FileBot.jar" "$@"
|
||||
java -Xmx256m -Dunixfs=false -DuseExtendedFileAttributes=true -Dapplication.deployment=portable "-Dapplication.dir=$dir_bin" "-Djava.io.tmpdir=$dir_bin/temp" "-Duser.home=$dir_bin" "-Djna.library.path=$dir_bin" "-Djava.library.path=$dir_bin" -Dsun.net.client.defaultConnectTimeout=10000 -Dsun.net.client.defaultReadTimeout=60000 -Djava.util.prefs.PreferencesFactory=net.sourceforge.tuned.prefs.FilePreferencesFactory -Dnet.sourceforge.tuned.prefs.file=prefs.properties -jar "$dir_bin/FileBot.jar" "$@"
|
|
@ -22,10 +22,12 @@
|
|||
<update check="background" policy="always" />
|
||||
|
||||
<resources>
|
||||
<property name="application.deployment" value="webstart" />
|
||||
<property name="application.update" value="skip" />
|
||||
<property name="useNativeShell" value="false" />
|
||||
|
||||
<property name="application.deployment" value="webstart" />
|
||||
<property name="application.update" value="skip" />
|
||||
<property name="useExtendedFileAttributes" value="true" />
|
||||
<property name="useNativeShell" value="false" />
|
||||
<property name="unixfs" value="false" />
|
||||
|
||||
<java version="1.6+" max-heap-size="256m" />
|
||||
<property name="jnlp.packEnabled" value="true" />
|
||||
|
||||
|
@ -45,6 +47,7 @@
|
|||
<jar href="xmlrpc.jar" download="eager" />
|
||||
<jar href="sublight-ws.jar" download="eager" />
|
||||
<jar href="json-simple.jar" download="lazy" />
|
||||
<jar href="json-io.jar" download="lazy" />
|
||||
<jar href="junrar-custom.jar" download="lazy" />
|
||||
<jar href="jacksum.jar" download="lazy" />
|
||||
<jar href="nekohtml.jar" download="lazy" part="scraper" />
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,117 @@
|
|||
|
||||
package net.sourceforge.filebot;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.attribute.UserDefinedFileAttributeView;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
public class MetaAttributeView extends AbstractMap<String, String> {
|
||||
|
||||
private final UserDefinedFileAttributeView attributeView;
|
||||
private final Charset encoding;
|
||||
|
||||
|
||||
public MetaAttributeView(File file) {
|
||||
attributeView = Files.getFileAttributeView(file.toPath(), UserDefinedFileAttributeView.class);
|
||||
encoding = Charset.forName("UTF-8");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String get(Object key) {
|
||||
try {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(attributeView.size(key.toString()));
|
||||
attributeView.read(key.toString(), buffer);
|
||||
buffer.flip();
|
||||
|
||||
return encoding.decode(buffer).toString();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String put(String key, String value) {
|
||||
try {
|
||||
if (value == null || value.isEmpty()) {
|
||||
attributeView.delete(key);
|
||||
} else {
|
||||
attributeView.write(key, encoding.encode(value));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return null; // since we don't know the old value
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Set<Entry<String, String>> entrySet() {
|
||||
try {
|
||||
Set<Entry<String, String>> entries = new LinkedHashSet<Entry<String, String>>();
|
||||
for (String name : attributeView.list()) {
|
||||
entries.add(new AttributeEntry(name));
|
||||
}
|
||||
return entries;
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
try {
|
||||
for (String it : attributeView.list()) {
|
||||
attributeView.delete(it);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class AttributeEntry implements Entry<String, String> {
|
||||
|
||||
private final String name;
|
||||
|
||||
|
||||
public AttributeEntry(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return get(name);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String setValue(String value) {
|
||||
return put(name, value);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getKey() + "=" + getValue();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -54,6 +54,11 @@ public final class Settings {
|
|||
}
|
||||
|
||||
|
||||
public static boolean useExtendedFileAttributes() {
|
||||
return Boolean.parseBoolean(System.getProperty("useExtendedFileAttributes"));
|
||||
}
|
||||
|
||||
|
||||
public static int getPreferredThreadPoolSize() {
|
||||
try {
|
||||
return Integer.parseInt(System.getProperty("threadPool"));
|
||||
|
|
|
@ -207,6 +207,15 @@ public class CmdlineOperations implements CmdlineInterface {
|
|||
Object episode = match.getCandidate();
|
||||
String newName = (format != null) ? format.format(new MediaBindingBean(episode, file)) : validateFileName(EpisodeFormat.SeasonEpisode.format(episode));
|
||||
|
||||
// first write all the metadata if xattr is enabled
|
||||
if (useExtendedFileAttributes()) {
|
||||
try {
|
||||
MediaDetection.storeMetaInfo(file, episode);
|
||||
} catch (Throwable e) {
|
||||
CLILogger.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
renameMap.put(file, getDestinationFile(file, newName, outputDir));
|
||||
}
|
||||
|
||||
|
@ -467,6 +476,15 @@ public class CmdlineOperations implements CmdlineInterface {
|
|||
Object movie = match.getCandidate();
|
||||
String newName = (format != null) ? format.format(new MediaBindingBean(movie, file)) : validateFileName(MovieFormat.NameYear.format(movie));
|
||||
|
||||
// first write all the metadata if xattr is enabled
|
||||
if (useExtendedFileAttributes()) {
|
||||
try {
|
||||
MediaDetection.storeMetaInfo(file, movie);
|
||||
} catch (Throwable e) {
|
||||
CLILogger.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
renameMap.put(file, getDestinationFile(file, newName, outputDir));
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ File.metaClass.validateFileName = { validateFileName(delegate) }
|
|||
File.metaClass.validateFilePath = { validateFilePath(delegate) }
|
||||
File.metaClass.moveTo = { f -> moveRename(delegate, f as File) }
|
||||
File.metaClass.copyTo = { dir -> copyAs(delegate, new File(dir, delegate.getName())) }
|
||||
File.metaClass.getXattr = { new net.sourceforge.filebot.MetaAttributeView(delegate) }
|
||||
List.metaClass.mapByFolder = { mapByFolder(delegate) }
|
||||
List.metaClass.mapByExtension = { mapByExtension(delegate) }
|
||||
String.metaClass.getExtension = { getExtension(delegate) }
|
||||
|
|
|
@ -5,6 +5,7 @@ package net.sourceforge.filebot.media;
|
|||
import static java.util.Collections.*;
|
||||
import static java.util.regex.Pattern.*;
|
||||
import static net.sourceforge.filebot.MediaTypes.*;
|
||||
import static net.sourceforge.filebot.Settings.*;
|
||||
import static net.sourceforge.filebot.similarity.CommonSequenceMatcher.*;
|
||||
import static net.sourceforge.filebot.similarity.Normalization.*;
|
||||
import static net.sourceforge.tuned.FileUtilities.*;
|
||||
|
@ -50,6 +51,7 @@ import net.sourceforge.filebot.similarity.SeriesNameMatcher;
|
|||
import net.sourceforge.filebot.similarity.SimilarityComparator;
|
||||
import net.sourceforge.filebot.similarity.SimilarityMetric;
|
||||
import net.sourceforge.filebot.web.Date;
|
||||
import net.sourceforge.filebot.web.Episode;
|
||||
import net.sourceforge.filebot.web.Movie;
|
||||
import net.sourceforge.filebot.web.MovieIdentificationService;
|
||||
import net.sourceforge.filebot.web.SearchResult;
|
||||
|
@ -233,6 +235,18 @@ public class MediaDetection {
|
|||
public static List<String> detectSeriesNames(Collection<File> files, Locale locale) throws Exception {
|
||||
List<String> names = new ArrayList<String>();
|
||||
|
||||
// try xattr metadata if enabled
|
||||
if (useExtendedFileAttributes()) {
|
||||
for (File it : files) {
|
||||
try {
|
||||
Episode episode = (Episode) new MetaAttributes(it).getMetaData();
|
||||
names.add(episode.getSeriesName());
|
||||
} catch (Throwable e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try to detect series name via nfo files
|
||||
try {
|
||||
for (SearchResult it : lookupSeriesNameByInfoFile(files, locale)) {
|
||||
|
@ -346,6 +360,18 @@ public class MediaDetection {
|
|||
public static Collection<Movie> detectMovie(File movieFile, MovieIdentificationService hashLookupService, MovieIdentificationService queryLookupService, Locale locale, boolean strict) throws Exception {
|
||||
Set<Movie> options = new LinkedHashSet<Movie>();
|
||||
|
||||
// try xattr metadata if enabled
|
||||
if (useExtendedFileAttributes()) {
|
||||
try {
|
||||
Movie movie = (Movie) new MetaAttributes(movieFile).getMetaData();
|
||||
if (movie != null) {
|
||||
options.add(movie);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
// lookup by file hash
|
||||
if (hashLookupService != null && movieFile.isFile()) {
|
||||
try {
|
||||
|
@ -742,4 +768,23 @@ public class MediaDetection {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public static void storeMetaInfo(File file, Object model) {
|
||||
// original filename
|
||||
MetaAttributes metadata = new MetaAttributes(file);
|
||||
metadata.putFileName(file.getName());
|
||||
|
||||
// store model as metadata
|
||||
if (model instanceof Episode || model instanceof Movie) {
|
||||
metadata.putMetaData(model);
|
||||
}
|
||||
|
||||
// set creation date to episode / movie release date
|
||||
if (model instanceof Episode) {
|
||||
metadata.setCreationDate(((Episode) model).airdate().getTimeStamp());
|
||||
} else if (model instanceof Movie) {
|
||||
metadata.setCreationDate(new Date(((Movie) model).getYear(), 1, 1).getTimeStamp());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
|
||||
package net.sourceforge.filebot.media;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.attribute.BasicFileAttributeView;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
|
||||
import net.sourceforge.filebot.MetaAttributeView;
|
||||
|
||||
import com.cedarsoftware.util.io.JsonReader;
|
||||
import com.cedarsoftware.util.io.JsonWriter;
|
||||
|
||||
|
||||
public class MetaAttributes {
|
||||
|
||||
private static final String FILENAME_KEY = "filename";
|
||||
private static final String METADATA_KEY = "metadata";
|
||||
|
||||
private final BasicFileAttributeView fileAttributeView;
|
||||
private final MetaAttributeView metaAttributeView;
|
||||
|
||||
|
||||
public MetaAttributes(File file) {
|
||||
this.metaAttributeView = new MetaAttributeView(file);
|
||||
this.fileAttributeView = Files.getFileAttributeView(file.toPath(), BasicFileAttributeView.class);
|
||||
}
|
||||
|
||||
|
||||
public void setCreationDate(long millis) {
|
||||
try {
|
||||
fileAttributeView.setTimes(null, null, FileTime.fromMillis(millis));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public long getCreationDate(long time) {
|
||||
try {
|
||||
return fileAttributeView.readAttributes().creationTime().toMillis();
|
||||
} catch (IOException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void putFileName(String name) {
|
||||
metaAttributeView.put(FILENAME_KEY, name);
|
||||
}
|
||||
|
||||
|
||||
public void getFileName(String name) {
|
||||
metaAttributeView.get(FILENAME_KEY);
|
||||
}
|
||||
|
||||
|
||||
public void putMetaData(Object object) {
|
||||
metaAttributeView.put(METADATA_KEY, JsonWriter.toJson(object));
|
||||
}
|
||||
|
||||
|
||||
public Object getMetaData() {
|
||||
return JsonReader.toJava(metaAttributeView.get(METADATA_KEY));
|
||||
}
|
||||
|
||||
}
|
|
@ -287,8 +287,12 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
|
|||
if (episodes.isEmpty()) {
|
||||
String suggestion = new SeriesNameMatcher(locale).matchByEpisodeIdentifier(getName(files.get(0)));
|
||||
if (suggestion != null) {
|
||||
// clean media info / release group info / etc
|
||||
suggestion = stripReleaseInfo(suggestion);
|
||||
// clean media info / release group info / etc
|
||||
try {
|
||||
suggestion = stripReleaseInfo(suggestion);
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
} else {
|
||||
// use folder name
|
||||
suggestion = files.get(0).getParentFile().getName();
|
||||
|
|
|
@ -41,6 +41,8 @@ import net.sourceforge.filebot.HistorySpooler;
|
|||
import net.sourceforge.filebot.NativeRenameAction;
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.filebot.StandardRenameAction;
|
||||
import net.sourceforge.filebot.media.MediaDetection;
|
||||
import net.sourceforge.filebot.similarity.Match;
|
||||
import net.sourceforge.tuned.ui.ProgressDialog;
|
||||
import net.sourceforge.tuned.ui.ProgressDialog.Cancellable;
|
||||
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
|
||||
|
@ -80,6 +82,17 @@ class RenameAction extends AbstractAction {
|
|||
// start processing
|
||||
window.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
|
||||
// first write all the metadata if xattr is enabled
|
||||
if (useExtendedFileAttributes()) {
|
||||
for (Match<Object, File> match : model.matches()) {
|
||||
try {
|
||||
MediaDetection.storeMetaInfo(match.getCandidate(), match.getValue());
|
||||
} catch (Throwable e) {
|
||||
Logger.getLogger(RenameAction.class.getName()).log(Level.WARNING, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (useNativeShell() && isNativeActionSupported(action)) {
|
||||
RenameJob renameJob = new NativeRenameJob(renameMap, NativeRenameAction.valueOf(action.name()));
|
||||
renameJob.execute();
|
||||
|
|
|
@ -50,7 +50,7 @@ public class Date implements Serializable {
|
|||
|
||||
|
||||
public long getTimeStamp() {
|
||||
return new GregorianCalendar(year, month, day).getTimeInMillis();
|
||||
return new GregorianCalendar(year, month - 1, day).getTimeInMillis(); // Month value is 0-based, e.g. 0 for January
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue