package net.filebot.ui.filter;
import static java.util.Collections.*;
import static net.filebot.Logging.*;
import static net.filebot.MediaTypes.*;
import static net.filebot.util.FileUtilities.*;
import java.awt.Color;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.concurrent.CancellationException;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
import javax.swing.BorderFactory;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import net.filebot.mediainfo.MediaInfo;
import net.filebot.mediainfo.MediaInfo.StreamKind;
import net.filebot.util.ui.LoadingOverlayPane;
import net.miginfocom.swing.MigLayout;
class MediaInfoTool extends Tool<TableModel> {
private JTable table = new JTable(new MediaInfoTableModel());
public MediaInfoTool() {
super("MediaInfo");
table.setAutoCreateRowSorter(true);
table.setAutoCreateColumnsFromModel(true);
table.setFillsViewportHeight(true);
table.setCellSelectionEnabled(true);
table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
table.setBackground(Color.white);
table.setGridColor(new Color(0xEEEEEE));
table.setRowHeight(25);
JScrollPane scrollPane = new JScrollPane(table);
scrollPane.setBorder(BorderFactory.createEmptyBorder());
setLayout(new MigLayout("insets 0, fill"));
add(new LoadingOverlayPane(scrollPane, this), "grow");
}
@Override
protected TableModel createModelInBackground(List<File> root) {
if (root.isEmpty()) {
return new MediaInfoTableModel();
}
List<File> files = listFiles(root, filter(VIDEO_FILES, AUDIO_FILES), HUMAN_NAME_ORDER);
Map<MediaInfoKey, String[]> data = new TreeMap<MediaInfoKey, String[]>();
try (MediaInfo mi = new MediaInfo()) {
IntStream.range(0, files.size()).forEach(f -> {
try {
mi.open(files.get(f));
mi.snapshot().forEach((kind, streams) -> {
IntStream.range(0, streams.size()).forEach(i -> {
streams.get(i).forEach((name, value) -> {
String[] values = data.computeIfAbsent(new MediaInfoKey(kind, i, name), k -> new String[files.size()]);
values[f] = value;
});
});
});
} catch (IllegalArgumentException e) {
debug.finest(e::toString);
} catch (Exception e) {
debug.warning(e::toString);
}
if (Thread.interrupted()) {
throw new CancellationException();
}
});
}
return new MediaInfoTableModel(data.isEmpty() ? emptyList() : files, data);
}
@Override
protected void setModel(TableModel model) {
table.setModel(model);
table.setAutoResizeMode(table.getRowCount() > 0 ? JTable.AUTO_RESIZE_OFF : JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
TableColumnModel columnModel = table.getColumnModel();
IntStream.range(0, columnModel.getColumnCount()).forEach(i -> columnModel.getColumn(i).setMinWidth(150));
}
private static class MediaInfoKey implements Comparable<MediaInfoKey> {
public final StreamKind kind;
public final int stream;
public final String name;
private static final Pattern strip = Pattern.compile("[^a-z]", Pattern.CASE_INSENSITIVE);
public MediaInfoKey(StreamKind kind, int stream, String name) {
this.kind = kind;
this.stream = stream;
this.name = strip.matcher(name).replaceAll("");
}
@Override
public boolean equals(Object obj) {
if (obj instanceof MediaInfoKey) {
MediaInfoKey other = (MediaInfoKey) obj;
return kind == other.kind && stream == other.stream && name.equals(other.name);
}
return false;
}
@Override
public int hashCode() {
return kind.ordinal() + (stream << 8) + name.hashCode();
}
@Override
public int compareTo(MediaInfoKey other) {
if (kind != other.kind)
return kind.compareTo(other.kind);
if (stream != other.stream)
return Integer.compare(stream, other.stream);
else
return name.compareTo(other.name);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (kind != StreamKind.General) {
sb.append(kind.name());
if (stream > 0) {
sb.append('[').append(stream).append(']');
}
sb.append('.');
}
return sb.append(name).toString();
}
}
private static class MediaInfoTableModel extends AbstractTableModel {
private final MediaInfoKey[] keys;
private final String[][] values;
private final String[] files;
private final Class<?>[] columnClass;
public MediaInfoTableModel() {
this(emptyList(), emptyMap());
}
public MediaInfoTableModel(List<File> files, Map<MediaInfoKey, String[]> values) {
this.keys = values.keySet().toArray(new MediaInfoKey[0]);
this.values = values.values().toArray(new String[0][]);
this.files = files.stream().map(File::getName).toArray(String[]::new);
this.columnClass = new Class<?>[getColumnCount()];
}
public int getHeaderColumnCount() {
return 1;
}
@Override
public int getColumnCount() {
return keys.length + getHeaderColumnCount();
}
@Override
public String getColumnName(int column) {
switch (column) {
case 0:
return "File";
default:
return keys[column - getHeaderColumnCount()].toString();
}
}
private boolean isNumber(String s) {
try {
Double.parseDouble(s);
return true;
} catch (Exception e) {
return false;
}
}
@Override
public Class<?> getColumnClass(int column) {
int c = column - getHeaderColumnCount();
if (c < 0) {
return String.class;
}
if (columnClass[c] != null) {
return columnClass[c];
}
if (IntStream.range(0, files.length).mapToObj(i -> values[c][i]).filter(Objects::nonNull).allMatch(this::isNumber)) {
columnClass[c] = Number.class;
return columnClass[c];
}
columnClass[c] = String.class;
return columnClass[c];
}
@Override
public int getRowCount() {
return files.length;
}
@Override
public String getValueAt(int row, int column) {
switch (column) {
case 0:
return files[row];
default:
return values[column - getHeaderColumnCount()][row];
}
}
}
}