diff --git a/source/net/sourceforge/filebot/format/AssociativeScriptObject.java b/source/net/sourceforge/filebot/format/AssociativeScriptObject.java new file mode 100644 index 00000000..f4e51794 --- /dev/null +++ b/source/net/sourceforge/filebot/format/AssociativeScriptObject.java @@ -0,0 +1,169 @@ + +package net.sourceforge.filebot.format; + + +import java.util.Comparator; +import java.util.Map; +import java.util.TreeMap; + +import sun.org.mozilla.javascript.internal.Scriptable; + + +class AssociativeScriptObject implements Scriptable { + + /** + * Map allowing look-up of values by a fault-tolerant key as specified by the defining key. + * + * @see {@link #definingKey(String)} + */ + protected final TreeMap properties = new TreeMap(new Comparator() { + + @Override + public int compare(String s1, String s2) { + return definingKey(s1).compareTo(definingKey(s2)); + } + }); + + + /** + * The Java constructor + */ + public AssociativeScriptObject(Map properties) { + this.properties.putAll(properties); + } + + + protected String definingKey(String s) { + // letters and digits are defining, everything else will be ignored + return s.replaceAll("[^\\p{Alnum}]", "").toLowerCase(); + } + + + /** + * Defines properties available by name. + * + * @param name the name of the property + * @param start the object where lookup began + */ + public boolean has(String name, Scriptable start) { + return properties.containsKey(name); + } + + + /** + * Get the property with the given name. + * + * @param name the property name + * @param start the object where the lookup began + */ + public Object get(String name, Scriptable start) { + Object value = properties.get(name); + + if (value == null) + throw new BindingException(name, "undefined"); + + return value; + } + + + /** + * Defines properties available by index. + * + * @param index the index of the property + * @param start the object where lookup began + */ + public boolean has(int index, Scriptable start) { + // get property by index not supported + return false; + } + + + /** + * Get property by index. + * + * @param index the index of the property + * @param start the object where the lookup began + */ + public Object get(int index, Scriptable start) { + // get property by index not supported + throw new BindingException(String.valueOf(index), "undefined"); + } + + + /** + * Get property names. + */ + public Object[] getIds() { + return properties.keySet().toArray(); + } + + + /** + * Returns the name of this JavaScript class. + */ + public String getClassName() { + return getClass().getSimpleName(); + } + + + /** + * Returns the string value of this object. + */ + @SuppressWarnings("unchecked") + @Override + public Object getDefaultValue(Class typeHint) { + return this.toString(); + } + + + @Override + public String toString() { + return getClassName() + properties.entrySet().toString(); + } + + + public void put(String name, Scriptable start, Object value) { + // ignore, object is immutable + } + + + public void put(int index, Scriptable start, Object value) { + // ignore, object is immutable + } + + + public void delete(String id) { + // ignore, object is immutable + } + + + public void delete(int index) { + // ignore, object is immutable + } + + + public Scriptable getPrototype() { + return null; + } + + + public void setPrototype(Scriptable prototype) { + // ignore, don't care about prototype + } + + + public Scriptable getParentScope() { + return null; + } + + + public void setParentScope(Scriptable parent) { + // ignore, don't care about scope + } + + + public boolean hasInstance(Scriptable value) { + return false; + } + +} diff --git a/source/net/sourceforge/filebot/format/BindingException.java b/source/net/sourceforge/filebot/format/BindingException.java index c262b66a..ee0b9d2c 100644 --- a/source/net/sourceforge/filebot/format/BindingException.java +++ b/source/net/sourceforge/filebot/format/BindingException.java @@ -9,6 +9,11 @@ public class BindingException extends RuntimeException { } + public BindingException(String binding, String innerMessage) { + this(binding, innerMessage, null); + } + + public BindingException(String binding, String innerMessage, Throwable cause) { this(String.format("BindingError: \"%s\": %s", binding, innerMessage), cause); } diff --git a/source/net/sourceforge/filebot/format/EpisodeFormatBindingBean.java b/source/net/sourceforge/filebot/format/EpisodeFormatBindingBean.java index a4c93580..e3d7ada8 100644 --- a/source/net/sourceforge/filebot/format/EpisodeFormatBindingBean.java +++ b/source/net/sourceforge/filebot/format/EpisodeFormatBindingBean.java @@ -8,7 +8,6 @@ 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; @@ -81,7 +80,7 @@ public class EpisodeFormatBindingBean { @Define("hi") public String getHeightAndInterlacement() { String height = getMediaInfo(StreamKind.Video, 0, "Height"); - String interlacement = getMediaInfo(StreamKind.Video, 0, "Interlacement/String"); + String interlacement = getMediaInfo(StreamKind.Video, 0, "Interlacement"); if (height == null || interlacement == null) return null; @@ -120,37 +119,37 @@ public class EpisodeFormatBindingBean { return null; } - + @Define("general") - public SortedMap getGeneralMediaInfo() { - return getMediaInfo().snapshot(StreamKind.General, 0); + public Object getGeneralMediaInfo() { + return new AssociativeScriptObject(getMediaInfo().snapshot(StreamKind.General, 0)); } - + @Define("video") - public SortedMap getVideoInfo() { - return getMediaInfo().snapshot(StreamKind.Video, 0); + public Object getVideoInfo() { + return new AssociativeScriptObject(getMediaInfo().snapshot(StreamKind.Video, 0)); } @Define("audio") - public SortedMap getAudioInfo() { - return getMediaInfo().snapshot(StreamKind.Audio, 0); + public Object getAudioInfo() { + return new AssociativeScriptObject(getMediaInfo().snapshot(StreamKind.Audio, 0)); } - - @Define("text") - public SortedMap getTextInfo() { - return getMediaInfo().snapshot(StreamKind.Text, 0); - } - - - @Define("image") - public SortedMap getImageInfo() { - return getMediaInfo().snapshot(StreamKind.Image, 0); - } + @Define("text") + public Object getTextInfo() { + return new AssociativeScriptObject(getMediaInfo().snapshot(StreamKind.Text, 0)); + } + + + @Define("image") + public Object getImageInfo() { + return new AssociativeScriptObject(getMediaInfo().snapshot(StreamKind.Image, 0)); + } + public synchronized MediaInfo getMediaInfo() { if (mediaFile == null) { diff --git a/source/net/sourceforge/filebot/mediainfo/MediaInfo.java b/source/net/sourceforge/filebot/mediainfo/MediaInfo.java index fd5575bb..6b7984fd 100644 --- a/source/net/sourceforge/filebot/mediainfo/MediaInfo.java +++ b/source/net/sourceforge/filebot/mediainfo/MediaInfo.java @@ -6,10 +6,9 @@ import java.io.Closeable; import java.io.File; import java.util.ArrayList; import java.util.EnumMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; import com.sun.jna.Pointer; import com.sun.jna.WString; @@ -80,14 +79,14 @@ public class MediaInfo implements Closeable { } - public Map>> snapshot() { - Map>> mediaInfo = new EnumMap>>(StreamKind.class); + public Map>> snapshot() { + Map>> mediaInfo = new EnumMap>>(StreamKind.class); for (StreamKind streamKind : StreamKind.values()) { int streamCount = streamCount(streamKind); if (streamCount > 0) { - List> streamInfoList = new ArrayList>(streamCount); + List> streamInfoList = new ArrayList>(streamCount); for (int i = 0; i < streamCount; i++) { streamInfoList.add(snapshot(streamKind, i)); @@ -101,8 +100,8 @@ public class MediaInfo implements Closeable { } - public SortedMap snapshot(StreamKind streamKind, int streamNumber) { - TreeMap streamInfo = new TreeMap(String.CASE_INSENSITIVE_ORDER); + public Map snapshot(StreamKind streamKind, int streamNumber) { + Map streamInfo = new LinkedHashMap(); for (int i = 0, count = parameterCount(streamKind, streamNumber); i < count; i++) { String value = get(streamKind, streamNumber, i, InfoKind.Text); diff --git a/source/net/sourceforge/filebot/ui/EpisodeFormatDialog.java b/source/net/sourceforge/filebot/ui/EpisodeFormatDialog.java index 18a58004..acecb43e 100644 --- a/source/net/sourceforge/filebot/ui/EpisodeFormatDialog.java +++ b/source/net/sourceforge/filebot/ui/EpisodeFormatDialog.java @@ -109,8 +109,8 @@ public class EpisodeFormatDialog extends JDialog { header.add(progressIndicator, "pos 1al 0al, hidemode 3"); header.add(title, "wrap unrel:push"); 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"); + header.add(errorMessage, "gap indent, hidemode 3, wmax 90%, newline"); + header.add(warningMessage, "gap indent, hidemode 3, wmax 90%, newline"); JPanel content = new JPanel(new MigLayout("insets dialog, nogrid, fill")); @@ -208,7 +208,7 @@ public class EpisodeFormatDialog extends JDialog { } catch (LinkageError e) { // MediaInfo native library is missing -> notify user Logger.getLogger("ui").log(Level.SEVERE, e.getMessage(), e); - + // rethrow error throw e; } @@ -353,7 +353,7 @@ public class EpisodeFormatDialog extends JDialog { error = e; } - errorMessage.setText(error != null ? error.getCause().getMessage() : null); + errorMessage.setText(error != null ? ExceptionUtilities.getRootCauseMessage(error) : null); errorMessage.setVisible(error != null); warningMessage.setText(warning != null ? warning.getCause().getMessage() : null); diff --git a/source/net/sourceforge/filebot/ui/MediaInfoComponent.java b/source/net/sourceforge/filebot/ui/MediaInfoComponent.java index 0e751a7f..7f2d57aa 100644 --- a/source/net/sourceforge/filebot/ui/MediaInfoComponent.java +++ b/source/net/sourceforge/filebot/ui/MediaInfoComponent.java @@ -9,7 +9,6 @@ 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; @@ -29,16 +28,24 @@ import net.sourceforge.tuned.ui.TunedUtilities; public class MediaInfoComponent extends JTabbedPane { - public MediaInfoComponent(Map>> mediaInfo) { + public MediaInfoComponent(Map>> mediaInfo) { insert(mediaInfo); } - public void insert(Map>> mediaInfo) { + public void insert(Map>> mediaInfo) { // create tabs for all streams - for (Entry>> entry : mediaInfo.entrySet()) { - for (SortedMap parameters : entry.getValue()) { - addTab(entry.getKey().toString(), new JScrollPane(new JTable(new ParameterTableModel(parameters)))); + for (Entry>> entry : mediaInfo.entrySet()) { + for (Map parameters : entry.getValue()) { + JTable table = new JTable(new ParameterTableModel(parameters)); + + // allow sorting + table.setAutoCreateRowSorter(true); + + // sort by parameter name + table.getRowSorter().toggleSortOrder(0); + + addTab(entry.getKey().toString(), new JScrollPane(table)); } } } @@ -75,11 +82,11 @@ public class MediaInfoComponent extends JTabbedPane { protected static class ParameterTableModel extends AbstractTableModel { - private final List> data; + private final List> data; - public ParameterTableModel(Map data) { - this.data = new ArrayList>(data.entrySet()); + public ParameterTableModel(Map data) { + this.data = new ArrayList>(data.entrySet()); }