ts2mkv.py

changeset 9
1bf778001041
parent 8
92409c985e0d
child 10
f436a7f94c6a
equal deleted inserted replaced
8:92409c985e0d 9:1bf778001041
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 2
3 import subprocess 3 import subprocess
4 from eit import readeit 4 from eit import readeit, eitinfo
5 import os, shlex 5 import os, shlex
6 6
7 def filter_lines(data, search): 7 def filter_lines(data, search):
8 ret = [] 8 ret = []
9 for line in data.split("\n"): 9 for line in data.split("\n"):
22 print output.strip() 22 print output.strip()
23 rc = process.poll() 23 rc = process.poll()
24 return rc 24 return rc
25 25
26 class ts2mkv(object): 26 class ts2mkv(object):
27 def __init__(self): 27 def __init__(self, crf=19, tune='film', scaleto_720p=True, rename=False):
28 self.msg_prepare = "" 28 self.msg_prepare = ""
29 self.msg_eit = "" 29 self.msg_eit = ""
30 self.msg_ffmpeg = "" 30 self.msg_ffmpeg = ""
31 self.command = None 31 self.command = None
32 32
33 self.scaleto_720p = True 33 self.scaleto_720p = scaleto_720p
34 self.rename = rename
34 35
35 self.video_options = [ 36 self.video_options = [
36 "-c:v libx264", 37 "-c:v libx264",
37 "-preset faster", # slow 38 "-preset faster", # slow
38 "-tune film", # film / animation 39 "-tune %s" % tune, # film / animation
39 "-crf 19" # 21, better 19 40 "-crf %i" % crf # 21, better 19
40 ] 41 ]
41 self.audio_options = [ 42 self.audio_options = [
42 "-c:a copy", 43 "-c:a copy",
43 ] 44 ]
44 45
48 return "" 49 return ""
49 idx += 8 50 idx += 8
50 self.msg_prepare += "Selecting: %s\n" % data 51 self.msg_prepare += "Selecting: %s\n" % data
51 return data[idx:idx+3] 52 return data[idx:idx+3]
52 53
53 def get_movie_description(self, filename): 54 def get_movie_description(self):
55 if not self.filename:
56 return
54 # read the EIT file 57 # read the EIT file
55 # TODO: fallback to meta file if no EIT 58 # TODO: fallback to meta file if no EIT
56 # TODO: is there a way to get the imdb for the movie automagically? 59 # TODO: is there a way to get the imdb for the movie automagically?
57 # http://www.omdbapi.com/apikey.aspx 60 # http://www.omdbapi.com/apikey.aspx
58 self.msg_eit = readeit(os.path.splitext(filename)[0] + ".eit") 61 filename = os.path.splitext(self.filename)[0] + ".eit"
62 self.msg_eit = readeit(filename)
63 if not self.rename or not self.msg_eit:
64 return
65 info = eitinfo(filename)
66 name = info.eit.get("name")
67 if name == "":
68 # cancel rename, no movie title found!
69 return
70 genre = info.eit.get("genre")
71 if genre != "":
72 name = "%s (%s)" % (name, genre)
73 # build new filename
74 name = name.replace(' : ', ' - ')
75 name = name.replace(': ', ' - ')
76 name = name.replace(':', '-')
77 name = name.replace('/', '')
78 name = name.replace('\\', '')
79 name = name.replace('?', '')
80 name = name.replace('*', '')
81 name = name.replace('\"', '\'')
82
83 self.outfilebase = os.path.join(
84 os.path.dirname(filename),
85 name
86 )
87
59 88
60 def get_crop_option(self): 89 def get_crop_option(self):
61 lines = filter_lines(self.msg_ffmpeg, "[Parsed_cropdetect").split("\n") 90 lines = filter_lines(self.msg_ffmpeg, "[Parsed_cropdetect").split("\n")
62 option = None 91 option = None
63 for line in lines: 92 for line in lines:
70 self.msg_prepare += "WARNING: cropdetect inconsistent over scan time, disabling autocrop\n" 99 self.msg_prepare += "WARNING: cropdetect inconsistent over scan time, disabling autocrop\n"
71 return None 100 return None
72 self.msg_prepare += "Crop detected: %s\n" % option 101 self.msg_prepare += "Crop detected: %s\n" % option
73 return option 102 return option
74 103
75 def get_ffmpeg_command(self, filename): 104 def get_ffmpeg_command(self):
105 if not self.filename:
106 return None
107
76 commands = [] 108 commands = []
77 fn = "\\'".join(p for p in filename.split("'")) 109 fn = "\\'".join(p for p in self.filename.split("'"))
110 fn = fn.replace(" ", "\\ ")
111 outfn = self.outfilebase + ".mkv"
112 outfn = "\\'".join(p for p in outfn.split("'"))
113 outfn = outfn.replace(" ", "\\ ")
78 114
79 # ffmpeg -ss 00:05:00 -t 2 -i testfiles/chappie.ts -vf "cropdetect=24:16:0" -f null - 115 # ffmpeg -ss 00:05:00 -t 2 -i testfiles/chappie.ts -vf "cropdetect=24:16:0" -f null -
80 116
81 cmd = ["ffmpeg", 117 cmd = ["ffmpeg",
82 "-ss 00:05:00", "-t 1", # search to 5 minutes, analyze 1 second 118 "-ss 00:05:00", "-t 1", # search to 5 minutes, analyze 1 second
92 self.msg_ffmpeg = self.msg_ffmpeg[self.msg_ffmpeg.find("Input #0"):] 128 self.msg_ffmpeg = self.msg_ffmpeg[self.msg_ffmpeg.find("Input #0"):]
93 129
94 # find "Stream #0:" lines 130 # find "Stream #0:" lines
95 info = filter_lines(self.msg_ffmpeg, "Stream #0:") 131 info = filter_lines(self.msg_ffmpeg, "Stream #0:")
96 132
97 fn = fn.replace(" ", "\\ ")
98 133
99 # CRAP! ffmpeg cannot decode dvb_teletext streams to srt :( 134 # CRAP! ffmpeg cannot decode dvb_teletext streams to srt :(
100 # are there any subtitle streams?! 135 # are there any subtitle streams?!
101 #s = filter_lines(info, "Subtitle:") 136 #s = filter_lines(info, "Subtitle:")
102 #s = filter_lines(s, "(deu):") 137 #s = filter_lines(s, "(deu):")
114 filter_lines(info, "Video:")) 149 filter_lines(info, "Video:"))
115 if v == "": 150 if v == "":
116 print "No video stream found" 151 print "No video stream found"
117 return None 152 return None
118 153
154 audiomap = []
119 audioall = filter_lines(info, "Audio:") 155 audioall = filter_lines(info, "Audio:")
120 audio = filter_lines(audioall, "(deu):") 156 audio = filter_lines(audioall, "(deu):")
121 a = self.get_stream_index( 157 a = self.get_stream_index(
122 filter_lines(audio, "ac3")) 158 filter_lines(audio, "ac3"))
123 # TODO: wenn kein ac3 stream dann dts oder mpeg fallback 159 # TODO: wenn kein ac3 stream dann dts oder mpeg fallback
124 audiomap = [a]
125 if a == "": 160 if a == "":
126 print "No suitable german audio stream found" 161 print audioall
127 return None 162 print "No AC3 german audio stream found"
163 # try to find the first german audio stream
164 a = self.get_stream_index(audio.split("\n")[0])
165 if a == "":
166 print "No other german audio streams, trying english ac3..."
167 else:
168 print "Selecting first german stream."
169 audiomap.append(a)
170 else:
171 audiomap.append(a)
128 172
129 audio = filter_lines(audioall, "(eng):") 173 audio = filter_lines(audioall, "(eng):")
130 a = self.get_stream_index( 174 a = self.get_stream_index(
131 filter_lines(audio, "ac3")) 175 filter_lines(audio, "ac3"))
132 if a != "": 176 if a != "":
133 # append english audio too! 177 # append english audio too!
178 print "Selecting english ac3 stream."
134 audiomap.append(a) 179 audiomap.append(a)
135 180
181 if len(audiomap) == 0:
182 print "No suitable audio stream found, aborting."
183 return None
136 184
137 185
138 self.msg_prepare += "Video Stream selected: Stream #%s\n" % v 186 self.msg_prepare += "Video Stream selected: Stream #%s\n" % v
139 cmd = [ 187 cmd = [
140 "ffmpeg", 188 "ffmpeg",
154 for a in audiomap: 202 for a in audiomap:
155 self.msg_prepare += "Audio Stream selected: Stream #%s\n" % a 203 self.msg_prepare += "Audio Stream selected: Stream #%s\n" % a
156 cmd.append("-map %s" % a) 204 cmd.append("-map %s" % a)
157 cmd.extend(self.video_options) 205 cmd.extend(self.video_options)
158 cmd.extend(self.audio_options) 206 cmd.extend(self.audio_options)
159 cmd.append(os.path.splitext(fn)[0] + ".mkv") 207 cmd.append(outfn)
160 208
161 commands.append(" ".join(cmd)) 209 commands.append(" ".join(cmd))
162 return commands 210 return commands
163 211
164 def load(self, filename): 212 def load(self, filename):
165 self.filename = filename 213 self.filename = filename
166 self.get_movie_description(filename) 214 self.outfilebase = os.path.splitext(filename)[0]
167 self.command = self.get_ffmpeg_command(filename) 215 self.get_movie_description()
168 216 self.command = self.get_ffmpeg_command()
169 if __name__ == "__main__": 217
170 import sys 218 def convert(self):
171 os.system('cls' if os.name == 'nt' else 'clear') 219 if not self.command:
172 220 return None
173 # TODO: get file from commandline 221 fd = open(self.outfilebase + ".txt", "wb")
174 #filename = "testfiles/chappie.ts" 222 fd.write(self.msg_eit)
175 filename = "/srv/storage0/DREAMBOX/Science Fiction/THE_ISLAND.ts"
176
177 mkv = ts2mkv()
178 mkv.load(filename)
179 if mkv.command:
180 fd = open(os.path.splitext(filename)[0] + ".txt", "wb")
181 fd.write(mkv.msg_eit)
182 fd.write("\n\n# ---DEBUG---\n\n") 223 fd.write("\n\n# ---DEBUG---\n\n")
183 fd.write(mkv.msg_prepare) 224 fd.write(self.msg_prepare)
184 fd.write(mkv.msg_ffmpeg) 225 fd.write(self.msg_ffmpeg)
185 fd.close() 226 fd.close()
186 print mkv.msg_ffmpeg 227 #print self.msg_ffmpeg
187 228
188 for cmd in mkv.command: 229 for cmd in mkv.command:
189 print "Executing ffmpeg:\n%s\n" % cmd 230 print "Executing ffmpeg:\n%s\n" % cmd
190 run_command(cmd) 231 return run_command(cmd)
232
233
234
235 if __name__ == "__main__":
236 # parse command line options
237 import argparse, sys, glob
238
239 parser = argparse.ArgumentParser(description = 'DVB-TS to MKV kung-fu')
240 parser.add_argument('--crf', type=int, default=19,
241 help='h264 crf (default 19)')
242 parser.add_argument('--tune', default='film',
243 help='ffmpeg tune preset [film, animation] (default is film)')
244 parser.add_argument('--ns', action='store_true', default=False,
245 help='no rescaling (default is scale to 720p)')
246 parser.add_argument('--rename', action='store_true', default=False,
247 help='rename file basename to name and genre from EIT file if present')
248 parser.add_argument('input', metavar='input', nargs='+',
249 help='one or more files, glob style syntax')
250
251 args = parser.parse_args()
252 mkv = ts2mkv(
253 crf = args.crf,
254 tune = args.tune,
255 scaleto_720p = (not args.ns),
256 rename = args.rename
257 )
258
259 #os.system('cls' if os.name == 'nt' else 'clear')
260
261 for srcstr in args.input:
262 src = glob.glob(srcstr)
263 for filename in src:
264 print filename
265 mkv.load(filename)
266 mkv.convert()
267
268

mercurial