net.straylightlabs.tivolibre.DecoderApp Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tivo-libre Show documentation
Show all versions of tivo-libre Show documentation
TivoLibre is a Java library for decoding TiVo files to standard MPEG files
The newest version!
/*
* Copyright 2015 Todd Kulesza .
*
* This file is part of TivoLibre. TivoLibre is derived from
* TivoDecode 0.4.4 by Jeremy Drake. See the LICENSE-TivoDecode
* file for the licensing terms for TivoDecode.
*
* TivoLibre is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* TivoLibre is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with TivoLibre. If not, see .
*
*/
package net.straylightlabs.tivolibre;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import org.apache.commons.cli.*;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.prefs.Preferences;
/**
* Simple driver application for command line use.
*/
public class DecoderApp {
private Options options;
private CommandLine cli;
private final static org.slf4j.Logger logger = LoggerFactory.getLogger(DecoderApp.class);
private static final String PREF_MAK = "mak";
public static void main(String args[]) {
DecoderApp app = new DecoderApp();
if (app.parseCommandLineArgs(args)) {
app.run();
}
}
public boolean parseCommandLineArgs(String[] args) {
try {
options = buildCliOptions();
CommandLineParser parser = new DefaultParser();
cli = parser.parse(options, args);
} catch (ParseException e) {
logger.error("Parsing command line options failed: {}", e.getLocalizedMessage());
showUsage();
return false;
}
return true;
}
public void run() {
if (options == null || cli == null) {
throw new IllegalStateException("Must call parseCommandLineArgs() before calling run()");
}
if (cli.hasOption('v')) {
System.out.format("TivoLibre %s%n", TivoDecoder.VERSION);
System.exit(0);
} else if (cli.hasOption('h')) {
showUsage();
System.exit(0);
}
Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
if (cli.hasOption('d')) {
root.setLevel(Level.DEBUG);
} else {
root.setLevel(Level.ERROR);
}
DecoderOptions decoderOptions = new DecoderOptions();
decoderOptions.mak = loadMak();
if (!cli.hasOption('m')) {
if (decoderOptions.mak == null) {
System.err.format("Error: You must provide your media access key%n");
showUsage();
System.exit(1);
}
} else {
decoderOptions.mak = cli.getOptionValue('m');
saveMak(decoderOptions.mak);
}
if (cli.hasOption("compat-mode")) {
logger.debug("Running in compatibility mode");
decoderOptions.compatibilityMode = true;
}
if (cli.hasOption('D')) {
decoderOptions.dumpMetadata = true;
}
if (cli.hasOption('p')) {
decoderOptions.pytivoMetadata = true;
}
if (cli.hasOption('x')) {
decoderOptions.noVideo = true;
}
try {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
if (cli.hasOption('i')) {
inputStream = new FileInputStream(cli.getOptionValue('i'));
} else {
inputStream = System.in;
}
if (cli.hasOption('o')) {
String outputLocation = cli.getOptionValue('o');
outputStream = new FileOutputStream(outputLocation);
decoderOptions.pytivoMetadataPath = appendToPath(Paths.get(outputLocation), ".txt");
} else {
outputStream = System.out;
decoderOptions.pytivoMetadataPath = Paths.get("pytivo.txt");
}
decode(inputStream, outputStream, decoderOptions);
} catch (FileNotFoundException e) {
logger.error("Input file {} not found: {}", cli.getOptionValue('i'), e.getLocalizedMessage());
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
} catch (IOException e) {
logger.error("IOException: {}", e.getLocalizedMessage(), e);
}
}
private Path appendToPath(Path path, String suffix) {
assert(path != null);
Path dir = path.getParent();
Path file = path.getFileName();
logger.info("dir = '{}', file = '{}'", dir, file);
if (dir != null) {
return Paths.get(dir.toString(), file.toString() + suffix);
} else {
return Paths.get(file.toString() + suffix);
}
}
private void showUsage() {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("java -jar tivo-libre.jar -i input.TiVo -o output.mpg -m 0123456789", options);
}
private void decode(InputStream input, OutputStream output, DecoderOptions options) {
try (BufferedInputStream inputStream = new BufferedInputStream(input);
BufferedOutputStream outputStream = new BufferedOutputStream(output)) {
TivoDecoder decoder = new TivoDecoder.Builder()
.input(inputStream).output(outputStream)
.mak(options.mak).compatibilityMode(options.compatibilityMode)
.build();
boolean decodeSuccessful;
if (options.noVideo) {
decodeSuccessful = decoder.decodeMetadata();
} else {
decodeSuccessful = decoder.decode();
}
if (decodeSuccessful && options.dumpMetadata) {
dumpMetadata(decoder);
}
if (decodeSuccessful && options.pytivoMetadata) {
dumpPytivoMetadata(decoder, options.pytivoMetadataPath);
}
} catch (FileNotFoundException e) {
logger.error("Error: {}", e.getLocalizedMessage());
} catch (IOException e) {
logger.error("Error reading/writing files: {}", e.getLocalizedMessage());
}
}
private Options buildCliOptions() {
Options options = new Options();
options.addOption("D", "metadata", false, "Dump TiVo recording metadata to XML files");
options.addOption("d", "debug", false, "Show debugging information while decoding");
options.addOption("h", "help", false, "Show this help message and exit");
options.addOption("p", "pytivo", false, "Dump TiVo recording metadata in pyTivo format");
options.addOption("v", "version", false, "Show version and exit");
options.addOption("x", "no-video", false, "Exit after processing metadata; doesn't decode the video");
Option option = Option.builder().longOpt("compat-mode").desc("Don't fix problems in the TiVo file; produces output that " +
"is binary compatible with the TiVo DirectShow filter").build();
options.addOption(option);
option = Option.builder("o").argName("FILENAME").longOpt("output").hasArg().
desc("Output file (defaults to standard output)").build();
options.addOption(option);
option = Option.builder("i").argName("FILENAME").longOpt("input").hasArg().
desc("File to decode (defaults to standard input)").build();
options.addOption(option);
option = Option.builder("m").argName("MAK").longOpt("mak").hasArg().
desc("Your media access key (will be saved between program executions)").build();
options.addOption(option);
return options;
}
private void dumpMetadata(TivoDecoder decoder) {
int counter = 0;
for (Document d : decoder.getMetadata()) {
String chunkFilename = String.format("chunk-%02d.xml", counter++);
logger.debug("Saving metadata chunk {} to {}...", counter, chunkFilename);
try {
OutputStream out = new FileOutputStream(chunkFilename);
printDocument(d, out);
} catch (IOException | TransformerException e) {
logger.error("Error saving file {}: ", chunkFilename, e);
}
}
}
private void dumpPytivoMetadata(TivoDecoder decoder, Path metadataPath) {
decoder.saveMetadata(metadataPath);
}
// From http://stackoverflow.com/questions/2325388/java-shortest-way-to-pretty-print-to-stdout-a-org-w3c-dom-document
private static void printDocument(Document doc, OutputStream out) throws IOException, TransformerException {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
transformer.transform(new DOMSource(doc),
new StreamResult(new OutputStreamWriter(out, "UTF-8")));
}
private void saveMak(String mak) {
Preferences prefs = getPrefs();
prefs.put(PREF_MAK, mak);
}
private String loadMak() {
Preferences prefs = getPrefs();
return prefs.get(PREF_MAK, null);
}
private Preferences getPrefs() {
return Preferences.userNodeForPackage(DecoderApp.class);
}
private static class DecoderOptions {
String mak;
boolean compatibilityMode;
boolean noVideo;
boolean dumpMetadata;
boolean pytivoMetadata;
Path pytivoMetadataPath;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy