* improved episode format and format creation dialog

* differentiate between format errors and format warnings
This commit is contained in:
Reinhard Pointner 2009-04-04 19:36:12 +00:00
parent c83d4132ec
commit 7e6f485882
16 changed files with 790 additions and 183 deletions

View File

@ -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"
maxElementsInMemory="120"
@ -130,7 +130,20 @@
timeToIdleSeconds="300"
timeToLiveSeconds="300"
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>

View File

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

View File

@ -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 = "";
}

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
package net.sourceforge.filebot.ui;
package net.sourceforge.filebot.format;
import java.io.InputStreamReader;
@ -14,17 +14,21 @@ import java.util.regex.Pattern;
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
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 Object[] expressions;
private ScriptException lastException;
public ExpressionFormat(String format) throws ScriptException {
this.format = format;
@ -35,7 +39,7 @@ public abstract class ExpressionFormat extends Format {
protected ScriptEngine initScriptEngine() throws ScriptException {
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;
}
@ -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
public StringBuffer format(Object object, StringBuffer sb, FieldPosition pos) {
Bindings bindings = getBindings(object);
ScriptContext context = new SimpleScriptContext();
context.setBindings(bindings, ScriptContext.GLOBAL_SCOPE);
try {
for (Object snipped : expressions) {
if (snipped instanceof String) {
sb.append(snipped);
} else {
Object value = ((CompiledScript) snipped).eval(bindings);
if (snipped instanceof CompiledScript) {
try {
Object value = ((CompiledScript) snipped).eval(context);
if (value != null) {
sb.append(value);
if (value != null) {
sb.append(value);
}
} catch (ScriptException e) {
lastException = e;
}
} else {
sb.append(snipped);
}
}
} catch (ScriptException e) {
throw new IllegalArgumentException(e);
} finally {
dispose(bindings);
}
return sb;
}
protected void dispose(Bindings bindings) {
}
public ScriptException scriptException() {
return lastException;
}
@Override
public Object parseObject(String source, ParsePosition pos) {
throw new UnsupportedOperationException();

View File

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

View File

@ -11,44 +11,51 @@ import java.awt.Component;
import java.awt.Font;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.text.Format;
import java.text.ParseException;
import java.util.Arrays;
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.Logger;
import javax.script.ScriptException;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.InputVerifier;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFormattedTextField;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.JFormattedTextField.AbstractFormatter;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.border.LineBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.DefaultFormatterFactory;
import javax.swing.filechooser.FileNameExtensionFilter;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.ResourceManager;
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.EpisodeFormat;
import net.sourceforge.tuned.ExceptionUtilities;
import net.sourceforge.tuned.PreferencesMap.PreferencesEntry;
import net.sourceforge.tuned.ui.GradientStyle;
import net.sourceforge.tuned.ui.LinkButton;
import net.sourceforge.tuned.ui.TunedUtilities;
@ -60,16 +67,20 @@ public class EpisodeFormatDialog extends JDialog {
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"));
protected final JTextField editor = new JTextField();
private JLabel warningMessage = new JLabel(ResourceManager.getIcon("status.warning"));
private JLabel errorMessage = new JLabel(ResourceManager.getIcon("status.error"));
protected Color defaultColor = preview.getForeground();
protected Color errorColor = Color.red;
private Episode previewSampleEpisode = getPreviewSampleEpisode();
private File previewSampleMediaFile = getPreviewSampleMediaFile();
protected final PreferencesEntry<String> persistentFormat = Settings.userRoot().entry("dialog.format");
protected final PreferencesEntry<String> persistentSample = Settings.userRoot().entry("dialog.sample");
private ExecutorService previewExecutor = createPreviewExecutor();
private JTextField editor = new JTextField();
private Color defaultColor = preview.getForeground();
private Color errorColor = Color.red;
public EpisodeFormatDialog(Window owner) {
@ -77,17 +88,9 @@ public class EpisodeFormatDialog extends JDialog {
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
editor.setText(Settings.userRoot().get("dialog.format"));
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
JLabel title = new JLabel(this.getTitle());
title.setFont(title.getFont().deriveFont(BOLD));
@ -97,9 +100,13 @@ public class EpisodeFormatDialog extends JDialog {
header.setBackground(Color.white);
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(errorMessage, "gap indent, hidemode 3");
header.add(preview, "gap indent, hidemode 3, growx");
header.add(preview, "gap indent, hidemode 3, wmax 90%");
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"));
@ -121,35 +128,31 @@ public class EpisodeFormatDialog extends JDialog {
pane.add(header, "h 60px, growx, dock north");
pane.add(content, "grow");
pack();
setSize(485, 390);
header.setComponentPopupMenu(createPreviewSamplePopup());
setLocation(TunedUtilities.getPreferredLocation(this));
TunedUtilities.putActionForKeystroke(pane, KeyStroke.getKeyStroke("released ESCAPE"), cancelAction);
// update preview to current format
checkFormatInBackground();
// update format on change
editor.getDocument().addDocumentListener(new DocumentAdapter() {
editor.getDocument().addDocumentListener(new LazyDocumentAdapter() {
@Override
public void update(DocumentEvent evt) {
checkEpisodeFormat();
public void update() {
checkFormatInBackground();
}
});
// keep focus on preview, if current text doesn't fit episode format
preview.setInputVerifier(new InputVerifier() {
addPropertyChangeListener("previewSample", new PropertyChangeListener() {
@Override
public boolean verify(JComponent input) {
return checkPreviewSample();
}
});
// check edit format on change
preview.getDocument().addDocumentListener(new DocumentAdapter() {
@Override
public void update(DocumentEvent evt) {
checkPreviewSample();
public void propertyChange(PropertyChangeEvent evt) {
checkFormatInBackground();
}
});
@ -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"));
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"));
panel.setBorder(new LineBorder(new Color(0xACA899)));
@ -207,8 +255,13 @@ public class EpisodeFormatDialog extends JDialog {
}
protected Episode getPreviewSample() {
String sample = persistentSample.getValue();
private Match<Episode, File> previewSample() {
return new Match<Episode, File>(previewSampleEpisode, previewSampleMediaFile);
}
private Episode getPreviewSampleEpisode() {
String sample = Settings.userRoot().get("dialog.sample.episode");
if (sample != null) {
try {
@ -223,61 +276,75 @@ public class EpisodeFormatDialog extends JDialog {
}
protected boolean checkPreviewSample() {
// check if field is being edited
if (preview.hasFocus()) {
private File getPreviewSampleMediaFile() {
String sample = Settings.userRoot().get("dialog.sample.file");
if (sample != null) {
try {
// try to parse text
preview.getFormatter().stringToValue(preview.getText());
return new File(sample);
} catch (Exception e) {
preview.setForeground(errorColor);
// failed to parse text
return false;
Logger.getLogger(getClass().getName()).log(Level.WARNING, e.getMessage(), e);
}
}
preview.setForeground(defaultColor);
return true;
// default sample
return null;
}
protected DefaultFormatterFactory createFormatterFactory(Format display) {
DefaultFormatterFactory factory = new DefaultFormatterFactory();
private ExecutorService createPreviewExecutor() {
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) {
factory.setDisplayFormatter(new SimpleFormatter(display));
}
return factory;
return executor;
}
protected boolean checkEpisodeFormat() {
Exception exception = null;
private void checkFormatInBackground() {
previewExecutor.execute(new SwingWorker<String, Void>() {
try {
Format format = new EpisodeExpressionFormat(editor.getText().trim());
private ScriptException warning = null;
// check if format produces empty strings
if (format.format(preview.getValue()).trim().isEmpty()) {
throw new IllegalArgumentException("Format must not be empty.");
@Override
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);
errorMessage.setVisible(exception != null);
@Override
protected void done() {
Exception error = null;
preview.setVisible(exception == null);
editor.setForeground(exception == null ? defaultColor : errorColor);
try {
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);
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")) {
@ -323,6 +382,7 @@ public class EpisodeFormatDialog extends JDialog {
public void actionPerformed(ActionEvent evt) {
try {
finish(new EpisodeExpressionFormat(editor.getText()));
Settings.userRoot().put("dialog.format", editor.getText());
} catch (ScriptException 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) {
EpisodeFormatDialog dialog = new EpisodeFormatDialog(parent != null ? SwingUtilities.getWindowAncestor(parent) : null);
EpisodeFormatDialog dialog = new EpisodeFormatDialog(TunedUtilities.getWindow(parent));
dialog.setVisible(true);
@ -362,10 +422,10 @@ public class EpisodeFormatDialog extends JDialog {
this.format = format;
// initialize text
updateText(preview.getValue());
updateText(previewSample());
// bind text to preview
preview.addPropertyChangeListener("value", new PropertyChangeListener() {
EpisodeFormatDialog.this.addPropertyChangeListener("previewSample", new PropertyChangeListener() {
@Override
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) {
this.format = format;
public LazyDocumentAdapter() {
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
public void changedUpdate(DocumentEvent e) {
update(e);
timer.restart();
}
@Override
public void insertUpdate(DocumentEvent e) {
update(e);
timer.restart();
}
@Override
public void removeUpdate(DocumentEvent e) {
update(e);
timer.restart();
}
public void update(DocumentEvent e) {
}
public abstract void update();
}

View File

@ -4,10 +4,10 @@ syntax: <html><b>{</b> <b>}</b> ... expression, <b>n</b> ... name, <b>s</b> ...
example[0]: {n} - {s}.{e} - {t}
# 1x01
example[1]: {n} - {if (s) s+'x'}{e.pad(2)}
example[1]: {n} - {s+'x'}{e.pad(2)}
# 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
example[3]: {n.replace(/\\s/g,'.').toLowerCase()}

View File

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

View File

@ -33,10 +33,10 @@ import javax.swing.SwingUtilities;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.format.EpisodeExpressionFormat;
import net.sourceforge.filebot.similarity.Match;
import net.sourceforge.filebot.similarity.NameSimilarityMetric;
import net.sourceforge.filebot.similarity.SimilarityMetric;
import net.sourceforge.filebot.ui.EpisodeExpressionFormat;
import net.sourceforge.filebot.ui.EpisodeFormatDialog;
import net.sourceforge.filebot.ui.SelectDialog;
import net.sourceforge.filebot.web.AnidbClient;

View File

@ -23,7 +23,6 @@ import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.ResourceManager;
@ -196,7 +195,7 @@ class ValidateNamesDialog extends JDialog {
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);

View File

@ -3,6 +3,7 @@ package net.sourceforge.tuned.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
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) {
Window owner = dialog.getOwner();

View File

@ -1,5 +1,5 @@
package net.sourceforge.filebot.ui;
package net.sourceforge.filebot.format;
import static org.junit.Assert.assertEquals;