* save metadata as extended file attributes for each file

* set file creation date to episode/movie release date
This commit is contained in:
Reinhard Pointner 2012-10-23 19:05:55 +00:00
parent 0b4b353912
commit 2d3b6cf3a4
20 changed files with 302 additions and 12 deletions

View File

@ -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>

View File

@ -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" "$@"

View File

@ -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 "$@"

View File

@ -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 "$@"

View File

@ -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" %*

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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" "$@"

View File

@ -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" />

BIN
lib/json-io.jar Normal file

Binary file not shown.

View File

@ -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();
}
}
}

View File

@ -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"));

View File

@ -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));
}

View File

@ -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) }

View File

@ -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());
}
}
}

View File

@ -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));
}
}

View File

@ -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();

View File

@ -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();

View File

@ -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
}