* improved episode format and format creation dialog
* differentiate between format errors and format warnings
This commit is contained in:
parent
c83d4132ec
commit
7e6f485882
|
@ -122,7 +122,7 @@
|
||||||
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Simple memory cache named web. Time to live is 5 min. This cache is used by TheTVDBClient and TVRageClient.
|
Short-lived memory cache for web responses. Time to live is 5 min. This cache is used by TheTVDBClient and TVRageClient.
|
||||||
-->
|
-->
|
||||||
<cache name="web"
|
<cache name="web"
|
||||||
maxElementsInMemory="120"
|
maxElementsInMemory="120"
|
||||||
|
@ -130,7 +130,20 @@
|
||||||
timeToIdleSeconds="300"
|
timeToIdleSeconds="300"
|
||||||
timeToLiveSeconds="300"
|
timeToLiveSeconds="300"
|
||||||
diskPersistent="false"
|
diskPersistent="false"
|
||||||
memoryStoreEvictionPolicy="FIFO"
|
memoryStoreEvictionPolicy="LRU"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Simple memory cache for calculated checksums. Time to live is 2 hours. This cache is used in EpisodeFormatBindingBean
|
||||||
|
-->
|
||||||
|
<cache name="checksum"
|
||||||
|
maxElementsInMemory="4200"
|
||||||
|
eternal="false"
|
||||||
|
timeToIdleSeconds="7200"
|
||||||
|
timeToLiveSeconds="7200"
|
||||||
|
diskPersistent="false"
|
||||||
|
memoryStoreEvictionPolicy="LRU"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</ehcache>
|
</ehcache>
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.format;
|
||||||
|
|
||||||
|
|
||||||
|
public class BindingException extends RuntimeException {
|
||||||
|
|
||||||
|
public BindingException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public BindingException(String binding, String innerMessage, Throwable cause) {
|
||||||
|
this(String.format("BindingError: \"%s\": %s", binding, innerMessage), cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.format;
|
||||||
|
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Target(METHOD)
|
||||||
|
public @interface Define {
|
||||||
|
|
||||||
|
String[] value();
|
||||||
|
|
||||||
|
static final String undefined = "";
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.format;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import javax.script.Bindings;
|
||||||
|
import javax.script.ScriptException;
|
||||||
|
|
||||||
|
import net.sourceforge.filebot.similarity.Match;
|
||||||
|
import net.sourceforge.filebot.web.Episode;
|
||||||
|
|
||||||
|
|
||||||
|
public class EpisodeExpressionFormat extends ExpressionFormat {
|
||||||
|
|
||||||
|
public EpisodeExpressionFormat(String format) throws ScriptException {
|
||||||
|
super(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bindings getBindings(Object value) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Match<Episode, File> match = (Match<Episode, File>) value;
|
||||||
|
|
||||||
|
return new ExpressionBindings(new EpisodeFormatBindingBean(match.getValue(), match.getCandidate()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void dispose(Bindings bindings) {
|
||||||
|
// dispose binding bean
|
||||||
|
getBindingBean(bindings).dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private EpisodeFormatBindingBean getBindingBean(Bindings bindings) {
|
||||||
|
return (EpisodeFormatBindingBean) ((ExpressionBindings) bindings).getBindingBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,214 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.format;
|
||||||
|
|
||||||
|
|
||||||
|
import static net.sourceforge.filebot.format.Define.undefined;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.SortedMap;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
|
||||||
|
import net.sf.ehcache.Cache;
|
||||||
|
import net.sf.ehcache.CacheManager;
|
||||||
|
import net.sf.ehcache.Element;
|
||||||
|
import net.sourceforge.filebot.FileBotUtilities;
|
||||||
|
import net.sourceforge.filebot.mediainfo.MediaInfo;
|
||||||
|
import net.sourceforge.filebot.mediainfo.MediaInfo.StreamKind;
|
||||||
|
import net.sourceforge.filebot.web.Episode;
|
||||||
|
|
||||||
|
|
||||||
|
public class EpisodeFormatBindingBean {
|
||||||
|
|
||||||
|
private final Episode episode;
|
||||||
|
|
||||||
|
private final File mediaFile;
|
||||||
|
|
||||||
|
private MediaInfo mediaInfo;
|
||||||
|
|
||||||
|
|
||||||
|
public EpisodeFormatBindingBean(Episode episode, File mediaFile) {
|
||||||
|
this.episode = episode;
|
||||||
|
this.mediaFile = mediaFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Define(undefined)
|
||||||
|
public String undefined() {
|
||||||
|
// omit expressions that depend on undefined values
|
||||||
|
throw new RuntimeException("undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Define("n")
|
||||||
|
public String getSeriesName() {
|
||||||
|
return episode.getSeriesName();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Define("s")
|
||||||
|
public String getSeasonNumber() {
|
||||||
|
return episode.getSeasonNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Define("e")
|
||||||
|
public String getEpisodeNumber() {
|
||||||
|
return episode.getEpisodeNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Define("t")
|
||||||
|
public String getTitle() {
|
||||||
|
return episode.getTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Define("vc")
|
||||||
|
public String getVideoCodec() {
|
||||||
|
return getMediaInfo(StreamKind.Video, 0, "Encoded_Library/Name", "CodecID/Hint", "Codec/String");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Define("ac")
|
||||||
|
public String getAudioCodec() {
|
||||||
|
return getMediaInfo(StreamKind.Audio, 0, "CodecID/Hint", "Codec/String");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Define("hi")
|
||||||
|
public String getHeightAndInterlacement() {
|
||||||
|
String height = getMediaInfo(StreamKind.Video, 0, "Height");
|
||||||
|
String interlacement = getMediaInfo(StreamKind.Video, 0, "Interlacement");
|
||||||
|
|
||||||
|
if (height == null || interlacement == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// e.g. 720p
|
||||||
|
return height + Character.toLowerCase(interlacement.charAt(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Define("resolution")
|
||||||
|
public String getVideoResolution() {
|
||||||
|
String width = getMediaInfo(StreamKind.Video, 0, "Width");
|
||||||
|
String height = getMediaInfo(StreamKind.Video, 0, "Height");
|
||||||
|
|
||||||
|
if (width == null || height == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// e.g. 1280x720
|
||||||
|
return width + 'x' + height;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Define("crc32")
|
||||||
|
public String getCRC32() throws IOException {
|
||||||
|
if (mediaFile != null) {
|
||||||
|
// try to get checksum from file name
|
||||||
|
String embeddedChecksum = FileBotUtilities.getEmbeddedChecksum(mediaFile.getName());
|
||||||
|
|
||||||
|
if (embeddedChecksum != null) {
|
||||||
|
return embeddedChecksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate checksum from file
|
||||||
|
return crc32(mediaFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Define("video")
|
||||||
|
public SortedMap<String, String> getVideoInfo() {
|
||||||
|
return getMediaInfo().snapshot(StreamKind.Video, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Define("audio")
|
||||||
|
public SortedMap<String, String> getAudioInfo() {
|
||||||
|
return getMediaInfo().snapshot(StreamKind.Audio, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Define("general")
|
||||||
|
public SortedMap<String, String> getGeneralMediaInfo() {
|
||||||
|
return getMediaInfo().snapshot(StreamKind.General, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public synchronized MediaInfo getMediaInfo() {
|
||||||
|
if (mediaInfo == null) {
|
||||||
|
mediaInfo = new MediaInfo();
|
||||||
|
|
||||||
|
if (mediaFile == null || !mediaInfo.open(mediaFile)) {
|
||||||
|
throw new RuntimeException(String.format("Cannot open file: %s", mediaFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mediaInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public synchronized void dispose() {
|
||||||
|
if (mediaInfo != null) {
|
||||||
|
mediaInfo.close();
|
||||||
|
mediaInfo.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaInfo = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private String getMediaInfo(StreamKind streamKind, int streamNumber, String... keys) {
|
||||||
|
MediaInfo mediaInfo = getMediaInfo();
|
||||||
|
|
||||||
|
if (mediaInfo != null) {
|
||||||
|
for (String key : keys) {
|
||||||
|
String value = mediaInfo.get(streamKind, streamNumber, key);
|
||||||
|
|
||||||
|
if (value.length() > 0) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Cache checksumCache = CacheManager.getInstance().getCache("checksum");
|
||||||
|
|
||||||
|
|
||||||
|
private String crc32(File file) throws IOException {
|
||||||
|
// try to get checksum from cache
|
||||||
|
Element cacheEntry = checksumCache.get(file);
|
||||||
|
|
||||||
|
if (cacheEntry != null) {
|
||||||
|
return (String) cacheEntry.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate checksum
|
||||||
|
InputStream in = new FileInputStream(file);
|
||||||
|
CRC32 crc = new CRC32();
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] buffer = new byte[32 * 1024];
|
||||||
|
int len = 0;
|
||||||
|
|
||||||
|
while ((len = in.read(buffer)) >= 0) {
|
||||||
|
crc.update(buffer, 0, len);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
String checksum = String.format("%08X", crc.getValue());
|
||||||
|
|
||||||
|
checksumCache.put(new Element(file, checksum));
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.format;
|
||||||
|
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.AbstractMap;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.script.Bindings;
|
||||||
|
|
||||||
|
import net.sourceforge.tuned.ExceptionUtilities;
|
||||||
|
|
||||||
|
|
||||||
|
public class ExpressionBindings extends AbstractMap<String, Object> implements Bindings {
|
||||||
|
|
||||||
|
protected final Object bean;
|
||||||
|
|
||||||
|
protected final Map<String, Method> bindings = new HashMap<String, Method>();
|
||||||
|
|
||||||
|
|
||||||
|
public ExpressionBindings(Object bindingBean) {
|
||||||
|
bean = bindingBean;
|
||||||
|
|
||||||
|
// get method bindings
|
||||||
|
for (Method method : bean.getClass().getMethods()) {
|
||||||
|
Define define = method.getAnnotation(Define.class);
|
||||||
|
|
||||||
|
if (define != null) {
|
||||||
|
for (String name : define.value()) {
|
||||||
|
Method existingBinding = bindings.put(name, method);
|
||||||
|
|
||||||
|
if (existingBinding != null)
|
||||||
|
throw new IllegalArgumentException(String.format("Illegal binding {%s} on %s", name, method.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Object getBindingBean() {
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected Object evaluate(Method method) throws Exception {
|
||||||
|
Object value = method.invoke(getBindingBean());
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// invoke fallback method
|
||||||
|
return bindings.get(Define.undefined).invoke(getBindingBean());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object get(Object key) {
|
||||||
|
Method method = bindings.get(key);
|
||||||
|
|
||||||
|
if (method != null) {
|
||||||
|
try {
|
||||||
|
return evaluate(method);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new BindingException(key.toString(), ExceptionUtilities.getRootCauseMessage(e), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object put(String key, Object value) {
|
||||||
|
// bindings are immutable
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object remove(Object key) {
|
||||||
|
// bindings are immutable
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(Object key) {
|
||||||
|
return bindings.containsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> keySet() {
|
||||||
|
return bindings.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return bindings.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Entry<String, Object>> entrySet() {
|
||||||
|
Set<Entry<String, Object>> entrySet = new HashSet<Entry<String, Object>>();
|
||||||
|
|
||||||
|
for (final String key : keySet()) {
|
||||||
|
entrySet.add(new Entry<String, Object>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValue() {
|
||||||
|
return get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object setValue(Object value) {
|
||||||
|
return put(key, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return entrySet;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
package net.sourceforge.filebot.ui;
|
package net.sourceforge.filebot.format;
|
||||||
|
|
||||||
|
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
@ -14,17 +14,21 @@ import java.util.regex.Pattern;
|
||||||
import javax.script.Bindings;
|
import javax.script.Bindings;
|
||||||
import javax.script.Compilable;
|
import javax.script.Compilable;
|
||||||
import javax.script.CompiledScript;
|
import javax.script.CompiledScript;
|
||||||
|
import javax.script.ScriptContext;
|
||||||
import javax.script.ScriptEngine;
|
import javax.script.ScriptEngine;
|
||||||
import javax.script.ScriptEngineManager;
|
import javax.script.ScriptEngineManager;
|
||||||
import javax.script.ScriptException;
|
import javax.script.ScriptException;
|
||||||
|
import javax.script.SimpleScriptContext;
|
||||||
|
|
||||||
|
|
||||||
public abstract class ExpressionFormat extends Format {
|
public class ExpressionFormat extends Format {
|
||||||
|
|
||||||
private final String format;
|
private final String format;
|
||||||
|
|
||||||
private final Object[] expressions;
|
private final Object[] expressions;
|
||||||
|
|
||||||
|
private ScriptException lastException;
|
||||||
|
|
||||||
|
|
||||||
public ExpressionFormat(String format) throws ScriptException {
|
public ExpressionFormat(String format) throws ScriptException {
|
||||||
this.format = format;
|
this.format = format;
|
||||||
|
@ -35,7 +39,7 @@ public abstract class ExpressionFormat extends Format {
|
||||||
protected ScriptEngine initScriptEngine() throws ScriptException {
|
protected ScriptEngine initScriptEngine() throws ScriptException {
|
||||||
ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
|
ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
|
||||||
|
|
||||||
engine.eval(new InputStreamReader(getClass().getResourceAsStream("ExpressionFormat.global.js")));
|
engine.eval(new InputStreamReader(ExpressionFormat.class.getResourceAsStream("ExpressionFormat.global.js")));
|
||||||
|
|
||||||
return engine;
|
return engine;
|
||||||
}
|
}
|
||||||
|
@ -78,33 +82,53 @@ public abstract class ExpressionFormat extends Format {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected abstract Bindings getBindings(Object value);
|
protected Bindings getBindings(Object value) {
|
||||||
|
// no bindings by default
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StringBuffer format(Object object, StringBuffer sb, FieldPosition pos) {
|
public StringBuffer format(Object object, StringBuffer sb, FieldPosition pos) {
|
||||||
Bindings bindings = getBindings(object);
|
Bindings bindings = getBindings(object);
|
||||||
|
|
||||||
|
ScriptContext context = new SimpleScriptContext();
|
||||||
|
context.setBindings(bindings, ScriptContext.GLOBAL_SCOPE);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (Object snipped : expressions) {
|
for (Object snipped : expressions) {
|
||||||
if (snipped instanceof String) {
|
if (snipped instanceof CompiledScript) {
|
||||||
sb.append(snipped);
|
try {
|
||||||
} else {
|
Object value = ((CompiledScript) snipped).eval(context);
|
||||||
Object value = ((CompiledScript) snipped).eval(bindings);
|
|
||||||
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
sb.append(value);
|
sb.append(value);
|
||||||
|
}
|
||||||
|
} catch (ScriptException e) {
|
||||||
|
lastException = e;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
sb.append(snipped);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (ScriptException e) {
|
} finally {
|
||||||
throw new IllegalArgumentException(e);
|
dispose(bindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb;
|
return sb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void dispose(Bindings bindings) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ScriptException scriptException() {
|
||||||
|
return lastException;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object parseObject(String source, ParsePosition pos) {
|
public Object parseObject(String source, ParsePosition pos) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
|
@ -1,38 +0,0 @@
|
||||||
|
|
||||||
package net.sourceforge.filebot.ui;
|
|
||||||
|
|
||||||
|
|
||||||
import javax.script.Bindings;
|
|
||||||
import javax.script.ScriptException;
|
|
||||||
import javax.script.SimpleBindings;
|
|
||||||
|
|
||||||
import net.sourceforge.filebot.web.Episode;
|
|
||||||
|
|
||||||
|
|
||||||
public class EpisodeExpressionFormat extends ExpressionFormat {
|
|
||||||
|
|
||||||
public EpisodeExpressionFormat(String format) throws ScriptException {
|
|
||||||
super(format);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Bindings getBindings(Object value) {
|
|
||||||
Episode episode = (Episode) value;
|
|
||||||
|
|
||||||
Bindings bindings = new SimpleBindings();
|
|
||||||
|
|
||||||
bindings.put("n", nonNull(episode.getSeriesName()));
|
|
||||||
bindings.put("s", nonNull(episode.getSeasonNumber()));
|
|
||||||
bindings.put("e", nonNull(episode.getEpisodeNumber()));
|
|
||||||
bindings.put("t", nonNull(episode.getTitle()));
|
|
||||||
|
|
||||||
return bindings;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private String nonNull(String value) {
|
|
||||||
return value == null ? "" : value;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -11,44 +11,51 @@ import java.awt.Component;
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
import java.awt.Window;
|
import java.awt.Window;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
import java.awt.event.WindowAdapter;
|
import java.awt.event.WindowAdapter;
|
||||||
import java.awt.event.WindowEvent;
|
import java.awt.event.WindowEvent;
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
|
import java.io.File;
|
||||||
import java.text.Format;
|
import java.text.Format;
|
||||||
import java.text.ParseException;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.script.ScriptException;
|
import javax.script.ScriptException;
|
||||||
import javax.swing.AbstractAction;
|
import javax.swing.AbstractAction;
|
||||||
import javax.swing.Action;
|
import javax.swing.Action;
|
||||||
import javax.swing.BorderFactory;
|
|
||||||
import javax.swing.InputVerifier;
|
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.JDialog;
|
import javax.swing.JDialog;
|
||||||
import javax.swing.JFormattedTextField;
|
import javax.swing.JFileChooser;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JPopupMenu;
|
||||||
import javax.swing.JTextField;
|
import javax.swing.JTextField;
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingWorker;
|
||||||
import javax.swing.JFormattedTextField.AbstractFormatter;
|
import javax.swing.Timer;
|
||||||
import javax.swing.border.LineBorder;
|
import javax.swing.border.LineBorder;
|
||||||
import javax.swing.event.DocumentEvent;
|
import javax.swing.event.DocumentEvent;
|
||||||
import javax.swing.event.DocumentListener;
|
import javax.swing.event.DocumentListener;
|
||||||
import javax.swing.text.DefaultFormatterFactory;
|
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||||
|
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
import net.sourceforge.filebot.ResourceManager;
|
import net.sourceforge.filebot.ResourceManager;
|
||||||
import net.sourceforge.filebot.Settings;
|
import net.sourceforge.filebot.Settings;
|
||||||
|
import net.sourceforge.filebot.format.EpisodeExpressionFormat;
|
||||||
|
import net.sourceforge.filebot.similarity.Match;
|
||||||
import net.sourceforge.filebot.web.Episode;
|
import net.sourceforge.filebot.web.Episode;
|
||||||
import net.sourceforge.filebot.web.Episode.EpisodeFormat;
|
import net.sourceforge.filebot.web.Episode.EpisodeFormat;
|
||||||
import net.sourceforge.tuned.ExceptionUtilities;
|
import net.sourceforge.tuned.ExceptionUtilities;
|
||||||
import net.sourceforge.tuned.PreferencesMap.PreferencesEntry;
|
|
||||||
import net.sourceforge.tuned.ui.GradientStyle;
|
import net.sourceforge.tuned.ui.GradientStyle;
|
||||||
import net.sourceforge.tuned.ui.LinkButton;
|
import net.sourceforge.tuned.ui.LinkButton;
|
||||||
import net.sourceforge.tuned.ui.TunedUtilities;
|
import net.sourceforge.tuned.ui.TunedUtilities;
|
||||||
|
@ -60,16 +67,20 @@ public class EpisodeFormatDialog extends JDialog {
|
||||||
|
|
||||||
private Format selectedFormat = null;
|
private Format selectedFormat = null;
|
||||||
|
|
||||||
protected final JFormattedTextField preview = new JFormattedTextField();
|
private JLabel preview = new JLabel();
|
||||||
|
|
||||||
protected final JLabel errorMessage = new JLabel(ResourceManager.getIcon("dialog.cancel"));
|
private JLabel warningMessage = new JLabel(ResourceManager.getIcon("status.warning"));
|
||||||
protected final JTextField editor = new JTextField();
|
private JLabel errorMessage = new JLabel(ResourceManager.getIcon("status.error"));
|
||||||
|
|
||||||
protected Color defaultColor = preview.getForeground();
|
private Episode previewSampleEpisode = getPreviewSampleEpisode();
|
||||||
protected Color errorColor = Color.red;
|
private File previewSampleMediaFile = getPreviewSampleMediaFile();
|
||||||
|
|
||||||
protected final PreferencesEntry<String> persistentFormat = Settings.userRoot().entry("dialog.format");
|
private ExecutorService previewExecutor = createPreviewExecutor();
|
||||||
protected final PreferencesEntry<String> persistentSample = Settings.userRoot().entry("dialog.sample");
|
|
||||||
|
private JTextField editor = new JTextField();
|
||||||
|
|
||||||
|
private Color defaultColor = preview.getForeground();
|
||||||
|
private Color errorColor = Color.red;
|
||||||
|
|
||||||
|
|
||||||
public EpisodeFormatDialog(Window owner) {
|
public EpisodeFormatDialog(Window owner) {
|
||||||
|
@ -77,17 +88,9 @@ public class EpisodeFormatDialog extends JDialog {
|
||||||
|
|
||||||
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
||||||
|
|
||||||
|
editor.setText(Settings.userRoot().get("dialog.format"));
|
||||||
editor.setFont(new Font(MONOSPACED, PLAIN, 14));
|
editor.setFont(new Font(MONOSPACED, PLAIN, 14));
|
||||||
|
|
||||||
// restore state
|
|
||||||
preview.setValue(getPreviewSample());
|
|
||||||
editor.setText(persistentFormat.getValue());
|
|
||||||
|
|
||||||
preview.setBorder(BorderFactory.createEmptyBorder());
|
|
||||||
|
|
||||||
// update preview to current format
|
|
||||||
checkEpisodeFormat();
|
|
||||||
|
|
||||||
// bold title label in header
|
// bold title label in header
|
||||||
JLabel title = new JLabel(this.getTitle());
|
JLabel title = new JLabel(this.getTitle());
|
||||||
title.setFont(title.getFont().deriveFont(BOLD));
|
title.setFont(title.getFont().deriveFont(BOLD));
|
||||||
|
@ -97,9 +100,13 @@ public class EpisodeFormatDialog extends JDialog {
|
||||||
header.setBackground(Color.white);
|
header.setBackground(Color.white);
|
||||||
header.setBorder(new SeparatorBorder(1, new Color(0xB4B4B4), new Color(0xACACAC), GradientStyle.LEFT_TO_RIGHT, Position.BOTTOM));
|
header.setBorder(new SeparatorBorder(1, new Color(0xB4B4B4), new Color(0xACACAC), GradientStyle.LEFT_TO_RIGHT, Position.BOTTOM));
|
||||||
|
|
||||||
|
errorMessage.setVisible(false);
|
||||||
|
warningMessage.setVisible(false);
|
||||||
|
|
||||||
header.add(title, "wrap unrel:push");
|
header.add(title, "wrap unrel:push");
|
||||||
header.add(errorMessage, "gap indent, hidemode 3");
|
header.add(preview, "gap indent, hidemode 3, wmax 90%");
|
||||||
header.add(preview, "gap indent, hidemode 3, growx");
|
header.add(errorMessage, "gap indent, hidemode 3, newline");
|
||||||
|
header.add(warningMessage, "gap indent, hidemode 3, newline");
|
||||||
|
|
||||||
JPanel content = new JPanel(new MigLayout("insets dialog, nogrid, fill"));
|
JPanel content = new JPanel(new MigLayout("insets dialog, nogrid, fill"));
|
||||||
|
|
||||||
|
@ -121,35 +128,31 @@ public class EpisodeFormatDialog extends JDialog {
|
||||||
pane.add(header, "h 60px, growx, dock north");
|
pane.add(header, "h 60px, growx, dock north");
|
||||||
pane.add(content, "grow");
|
pane.add(content, "grow");
|
||||||
|
|
||||||
pack();
|
setSize(485, 390);
|
||||||
|
|
||||||
|
header.setComponentPopupMenu(createPreviewSamplePopup());
|
||||||
|
|
||||||
setLocation(TunedUtilities.getPreferredLocation(this));
|
setLocation(TunedUtilities.getPreferredLocation(this));
|
||||||
|
|
||||||
TunedUtilities.putActionForKeystroke(pane, KeyStroke.getKeyStroke("released ESCAPE"), cancelAction);
|
TunedUtilities.putActionForKeystroke(pane, KeyStroke.getKeyStroke("released ESCAPE"), cancelAction);
|
||||||
|
|
||||||
|
// update preview to current format
|
||||||
|
checkFormatInBackground();
|
||||||
|
|
||||||
// update format on change
|
// update format on change
|
||||||
editor.getDocument().addDocumentListener(new DocumentAdapter() {
|
editor.getDocument().addDocumentListener(new LazyDocumentAdapter() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update(DocumentEvent evt) {
|
public void update() {
|
||||||
checkEpisodeFormat();
|
checkFormatInBackground();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// keep focus on preview, if current text doesn't fit episode format
|
addPropertyChangeListener("previewSample", new PropertyChangeListener() {
|
||||||
preview.setInputVerifier(new InputVerifier() {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean verify(JComponent input) {
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
return checkPreviewSample();
|
checkFormatInBackground();
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// check edit format on change
|
|
||||||
preview.getDocument().addDocumentListener(new DocumentAdapter() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(DocumentEvent evt) {
|
|
||||||
checkPreviewSample();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -164,7 +167,52 @@ public class EpisodeFormatDialog extends JDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected JPanel createSyntaxPanel() {
|
private JPopupMenu createPreviewSamplePopup() {
|
||||||
|
JPopupMenu actionPopup = new JPopupMenu("Sample");
|
||||||
|
|
||||||
|
actionPopup.add(new AbstractAction("Change Episode") {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent evt) {
|
||||||
|
String episode = JOptionPane.showInputDialog(EpisodeFormatDialog.this, null, previewSampleEpisode);
|
||||||
|
|
||||||
|
if (episode != null) {
|
||||||
|
try {
|
||||||
|
previewSampleEpisode = EpisodeFormat.getInstance().parseObject(episode);
|
||||||
|
Settings.userRoot().put("dialog.sample.episode", episode);
|
||||||
|
|
||||||
|
EpisodeFormatDialog.this.firePropertyChange("previewSample", null, previewSample());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.getLogger("ui").warning(String.format("Cannot parse %s", episode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
actionPopup.add(new AbstractAction("Change Media File") {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent evt) {
|
||||||
|
JFileChooser fileChooser = new JFileChooser();
|
||||||
|
fileChooser.setSelectedFile(previewSampleMediaFile);
|
||||||
|
fileChooser.setFileFilter(new FileNameExtensionFilter("Media files", "avi", "mkv", "mp4", "ogm"));
|
||||||
|
|
||||||
|
if (fileChooser.showOpenDialog(EpisodeFormatDialog.this) == JFileChooser.APPROVE_OPTION) {
|
||||||
|
previewSampleMediaFile = fileChooser.getSelectedFile();
|
||||||
|
Settings.userRoot().put("dialog.sample.file", previewSampleMediaFile.getAbsolutePath());
|
||||||
|
|
||||||
|
EpisodeFormatDialog.this.firePropertyChange("previewSample", null, previewSample());
|
||||||
|
|
||||||
|
MediaInfoComponent.showDialog(EpisodeFormatDialog.this, previewSampleMediaFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return actionPopup;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private JPanel createSyntaxPanel() {
|
||||||
JPanel panel = new JPanel(new MigLayout("fill, nogrid"));
|
JPanel panel = new JPanel(new MigLayout("fill, nogrid"));
|
||||||
|
|
||||||
panel.setBorder(new LineBorder(new Color(0xACA899)));
|
panel.setBorder(new LineBorder(new Color(0xACA899)));
|
||||||
|
@ -177,7 +225,7 @@ public class EpisodeFormatDialog extends JDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected JPanel createExamplesPanel() {
|
private JPanel createExamplesPanel() {
|
||||||
JPanel panel = new JPanel(new MigLayout("fill, wrap 3"));
|
JPanel panel = new JPanel(new MigLayout("fill, wrap 3"));
|
||||||
|
|
||||||
panel.setBorder(new LineBorder(new Color(0xACA899)));
|
panel.setBorder(new LineBorder(new Color(0xACA899)));
|
||||||
|
@ -207,8 +255,13 @@ public class EpisodeFormatDialog extends JDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected Episode getPreviewSample() {
|
private Match<Episode, File> previewSample() {
|
||||||
String sample = persistentSample.getValue();
|
return new Match<Episode, File>(previewSampleEpisode, previewSampleMediaFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Episode getPreviewSampleEpisode() {
|
||||||
|
String sample = Settings.userRoot().get("dialog.sample.episode");
|
||||||
|
|
||||||
if (sample != null) {
|
if (sample != null) {
|
||||||
try {
|
try {
|
||||||
|
@ -223,61 +276,75 @@ public class EpisodeFormatDialog extends JDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected boolean checkPreviewSample() {
|
private File getPreviewSampleMediaFile() {
|
||||||
// check if field is being edited
|
String sample = Settings.userRoot().get("dialog.sample.file");
|
||||||
if (preview.hasFocus()) {
|
|
||||||
|
if (sample != null) {
|
||||||
try {
|
try {
|
||||||
// try to parse text
|
return new File(sample);
|
||||||
preview.getFormatter().stringToValue(preview.getText());
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
preview.setForeground(errorColor);
|
Logger.getLogger(getClass().getName()).log(Level.WARNING, e.getMessage(), e);
|
||||||
// failed to parse text
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
preview.setForeground(defaultColor);
|
// default sample
|
||||||
return true;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected DefaultFormatterFactory createFormatterFactory(Format display) {
|
private ExecutorService createPreviewExecutor() {
|
||||||
DefaultFormatterFactory factory = new DefaultFormatterFactory();
|
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1));
|
||||||
|
|
||||||
factory.setEditFormatter(new SimpleFormatter(EpisodeFormat.getInstance()));
|
// only keep the latest task in the queue
|
||||||
|
executor.setRejectedExecutionHandler(new DiscardOldestPolicy());
|
||||||
|
|
||||||
if (display != null) {
|
return executor;
|
||||||
factory.setDisplayFormatter(new SimpleFormatter(display));
|
|
||||||
}
|
|
||||||
|
|
||||||
return factory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected boolean checkEpisodeFormat() {
|
private void checkFormatInBackground() {
|
||||||
Exception exception = null;
|
previewExecutor.execute(new SwingWorker<String, Void>() {
|
||||||
|
|
||||||
try {
|
private ScriptException warning = null;
|
||||||
Format format = new EpisodeExpressionFormat(editor.getText().trim());
|
|
||||||
|
|
||||||
// check if format produces empty strings
|
|
||||||
if (format.format(preview.getValue()).trim().isEmpty()) {
|
@Override
|
||||||
throw new IllegalArgumentException("Format must not be empty.");
|
protected String doInBackground() throws Exception {
|
||||||
|
EpisodeExpressionFormat format = new EpisodeExpressionFormat(editor.getText().trim());
|
||||||
|
|
||||||
|
String text = format.format(previewSample());
|
||||||
|
warning = format.scriptException();
|
||||||
|
|
||||||
|
// check if format produces empty strings
|
||||||
|
if (text.trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Format must not be empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update preview
|
|
||||||
preview.setFormatterFactory(createFormatterFactory(format));
|
|
||||||
} catch (Exception e) {
|
|
||||||
exception = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorMessage.setText(exception != null ? ExceptionUtilities.getRootCauseMessage(exception) : null);
|
@Override
|
||||||
errorMessage.setVisible(exception != null);
|
protected void done() {
|
||||||
|
Exception error = null;
|
||||||
|
|
||||||
preview.setVisible(exception == null);
|
try {
|
||||||
editor.setForeground(exception == null ? defaultColor : errorColor);
|
preview.setText(get());
|
||||||
|
} catch (Exception e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
return exception == null;
|
errorMessage.setText(error != null ? error.getCause().getMessage() : null);
|
||||||
|
errorMessage.setVisible(error != null);
|
||||||
|
|
||||||
|
warningMessage.setText(warning != null ? warning.getCause().getMessage() : null);
|
||||||
|
warningMessage.setVisible(warning != null);
|
||||||
|
|
||||||
|
preview.setVisible(error == null);
|
||||||
|
editor.setForeground(error == null ? defaultColor : errorColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -291,14 +358,6 @@ public class EpisodeFormatDialog extends JDialog {
|
||||||
|
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
dispose();
|
dispose();
|
||||||
|
|
||||||
if (checkEpisodeFormat()) {
|
|
||||||
persistentFormat.setValue(editor.getText());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checkPreviewSample()) {
|
|
||||||
persistentSample.setValue(EpisodeFormat.getInstance().format(preview.getValue()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final Action cancelAction = new AbstractAction("Cancel", ResourceManager.getIcon("dialog.cancel")) {
|
protected final Action cancelAction = new AbstractAction("Cancel", ResourceManager.getIcon("dialog.cancel")) {
|
||||||
|
@ -323,6 +382,7 @@ public class EpisodeFormatDialog extends JDialog {
|
||||||
public void actionPerformed(ActionEvent evt) {
|
public void actionPerformed(ActionEvent evt) {
|
||||||
try {
|
try {
|
||||||
finish(new EpisodeExpressionFormat(editor.getText()));
|
finish(new EpisodeExpressionFormat(editor.getText()));
|
||||||
|
Settings.userRoot().put("dialog.format", editor.getText());
|
||||||
} catch (ScriptException e) {
|
} catch (ScriptException e) {
|
||||||
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e), e);
|
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e), e);
|
||||||
}
|
}
|
||||||
|
@ -331,7 +391,7 @@ public class EpisodeFormatDialog extends JDialog {
|
||||||
|
|
||||||
|
|
||||||
public static Format showDialog(Component parent) {
|
public static Format showDialog(Component parent) {
|
||||||
EpisodeFormatDialog dialog = new EpisodeFormatDialog(parent != null ? SwingUtilities.getWindowAncestor(parent) : null);
|
EpisodeFormatDialog dialog = new EpisodeFormatDialog(TunedUtilities.getWindow(parent));
|
||||||
|
|
||||||
dialog.setVisible(true);
|
dialog.setVisible(true);
|
||||||
|
|
||||||
|
@ -362,10 +422,10 @@ public class EpisodeFormatDialog extends JDialog {
|
||||||
this.format = format;
|
this.format = format;
|
||||||
|
|
||||||
// initialize text
|
// initialize text
|
||||||
updateText(preview.getValue());
|
updateText(previewSample());
|
||||||
|
|
||||||
// bind text to preview
|
// bind text to preview
|
||||||
preview.addPropertyChangeListener("value", new PropertyChangeListener() {
|
EpisodeFormatDialog.this.addPropertyChangeListener("previewSample", new PropertyChangeListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void propertyChange(PropertyChangeEvent evt) {
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
|
@ -387,53 +447,41 @@ public class EpisodeFormatDialog extends JDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected static class SimpleFormatter extends AbstractFormatter {
|
protected static abstract class LazyDocumentAdapter implements DocumentListener {
|
||||||
|
|
||||||
private final Format format;
|
private final Timer timer = new Timer(200, new ActionListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
public SimpleFormatter(Format format) {
|
public LazyDocumentAdapter() {
|
||||||
this.format = format;
|
timer.setRepeats(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String valueToString(Object value) throws ParseException {
|
|
||||||
return format.format(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object stringToValue(String text) throws ParseException {
|
|
||||||
return format.parseObject(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected static class DocumentAdapter implements DocumentListener {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void changedUpdate(DocumentEvent e) {
|
public void changedUpdate(DocumentEvent e) {
|
||||||
update(e);
|
timer.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void insertUpdate(DocumentEvent e) {
|
public void insertUpdate(DocumentEvent e) {
|
||||||
update(e);
|
timer.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeUpdate(DocumentEvent e) {
|
public void removeUpdate(DocumentEvent e) {
|
||||||
update(e);
|
timer.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void update(DocumentEvent e) {
|
public abstract void update();
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,10 @@ syntax: <html><b>{</b> <b>}</b> ... expression, <b>n</b> ... name, <b>s</b> ...
|
||||||
example[0]: {n} - {s}.{e} - {t}
|
example[0]: {n} - {s}.{e} - {t}
|
||||||
|
|
||||||
# 1x01
|
# 1x01
|
||||||
example[1]: {n} - {if (s) s+'x'}{e.pad(2)}
|
example[1]: {n} - {s+'x'}{e.pad(2)}
|
||||||
|
|
||||||
# S01E01
|
# S01E01
|
||||||
example[2]: {n} - {if (s) 'S'+s.pad(2)}E{e.pad(2)}
|
example[2]: {n} - {'S'+s.pad(2)}E{e.pad(2)}
|
||||||
|
|
||||||
# uglyfy name
|
# uglyfy name
|
||||||
example[3]: {n.replace(/\\s/g,'.').toLowerCase()}
|
example[3]: {n.replace(/\\s/g,'.').toLowerCase()}
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui;
|
||||||
|
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.Dialog.ModalityType;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.SortedMap;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import javax.swing.AbstractAction;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
import javax.swing.JDialog;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JTabbedPane;
|
||||||
|
import javax.swing.JTable;
|
||||||
|
import javax.swing.table.AbstractTableModel;
|
||||||
|
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
import net.sourceforge.filebot.mediainfo.MediaInfo;
|
||||||
|
import net.sourceforge.filebot.mediainfo.MediaInfo.StreamKind;
|
||||||
|
import net.sourceforge.tuned.ui.TunedUtilities;
|
||||||
|
|
||||||
|
|
||||||
|
public class MediaInfoComponent extends JTabbedPane {
|
||||||
|
|
||||||
|
public MediaInfoComponent(Map<StreamKind, List<SortedMap<String, String>>> mediaInfo) {
|
||||||
|
insert(mediaInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void insert(Map<StreamKind, List<SortedMap<String, String>>> mediaInfo) {
|
||||||
|
// create tabs for all streams
|
||||||
|
for (Entry<StreamKind, List<SortedMap<String, String>>> entry : mediaInfo.entrySet()) {
|
||||||
|
for (SortedMap<String, String> parameters : entry.getValue()) {
|
||||||
|
addTab(entry.getKey().toString(), new JScrollPane(new JTable(new ParameterTableModel(parameters))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void showDialog(Component parent, File file) {
|
||||||
|
final JDialog dialog = new JDialog(TunedUtilities.getWindow(parent), "MediaInfo", ModalityType.DOCUMENT_MODAL);
|
||||||
|
|
||||||
|
JComponent c = (JComponent) dialog.getContentPane();
|
||||||
|
c.setLayout(new MigLayout("fill", "[align center]", "[fill][pref!]"));
|
||||||
|
|
||||||
|
MediaInfo mediaInfo = new MediaInfo();
|
||||||
|
mediaInfo.open(file);
|
||||||
|
|
||||||
|
MediaInfoComponent mediaInfoComponent = new MediaInfoComponent(mediaInfo.snapshot());
|
||||||
|
|
||||||
|
mediaInfo.close();
|
||||||
|
|
||||||
|
c.add(mediaInfoComponent, "grow, wrap");
|
||||||
|
|
||||||
|
c.add(new JButton(new AbstractAction("OK") {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
dialog.setVisible(false);
|
||||||
|
}
|
||||||
|
}), "wmin 80px, hmin 25px");
|
||||||
|
|
||||||
|
dialog.pack();
|
||||||
|
dialog.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected static class ParameterTableModel extends AbstractTableModel {
|
||||||
|
|
||||||
|
private final List<Entry<?, ?>> data;
|
||||||
|
|
||||||
|
|
||||||
|
public ParameterTableModel(Map<?, ?> data) {
|
||||||
|
this.data = new ArrayList<Entry<?, ?>>(data.entrySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRowCount() {
|
||||||
|
return data.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColumnCount() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName(int column) {
|
||||||
|
switch (column) {
|
||||||
|
case 0:
|
||||||
|
return "Parameter";
|
||||||
|
case 1:
|
||||||
|
return "Value";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getValueAt(int row, int column) {
|
||||||
|
switch (column) {
|
||||||
|
case 0:
|
||||||
|
return data.get(row).getKey();
|
||||||
|
case 1:
|
||||||
|
return data.get(row).getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,10 +33,10 @@ import javax.swing.SwingUtilities;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
import net.sourceforge.filebot.ResourceManager;
|
import net.sourceforge.filebot.ResourceManager;
|
||||||
import net.sourceforge.filebot.Settings;
|
import net.sourceforge.filebot.Settings;
|
||||||
|
import net.sourceforge.filebot.format.EpisodeExpressionFormat;
|
||||||
import net.sourceforge.filebot.similarity.Match;
|
import net.sourceforge.filebot.similarity.Match;
|
||||||
import net.sourceforge.filebot.similarity.NameSimilarityMetric;
|
import net.sourceforge.filebot.similarity.NameSimilarityMetric;
|
||||||
import net.sourceforge.filebot.similarity.SimilarityMetric;
|
import net.sourceforge.filebot.similarity.SimilarityMetric;
|
||||||
import net.sourceforge.filebot.ui.EpisodeExpressionFormat;
|
|
||||||
import net.sourceforge.filebot.ui.EpisodeFormatDialog;
|
import net.sourceforge.filebot.ui.EpisodeFormatDialog;
|
||||||
import net.sourceforge.filebot.ui.SelectDialog;
|
import net.sourceforge.filebot.ui.SelectDialog;
|
||||||
import net.sourceforge.filebot.web.AnidbClient;
|
import net.sourceforge.filebot.web.AnidbClient;
|
||||||
|
|
|
@ -23,7 +23,6 @@ import javax.swing.JLabel;
|
||||||
import javax.swing.JList;
|
import javax.swing.JList;
|
||||||
import javax.swing.JScrollPane;
|
import javax.swing.JScrollPane;
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
|
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
import net.sourceforge.filebot.ResourceManager;
|
import net.sourceforge.filebot.ResourceManager;
|
||||||
|
@ -196,7 +195,7 @@ class ValidateNamesDialog extends JDialog {
|
||||||
|
|
||||||
|
|
||||||
public static boolean showDialog(Component parent, List<String> source) {
|
public static boolean showDialog(Component parent, List<String> source) {
|
||||||
ValidateNamesDialog dialog = new ValidateNamesDialog(parent != null ? SwingUtilities.getWindowAncestor(parent) : null, source);
|
ValidateNamesDialog dialog = new ValidateNamesDialog(TunedUtilities.getWindow(parent), source);
|
||||||
|
|
||||||
dialog.setVisible(true);
|
dialog.setVisible(true);
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package net.sourceforge.tuned.ui;
|
||||||
|
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.Image;
|
import java.awt.Image;
|
||||||
|
@ -56,6 +57,17 @@ public final class TunedUtilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Window getWindow(Component component) {
|
||||||
|
if (component == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (component instanceof Window)
|
||||||
|
return (Window) component;
|
||||||
|
|
||||||
|
return SwingUtilities.getWindowAncestor(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Point getPreferredLocation(JDialog dialog) {
|
public static Point getPreferredLocation(JDialog dialog) {
|
||||||
Window owner = dialog.getOwner();
|
Window owner = dialog.getOwner();
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
package net.sourceforge.filebot.ui;
|
package net.sourceforge.filebot.format;
|
||||||
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
Loading…
Reference in New Issue