# HG changeset patch # User mdd # Date 1511563350 -3600 # Node ID f436a7f94c6a64355ffd137bfc4732e135c74804 # Parent 1bf77800104169996ed821bcdd55734a41d66915 pylint diff -r 1bf778001041 -r f436a7f94c6a ts2mkv.py --- a/ts2mkv.py Fri Nov 24 23:11:58 2017 +0100 +++ b/ts2mkv.py Fri Nov 24 23:42:30 2017 +0100 @@ -1,10 +1,22 @@ #!/usr/bin/env python +""" +DVB-TS to MKV kung-fu +2017 by mdd + +Toolkit / executable to automagically convert DVB recordings to h264 mkv. +Automatic audio stream selection (deu/eng) +Automatic crop detection to remove cinematic bars +""" import subprocess from eit import readeit, eitinfo import os, shlex def filter_lines(data, search): + """ + input: data = \n separated string + output: tuple containing all lines where search is found + """ ret = [] for line in data.split("\n"): if line.find(search) == -1: @@ -13,7 +25,11 @@ return "\n".join(ret) def run_command(command): - process = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE) + """ + run command as blocking subprocess, returns exit code + """ + process = subprocess.Popen(shlex.split(command), \ + stdout=subprocess.PIPE) while True: output = process.stdout.readline() if output == '' and process.poll() is not None: @@ -24,6 +40,9 @@ return rc class ts2mkv(object): + """ + Main worker class, contains all the magic & ffmpeg voodoo + """ def __init__(self, crf=19, tune='film', scaleto_720p=True, rename=False): self.msg_prepare = "" self.msg_eit = "" @@ -37,13 +56,20 @@ "-c:v libx264", "-preset faster", # slow "-tune %s" % tune, # film / animation - "-crf %i" % crf # 21, better 19 - ] + "-crf %i" % crf, # 21, better 19 + ] self.audio_options = [ "-c:a copy", - ] + ] + + self.filename = None + self.outfilebase = None def get_stream_index(self, data): + """ + input: ffmpeg stream info string + output: ffmpeg stream mapping part + """ idx = data.find("Stream #") if idx == -1: return "" @@ -52,12 +78,16 @@ return data[idx:idx+3] def get_movie_description(self): + """ + looks for eit file with same basename of current filename + parse the eit file for txt infofile and optional build new + output filename base with movie name and genre + + output: nothing, manipulates internal variables + """ if not self.filename: return # read the EIT file - # TODO: fallback to meta file if no EIT - # TODO: is there a way to get the imdb for the movie automagically? - # http://www.omdbapi.com/apikey.aspx filename = os.path.splitext(self.filename)[0] + ".eit" self.msg_eit = readeit(filename) if not self.rename or not self.msg_eit: @@ -87,6 +117,10 @@ def get_crop_option(self): + """ + parse the ffmpeg analyze output cropdetect lines + returns None or valid crop string for ffmpeg video filter + """ lines = filter_lines(self.msg_ffmpeg, "[Parsed_cropdetect").split("\n") option = None for line in lines: @@ -102,6 +136,10 @@ return option def get_ffmpeg_command(self): + """ + Too complex to describe, this does all the magic + output: produces internal ffmpeg command list (empty command list on error) + """ if not self.filename: return None @@ -112,8 +150,6 @@ outfn = "\\'".join(p for p in outfn.split("'")) outfn = outfn.replace(" ", "\\ ") - # ffmpeg -ss 00:05:00 -t 2 -i testfiles/chappie.ts -vf "cropdetect=24:16:0" -f null - - cmd = ["ffmpeg", "-ss 00:05:00", "-t 1", # search to 5 minutes, analyze 1 second "-i %s" % fn, @@ -121,7 +157,8 @@ "-f null", "-" # no output file ] print " ".join(cmd) - p = subprocess.Popen(shlex.split(" ".join(cmd)), stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen(shlex.split(" ".join(cmd)), \ + stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() print "Command return code: ", p.poll() self.msg_ffmpeg = out + "\n" + err @@ -194,7 +231,8 @@ if crop: flt.append(crop) if self.scaleto_720p: - flt.append("scale='min(1280,iw)':-2'") # -2 ensures division by two for codec + # -2 ensures division by two for codec + flt.append("scale='min(1280,iw)':-2'") self.msg_prepare += "Scaling cropped output stream to 720p\n" if len(flt) > 0: # append video filters @@ -210,12 +248,20 @@ return commands def load(self, filename): + """ + First step: setup, analyze & prepare for conversion + """ self.filename = filename self.outfilebase = os.path.splitext(filename)[0] self.get_movie_description() self.command = self.get_ffmpeg_command() def convert(self): + """ + Second step: write info text file and start ffmpeg conversion + requires successful load as first step + returns ffmpeg conversion exit status + """ if not self.command: return None fd = open(self.outfilebase + ".txt", "wb") @@ -226,7 +272,7 @@ fd.close() #print self.msg_ffmpeg - for cmd in mkv.command: + for cmd in self.command: print "Executing ffmpeg:\n%s\n" % cmd return run_command(cmd) @@ -234,35 +280,29 @@ if __name__ == "__main__": # parse command line options - import argparse, sys, glob + import argparse, glob - parser = argparse.ArgumentParser(description = 'DVB-TS to MKV kung-fu') - parser.add_argument('--crf', type=int, default=19, + parser = argparse.ArgumentParser(description='DVB-TS to MKV kung-fu') + parser.add_argument('--crf', type=int, default=19, \ help='h264 crf (default 19)') - parser.add_argument('--tune', default='film', + parser.add_argument('--tune', default='film', \ help='ffmpeg tune preset [film, animation] (default is film)') - parser.add_argument('--ns', action='store_true', default=False, + parser.add_argument('--ns', action='store_true', default=False, \ help='no rescaling (default is scale to 720p)') - parser.add_argument('--rename', action='store_true', default=False, + parser.add_argument('--rename', action='store_true', default=False, \ help='rename file basename to name and genre from EIT file if present') - parser.add_argument('input', metavar='input', nargs='+', + parser.add_argument('input', metavar='input', nargs='+', \ help='one or more files, glob style syntax') args = parser.parse_args() - mkv = ts2mkv( - crf = args.crf, - tune = args.tune, - scaleto_720p = (not args.ns), - rename = args.rename - ) - - #os.system('cls' if os.name == 'nt' else 'clear') + processor = ts2mkv(crf=args.crf, tune=args.tune, scaleto_720p=(not args.ns), \ + rename=args.rename) for srcstr in args.input: src = glob.glob(srcstr) - for filename in src: - print filename - mkv.load(filename) - mkv.convert() + for srcfile in src: + print "Processing: %s" % srcfile + processor.load(srcfile) + processor.convert()