+ added support for re-encoding downloaded subtitles as .srt using a given charset and optionally changing the subtitle timing

This commit is contained in:
Reinhard Pointner 2011-09-06 04:45:48 +00:00
parent 332f371636
commit 38210a5565
8 changed files with 223 additions and 16 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 833 B

View File

@ -0,0 +1,47 @@
package net.sourceforge.filebot.subtitle;
import java.io.Closeable;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Formatter;
import java.util.Locale;
import java.util.TimeZone;
public class SubRipWriter implements Closeable {
private final DateFormat timeFormat;
private final Formatter out;
private int lineNumber = 0;
public SubRipWriter(Appendable out) {
this.out = new Formatter(out, Locale.ROOT);
// format used to create time stamps (e.g. 00:02:26,407 --> 00:02:31,356)
timeFormat = new SimpleDateFormat("HH:mm:ss,SSS", Locale.ROOT);
timeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
}
public void write(SubtitleElement element) {
// write a single subtitle in SubRip format, e.g.
// 1
// 00:00:20,000 --> 00:00:24,400
// Altocumulus clouds occur between six thousand
out.format("%d%n", ++lineNumber);
out.format("%s --> %s%n", timeFormat.format(element.getStart()), timeFormat.format(element.getEnd()));
out.format("%s%n%n", element.getText());
}
@Override
public void close() throws IOException {
out.close();
}
}

View File

@ -2,6 +2,7 @@
package net.sourceforge.filebot.subtitle;
import static java.util.regex.Pattern.*;
import static net.sourceforge.tuned.StringUtilities.*;
import java.text.DateFormat;
@ -12,7 +13,7 @@ import java.util.regex.Pattern;
public class SubViewerReader extends SubtitleReader {
private final DateFormat timeFormat = new SubtitleTimeFormat();
private final Pattern newline = Pattern.compile(Pattern.quote("[br]"), Pattern.CASE_INSENSITIVE);
private final Pattern newline = compile(quote("[br]"), CASE_INSENSITIVE);
public SubViewerReader(Readable source) {

View File

@ -44,7 +44,7 @@ public enum SubtitleFormat {
public ExtensionFileFilter getFilter() {
return MediaTypes.getDefaultFilter("subtitle/" + this);
return MediaTypes.getDefaultFilter("subtitle/" + this.name());
}
}

View File

@ -14,7 +14,6 @@ import java.util.logging.Logger;
public abstract class SubtitleReader implements Iterator<SubtitleElement>, Closeable {
protected final Scanner scanner;
protected SubtitleElement current;

View File

@ -6,6 +6,7 @@ import static net.sourceforge.filebot.MediaTypes.*;
import static net.sourceforge.filebot.ui.NotificationLogging.*;
import static net.sourceforge.filebot.ui.panel.subtitle.SubtitleUtilities.*;
import static net.sourceforge.tuned.FileUtilities.*;
import static net.sourceforge.tuned.ui.TunedUtilities.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
@ -278,7 +279,7 @@ class SubtitleDownloadComponent extends JComponent {
viewer.getTitleLabel().setText("Subtitle Viewer");
viewer.getInfoLabel().setText(file.getPath());
viewer.setData(decode(file));
viewer.setData(decodeSubtitles(file));
viewer.setVisible(true);
}
@ -289,23 +290,61 @@ class SubtitleDownloadComponent extends JComponent {
// single file
MemoryFile file = (MemoryFile) selection[0];
JFileChooser fileChooser = new JFileChooser();
fileChooser.setSelectedFile(new File(validateFileName(file.getName())));
JFileChooser fc = new JFileChooser();
fc.setSelectedFile(new File(validateFileName(file.getName())));
if (fileChooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
write(file.getData(), fileChooser.getSelectedFile());
if (fc.showSaveDialog(getWindow(this)) == JFileChooser.APPROVE_OPTION) {
write(file.getData(), fc.getSelectedFile());
}
} else {
// multiple files
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
JFileChooser fc = new JFileChooser();
fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
if (fileChooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
File folder = fileChooser.getSelectedFile();
if (fc.showSaveDialog(getWindow(this)) == JFileChooser.APPROVE_OPTION) {
File folder = fc.getSelectedFile();
for (Object object : selection) {
MemoryFile file = (MemoryFile) object;
write(file.getData(), new File(folder, validateFileName(file.getName())));
File destination = new File(folder, validateFileName(file.getName()));
write(file.getData(), destination);
}
}
}
} catch (IOException e) {
UILogger.log(Level.WARNING, e.getMessage(), e);
}
}
private void export(Object[] selection) {
try {
if (selection.length == 1) {
// single file
MemoryFile file = (MemoryFile) selection[0];
SubtitleFileChooser sf = new SubtitleFileChooser();
// normalize name and auto-adjust extension
String ext = sf.getSelectedFormat().getFilter().extensions()[0];
String name = validateFileName(getNameWithoutExtension(file.getName()));
sf.setSelectedFile(new File(name + "." + ext));
if (sf.showSaveDialog(getWindow(this)) == JFileChooser.APPROVE_OPTION) {
exportSubtitles(decodeSubtitles(file), sf.getSelectedFile(), sf.getSelectedEncoding(), sf.getSelectedFormat(), sf.getTimingOffset());
}
} else {
// multiple files
SubtitleFileChooser sf = new SubtitleFileChooser();
sf.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
if (sf.showSaveDialog(getWindow(this)) == JFileChooser.APPROVE_OPTION) {
File folder = sf.getSelectedFile();
for (Object object : selection) {
MemoryFile file = (MemoryFile) object;
File destination = new File(folder, validateFileName(file.getName()));
exportSubtitles(decodeSubtitles(file), destination, sf.getSelectedEncoding(), sf.getSelectedFormat(), sf.getTimingOffset());
}
}
}
@ -446,8 +485,8 @@ class SubtitleDownloadComponent extends JComponent {
}
});
// Save as ...
contextMenu.add(new AbstractAction("Save as ...") {
// Save As...
contextMenu.add(new AbstractAction("Save As...", ResourceManager.getIcon("action.save")) {
@Override
public void actionPerformed(ActionEvent evt) {
@ -455,6 +494,15 @@ class SubtitleDownloadComponent extends JComponent {
}
});
// Export...
contextMenu.add(new AbstractAction("Export...", ResourceManager.getIcon("action.export")) {
@Override
public void actionPerformed(ActionEvent evt) {
export(selection);
}
});
contextMenu.show(e.getComponent(), e.getX(), e.getY());
}
}

View File

@ -0,0 +1,85 @@
package net.sourceforge.filebot.ui.panel.subtitle;
import static java.util.Collections.*;
import java.nio.charset.Charset;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.subtitle.SubtitleFormat;
public class SubtitleFileChooser extends JFileChooser {
protected final JComboBox format = new JComboBox();
protected final JComboBox encoding = new JComboBox();
protected final JSpinner offset = new JSpinner(new SpinnerNumberModel(0, -14400000, 14400000, 100));
public SubtitleFileChooser() {
setAccessory(createAcessory());
setDefaultOptions();
}
protected void setDefaultOptions() {
setFormatOptions(singleton(SubtitleFormat.SubRip));
Set<Charset> encodings = new LinkedHashSet<Charset>(2);
encodings.add(Charset.forName("UTF-8")); // UTF-8 as default charset
encodings.add(Charset.defaultCharset()); // allow default system encoding to be used as well
setEncodingOptions(encodings);
}
protected JComponent createAcessory() {
JPanel acessory = new JPanel(new MigLayout("nogrid"));
acessory.add(new JLabel("Encoding:"), "wrap rel");
acessory.add(encoding, "sg w, wrap para");
acessory.add(new JLabel("Format:"), "wrap rel");
acessory.add(format, "sg w, wrap para");
acessory.add(new JLabel("Timing Offset:"), "wrap rel");
acessory.add(offset, "wmax 50px");
acessory.add(new JLabel("ms"));
return acessory;
}
public void setEncodingOptions(Set<Charset> options) {
encoding.setModel(new DefaultComboBoxModel(options.toArray()));
}
public Charset getSelectedEncoding() {
return (Charset) encoding.getSelectedItem();
}
public void setFormatOptions(Set<SubtitleFormat> options) {
format.setModel(new DefaultComboBoxModel(options.toArray()));
}
public SubtitleFormat getSelectedFormat() {
return (SubtitleFormat) format.getSelectedItem();
}
public long getTimingOffset() {
return (Integer) offset.getValue();
}
}

View File

@ -2,18 +2,23 @@
package net.sourceforge.filebot.ui.panel.subtitle;
import static java.lang.Math.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import com.ibm.icu.text.CharsetDetector;
import net.sourceforge.filebot.subtitle.SubRipWriter;
import net.sourceforge.filebot.subtitle.SubtitleElement;
import net.sourceforge.filebot.subtitle.SubtitleFormat;
import net.sourceforge.filebot.subtitle.SubtitleReader;
@ -25,7 +30,7 @@ final class SubtitleUtilities {
/**
* Detect charset and parse subtitle file even if extension is invalid
*/
public static List<SubtitleElement> decode(MemoryFile file) throws IOException {
public static List<SubtitleElement> decodeSubtitles(MemoryFile file) throws IOException {
// detect charset and read text content
CharsetDetector detector = new CharsetDetector();
detector.setDeclaredEncoding("UTF-8");
@ -68,6 +73,28 @@ final class SubtitleUtilities {
}
/**
* Write a subtitle file to disk
*/
public static void exportSubtitles(List<SubtitleElement> data, File destination, Charset encoding, SubtitleFormat format, long timingOffset) throws IOException {
if (format != SubtitleFormat.SubRip)
throw new IllegalArgumentException("Format not supported");
StringBuilder buffer = new StringBuilder(4 * 1024);
SubRipWriter out = new SubRipWriter(buffer);
for (SubtitleElement it : data) {
if (timingOffset != 0)
it = new SubtitleElement(max(0, it.getStart() + timingOffset), max(0, it.getEnd() + timingOffset), it.getText());
out.write(it);
}
// write to file
write(encoding.encode(CharBuffer.wrap(buffer)), destination);
}
/**
* Write {@link ByteBuffer} to {@link File}.
*/