+ support for --db xattr to allow offline renaming directly using previously written xattr metadata
This commit is contained in:
parent
d9ea19f259
commit
76073cfb9d
|
@ -18,6 +18,7 @@ import java.util.concurrent.Future;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import net.filebot.media.XattrMetaInfoProvider;
|
||||||
import net.filebot.web.AcoustIDClient;
|
import net.filebot.web.AcoustIDClient;
|
||||||
import net.filebot.web.AnidbClient;
|
import net.filebot.web.AnidbClient;
|
||||||
import net.filebot.web.AnidbSearchResult;
|
import net.filebot.web.AnidbSearchResult;
|
||||||
|
@ -64,6 +65,7 @@ public final class WebServices {
|
||||||
// misc
|
// misc
|
||||||
public static final FanartTVClient FanartTV = new FanartTVClient(Settings.getApplicationProperty("fanart.tv.apikey"));
|
public static final FanartTVClient FanartTV = new FanartTVClient(Settings.getApplicationProperty("fanart.tv.apikey"));
|
||||||
public static final AcoustIDClient AcoustID = new AcoustIDClient(Settings.getApplicationProperty("acoustid.apikey"));
|
public static final AcoustIDClient AcoustID = new AcoustIDClient(Settings.getApplicationProperty("acoustid.apikey"));
|
||||||
|
public static final XattrMetaInfoProvider XattrMetaData = new XattrMetaInfoProvider();
|
||||||
|
|
||||||
public static EpisodeListProvider[] getEpisodeListProviders() {
|
public static EpisodeListProvider[] getEpisodeListProviders() {
|
||||||
return new EpisodeListProvider[] { TheTVDB, AniDB, TVRage, Serienjunkies };
|
return new EpisodeListProvider[] { TheTVDB, AniDB, TVRage, Serienjunkies };
|
||||||
|
@ -90,7 +92,6 @@ public final class WebServices {
|
||||||
if (it.getName().equalsIgnoreCase(name))
|
if (it.getName().equalsIgnoreCase(name))
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null; // default
|
return null; // default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +100,6 @@ public final class WebServices {
|
||||||
if (it.getName().equalsIgnoreCase(name))
|
if (it.getName().equalsIgnoreCase(name))
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null; // default
|
return null; // default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +108,6 @@ public final class WebServices {
|
||||||
if (it.getName().equalsIgnoreCase(name))
|
if (it.getName().equalsIgnoreCase(name))
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null; // default
|
return null; // default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ public class ArgumentBean {
|
||||||
@Option(name = "-rename", usage = "Rename episode/movie files", metaVar = "fileset")
|
@Option(name = "-rename", usage = "Rename episode/movie files", metaVar = "fileset")
|
||||||
public boolean rename = false;
|
public boolean rename = false;
|
||||||
|
|
||||||
@Option(name = "--db", usage = "Episode/Movie database", metaVar = "[TheTVDB, AniDB, TVRage] or [TheMovieDB, IMDb]")
|
@Option(name = "--db", usage = "Episode/Movie database", metaVar = "[TheTVDB, AniDB, TVRage] or [TheMovieDB, IMDb] or [xattr]")
|
||||||
public String db;
|
public String db;
|
||||||
|
|
||||||
@Option(name = "--order", usage = "Episode order", metaVar = "[Airdate, Absolute, DVD]")
|
@Option(name = "--order", usage = "Episode order", metaVar = "[Airdate, Absolute, DVD]")
|
||||||
|
|
|
@ -111,6 +111,8 @@ public class ArgumentProcessor {
|
||||||
// script finished successfully
|
// script finished successfully
|
||||||
CLILogger.finest("Done ヾ(@⌒ー⌒@)ノ");
|
CLILogger.finest("Done ヾ(@⌒ー⌒@)ノ");
|
||||||
return 0;
|
return 0;
|
||||||
|
} catch (CmdlineException e) {
|
||||||
|
CLILogger.log(Level.WARNING, e.getMessage());
|
||||||
} catch (ScriptDeath e) {
|
} catch (ScriptDeath e) {
|
||||||
CLILogger.log(Level.WARNING, e.getMessage(), e.getCause());
|
CLILogger.log(Level.WARNING, e.getMessage(), e.getCause());
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package net.filebot.cli;
|
||||||
|
|
||||||
|
public class CmdlineException extends RuntimeException {
|
||||||
|
|
||||||
|
public CmdlineException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CmdlineException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -50,6 +50,7 @@ import net.filebot.hash.HashType;
|
||||||
import net.filebot.hash.VerificationFileReader;
|
import net.filebot.hash.VerificationFileReader;
|
||||||
import net.filebot.hash.VerificationFileWriter;
|
import net.filebot.hash.VerificationFileWriter;
|
||||||
import net.filebot.media.MediaDetection;
|
import net.filebot.media.MediaDetection;
|
||||||
|
import net.filebot.media.XattrMetaInfoProvider;
|
||||||
import net.filebot.similarity.CommonSequenceMatcher;
|
import net.filebot.similarity.CommonSequenceMatcher;
|
||||||
import net.filebot.similarity.EpisodeMatcher;
|
import net.filebot.similarity.EpisodeMatcher;
|
||||||
import net.filebot.similarity.Match;
|
import net.filebot.similarity.Match;
|
||||||
|
@ -104,6 +105,10 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
return renameMusic(files, action, conflictAction, outputDir, format, getMusicIdentificationService(db) == null ? AcoustID : getMusicIdentificationService(db));
|
return renameMusic(files, action, conflictAction, outputDir, format, getMusicIdentificationService(db) == null ? AcoustID : getMusicIdentificationService(db));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (XattrMetaData.getName().equalsIgnoreCase(db)) {
|
||||||
|
return renameByMetaData(files, action, conflictAction, outputDir, format, filter, XattrMetaData);
|
||||||
|
}
|
||||||
|
|
||||||
// auto-determine mode
|
// auto-determine mode
|
||||||
List<File> mediaFiles = filter(files, VIDEO_FILES, SUBTITLE_FILES);
|
List<File> mediaFiles = filter(files, VIDEO_FILES, SUBTITLE_FILES);
|
||||||
double max = mediaFiles.size();
|
double max = mediaFiles.size();
|
||||||
|
@ -153,7 +158,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
|
|
||||||
List<File> mediaFiles = filter(fileset, VIDEO_FILES, SUBTITLE_FILES);
|
List<File> mediaFiles = filter(fileset, VIDEO_FILES, SUBTITLE_FILES);
|
||||||
if (mediaFiles.isEmpty()) {
|
if (mediaFiles.isEmpty()) {
|
||||||
throw new Exception("No media files: " + files);
|
throw new CmdlineException("No media files: " + files);
|
||||||
}
|
}
|
||||||
|
|
||||||
// similarity metrics for matching
|
// similarity metrics for matching
|
||||||
|
@ -185,7 +190,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strict && seriesNames.size() > 1) {
|
if (strict && seriesNames.size() > 1) {
|
||||||
throw new Exception("Processing multiple shows at once requires -non-strict");
|
throw new CmdlineException("Processing multiple shows at once requires -non-strict");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seriesNames.size() == 0) {
|
if (seriesNames.size() == 0) {
|
||||||
|
@ -210,7 +215,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matches.isEmpty()) {
|
if (matches.isEmpty()) {
|
||||||
throw new Exception("Unable to match files to episode data");
|
throw new CmdlineException("Unable to match files to episode data");
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle derived files
|
// handle derived files
|
||||||
|
@ -385,7 +390,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
List<Movie> results = service.searchMovie(query, locale);
|
List<Movie> results = service.searchMovie(query, locale);
|
||||||
List<Movie> validResults = applyExpressionFilter(results, filter);
|
List<Movie> validResults = applyExpressionFilter(results, filter);
|
||||||
if (validResults.isEmpty()) {
|
if (validResults.isEmpty()) {
|
||||||
throw new Exception("Unable to find a valid match: " + results);
|
throw new CmdlineException("Unable to find a valid match: " + results);
|
||||||
}
|
}
|
||||||
|
|
||||||
// force all mappings
|
// force all mappings
|
||||||
|
@ -404,7 +409,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
|
|
||||||
// sanity check that we have something to do
|
// sanity check that we have something to do
|
||||||
if (fileset.isEmpty() || movieMatchFiles.isEmpty()) {
|
if (fileset.isEmpty() || movieMatchFiles.isEmpty()) {
|
||||||
throw new Exception("No media files: " + files);
|
throw new CmdlineException("No media files: " + files);
|
||||||
}
|
}
|
||||||
|
|
||||||
// map movies to (possibly multiple) files (in natural order)
|
// map movies to (possibly multiple) files (in natural order)
|
||||||
|
@ -536,6 +541,26 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
return renameAll(renameMap, renameAction, conflictAction, null);
|
return renameAll(renameMap, renameAction, conflictAction, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<File> renameByMetaData(Collection<File> files, RenameAction renameAction, ConflictAction conflictAction, File outputDir, ExpressionFormat format, ExpressionFilter filter, XattrMetaInfoProvider service) throws Exception {
|
||||||
|
CLILogger.config(format("Rename files using [%s]", service.getName()));
|
||||||
|
|
||||||
|
// force sort order
|
||||||
|
List<File> selection = sortByUniquePath(files);
|
||||||
|
Map<File, File> renameMap = new LinkedHashMap<File, File>();
|
||||||
|
|
||||||
|
for (Entry<File, Object> it : service.getMetaData(selection).entrySet()) {
|
||||||
|
MediaBindingBean bindingBean = new MediaBindingBean(it.getValue(), it.getKey(), null);
|
||||||
|
|
||||||
|
if (filter == null || filter.matches(bindingBean)) {
|
||||||
|
String newName = (format != null) ? format.format(bindingBean) : validateFileName(it.getValue().toString());
|
||||||
|
renameMap.put(it.getKey(), getDestinationFile(it.getKey(), newName, outputDir));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rename files according to xattr metadata objects
|
||||||
|
return renameAll(renameMap, renameAction, conflictAction, null);
|
||||||
|
}
|
||||||
|
|
||||||
private Map<File, Object> getContext(final Collection<Match<File, ?>> matches) {
|
private Map<File, Object> getContext(final Collection<Match<File, ?>> matches) {
|
||||||
return new AbstractMap<File, Object>() {
|
return new AbstractMap<File, Object>() {
|
||||||
|
|
||||||
|
@ -571,7 +596,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
|
|
||||||
public List<File> renameAll(Map<File, File> renameMap, RenameAction renameAction, ConflictAction conflictAction, List<Match<File, ?>> matches) throws Exception {
|
public List<File> renameAll(Map<File, File> renameMap, RenameAction renameAction, ConflictAction conflictAction, List<Match<File, ?>> matches) throws Exception {
|
||||||
if (renameMap.isEmpty()) {
|
if (renameMap.isEmpty()) {
|
||||||
throw new Exception("Unable to identify or process any files");
|
throw new CmdlineException("Unable to identify or process any files");
|
||||||
}
|
}
|
||||||
|
|
||||||
// rename files
|
// rename files
|
||||||
|
@ -591,7 +616,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
|
|
||||||
if (!destination.equals(source) && destination.exists()) {
|
if (!destination.equals(source) && destination.exists()) {
|
||||||
if (conflictAction == ConflictAction.FAIL) {
|
if (conflictAction == ConflictAction.FAIL) {
|
||||||
throw new Exception("File already exists: " + destination);
|
throw new CmdlineException("File already exists: " + destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conflictAction == ConflictAction.OVERRIDE || (conflictAction == ConflictAction.AUTO && VIDEO_SIZE_ORDER.compare(source, destination) > 0)) {
|
if (conflictAction == ConflictAction.OVERRIDE || (conflictAction == ConflictAction.AUTO && VIDEO_SIZE_ORDER.compare(source, destination) > 0)) {
|
||||||
|
@ -677,7 +702,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
|
|
||||||
CLILogger.finest(String.format("Get [%s] subtitles for %d files", language.getName(), remainingVideos.size()));
|
CLILogger.finest(String.format("Get [%s] subtitles for %d files", language.getName(), remainingVideos.size()));
|
||||||
if (remainingVideos.isEmpty()) {
|
if (remainingVideos.isEmpty()) {
|
||||||
throw new Exception("No video files: " + files);
|
throw new CmdlineException("No video files: " + files);
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookup subtitles by hash
|
// lookup subtitles by hash
|
||||||
|
@ -902,7 +927,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strict) {
|
if (strict) {
|
||||||
throw new Exception("Multiple options: Force auto-select requires non-strict matching: " + searchResults);
|
throw new CmdlineException("Multiple options: Force auto-select requires non-strict matching: " + searchResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
// just pick the best 5 matches
|
// just pick the best 5 matches
|
||||||
|
@ -921,7 +946,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
|
|
||||||
if (language == null) {
|
if (language == null) {
|
||||||
// unable to lookup language
|
// unable to lookup language
|
||||||
throw new Exception("Illegal language code: " + lang);
|
throw new CmdlineException("Illegal language code: " + lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
return language;
|
return language;
|
||||||
|
@ -955,7 +980,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
File[] common = csm.matchFirstCommonSequence(pathArray);
|
File[] common = csm.matchFirstCommonSequence(pathArray);
|
||||||
|
|
||||||
if (common == null) {
|
if (common == null) {
|
||||||
throw new Exception("Paths must be on the same filesystem: " + files);
|
throw new CmdlineException("Paths must be on the same filesystem: " + files);
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element in the common sequence must be the root folder
|
// last element in the common sequence must be the root folder
|
||||||
|
@ -976,11 +1001,11 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hashType == null) {
|
if (hashType == null) {
|
||||||
throw new Exception("Illegal output type: " + output);
|
throw new CmdlineException("Illegal output type: " + output);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (files.isEmpty()) {
|
if (files.isEmpty()) {
|
||||||
throw new Exception("No files: " + files);
|
throw new CmdlineException("No files: " + files);
|
||||||
}
|
}
|
||||||
|
|
||||||
CLILogger.info(format("Compute %s hash for %s files [%s]", hashType, files.size(), outputFile));
|
CLILogger.info(format("Compute %s hash for %s files [%s]", hashType, files.size(), outputFile));
|
||||||
|
@ -994,7 +1019,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
|
|
||||||
// check if type is supported
|
// check if type is supported
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
throw new Exception("Unsupported format: " + verificationFile);
|
throw new CmdlineException("Unsupported format: " + verificationFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add all file names from verification file
|
// add all file names from verification file
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package net.filebot.media;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class XattrMetaInfoProvider {
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return "xattr";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<File, Object> getMetaData(Iterable<File> files) {
|
||||||
|
Map<File, Object> result = new LinkedHashMap<File, Object>();
|
||||||
|
|
||||||
|
for (File f : files) {
|
||||||
|
Object metaObject = MediaDetection.readMetaInfo(f);
|
||||||
|
if (metaObject != null) {
|
||||||
|
result.put(f, metaObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue