Wed, 29 Nov 2017 23:34:51 +0100
implemented Levenshtein algorithm for incredible speedup
7 | 1 | #!/usr/bin/env python |
10 | 2 | """ |
3 | DVB-TS to MKV kung-fu | |
4 | 2017 by mdd | |
5 | ||
6 | Toolkit / executable to automagically convert DVB recordings to h264 mkv. | |
17 | 7 | Automatic audio stream selection |
8 | deu: ac3, otherwise fallback to first german stream | |
9 | eng: ac3, no fallback | |
10 | 10 | Automatic crop detection to remove cinematic bars |
17 | 11 | percentage + ETA for ffmpeg conversion subprocess |
10 | 12 | """ |
17 | 13 | #pylint: disable=line-too-long |
14 | #pylint: disable=invalid-name | |
15 | ||
7 | 16 | |
17 | import subprocess | |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
18 | import pexpect |
17 | 19 | from eit import eitinfo |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
20 | import os, shlex, sys, time |
7 | 21 | |
22 | def filter_lines(data, search): | |
10 | 23 | """ |
24 | input: data = \n separated string | |
17 | 25 | output: all lines where search is found |
10 | 26 | """ |
7 | 27 | ret = [] |
28 | for line in data.split("\n"): | |
29 | if line.find(search) == -1: | |
30 | continue | |
31 | ret.append(line) | |
32 | return "\n".join(ret) | |
33 | ||
34 | def run_command(command): | |
10 | 35 | """ |
36 | run command as blocking subprocess, returns exit code | |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
37 | if total_frames > 0 parse ffmpeg status line and insert ETA at line start before output |
10 | 38 | """ |
39 | process = subprocess.Popen(shlex.split(command), \ | |
40 | stdout=subprocess.PIPE) | |
7 | 41 | while True: |
42 | output = process.stdout.readline() | |
43 | if output == '' and process.poll() is not None: | |
44 | break | |
45 | if output: | |
46 | print output.strip() | |
47 | rc = process.poll() | |
48 | return rc | |
49 | ||
17 | 50 | def run_ffmpeg_watch(command, frames_total=0): |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
51 | """ |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
52 | run command as blocking subprocess, returns exit code |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
53 | if total_frames > 0 parse ffmpeg status line and insert ETA at line start before output |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
54 | """ |
17 | 55 | #pylint: disable=maybe-no-member |
56 | ||
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
57 | thread = pexpect.spawn(command) |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
58 | cpl = thread.compile_pattern_list([ |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
59 | pexpect.EOF, |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
60 | "frame= *(\d+)", |
16 | 61 | "(.+)\n", |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
62 | '(.+)' |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
63 | ]) |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
64 | percent = 0 |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
65 | eta = 0 |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
66 | time_start = time.time() - 0.1 # start in the past |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
67 | while True: |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
68 | i = thread.expect_list(cpl, timeout=None) |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
69 | if i == 0: # EOF |
16 | 70 | print "\nffmpeg subprocess finished!" |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
71 | break |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
72 | elif i == 1: |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
73 | try: |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
74 | frame_number = int(thread.match.group(1)) |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
75 | if frames_total > 0: |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
76 | percent = frame_number * 100.00 / frames_total |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
77 | eta = frame_number / (time.time() - time_start) |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
78 | # eta is frames per second so far |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
79 | eta = (frames_total - frame_number) / eta / 60 |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
80 | sys.stdout.write("\rFrame %i of %i, %.1f%% done, ETA %.0f minutes, " % ( |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
81 | frame_number, frames_total, percent, eta |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
82 | )) |
17 | 83 | except ValueError: |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
84 | sys.stdout.write(thread.match.group(0)) |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
85 | sys.stdout.flush() |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
86 | thread.close |
17 | 87 | #elif i == 2: |
16 | 88 | # normal newline line, just ignore them... |
17 | 89 | # pass |
16 | 90 | elif i == 3: |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
91 | unknown_line = thread.match.group(0) |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
92 | sys.stdout.write(unknown_line) |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
93 | sys.stdout.flush() |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
94 | |
14 | 95 | def ffmpeg_filename(filename): |
96 | """ | |
97 | Escape filename path contents for ffmpeg shell command | |
98 | """ | |
17 | 99 | fn = filename.replace("'", r"\'") |
100 | fn = fn.replace(" ", r"\ ") | |
14 | 101 | return fn |
102 | ||
7 | 103 | class ts2mkv(object): |
10 | 104 | """ |
105 | Main worker class, contains all the magic & ffmpeg voodoo | |
106 | """ | |
17 | 107 | def __init__(self, crf=19, tune='film'): |
7 | 108 | self.command = None |
13
cf5c5cec1b2b
bugfix: cleanup status messages when processing multiple files
mdd
parents:
12
diff
changeset
|
109 | self.filename = None |
cf5c5cec1b2b
bugfix: cleanup status messages when processing multiple files
mdd
parents:
12
diff
changeset
|
110 | self.outfilebase = None |
17 | 111 | self.info = {} |
112 | self.__reset() | |
8 | 113 | |
17 | 114 | self.config = { |
115 | "overwrite": False, | |
116 | "scaledown": True, | |
117 | "rename": True, | |
118 | "video": [ | |
119 | "-c:v libx264", | |
120 | "-preset faster", # slow | |
121 | "-tune %s" % tune, # film / animation | |
122 | "-crf %i" % crf, # 21, better 19 | |
123 | ], | |
124 | "audio": [ | |
125 | "-c:a copy", | |
126 | ] | |
127 | } | |
10 | 128 | |
17 | 129 | def __reset(self): |
130 | """ | |
131 | Reset internal stuff before loading new task | |
132 | """ | |
133 | self.info = { | |
134 | "msg_prepare": "", | |
135 | "msg_eit": "", | |
136 | "msg_ffmpeg": "", | |
137 | "fps": 0, | |
138 | "frames_total": 0 | |
139 | } | |
140 | self.command = None | |
141 | self.filename = None | |
142 | self.outfilebase = None | |
7 | 143 | |
144 | def get_stream_index(self, data): | |
10 | 145 | """ |
146 | input: ffmpeg stream info string | |
147 | output: ffmpeg stream mapping part | |
148 | """ | |
7 | 149 | idx = data.find("Stream #") |
150 | if idx == -1: | |
151 | return "" | |
152 | idx += 8 | |
17 | 153 | self.info["msg_prepare"] += "Selecting: %s\n" % data |
7 | 154 | return data[idx:idx+3] |
155 | ||
17 | 156 | def __get_movie_description(self): |
10 | 157 | """ |
158 | looks for eit file with same basename of current filename | |
159 | parse the eit file for txt infofile and optional build new | |
160 | output filename base with movie name and genre | |
161 | ||
162 | output: nothing, manipulates internal variables | |
163 | """ | |
9 | 164 | if not self.filename: |
165 | return | |
7 | 166 | # read the EIT file |
9 | 167 | filename = os.path.splitext(self.filename)[0] + ".eit" |
17 | 168 | info = eitinfo(filename) |
169 | self.info["msg_eit"] = info.dump() | |
170 | if not self.config["rename"] or not self.info["msg_eit"]: | |
9 | 171 | return |
172 | name = info.eit.get("name") | |
173 | if name == "": | |
174 | # cancel rename, no movie title found! | |
175 | return | |
176 | genre = info.eit.get("genre") | |
177 | if genre != "": | |
178 | name = "%s (%s)" % (name, genre) | |
179 | # build new filename | |
180 | name = name.replace(' : ', ' - ') | |
181 | name = name.replace(': ', ' - ') | |
182 | name = name.replace(':', '-') | |
183 | name = name.replace('/', '') | |
184 | name = name.replace('\\', '') | |
185 | name = name.replace('?', '') | |
186 | name = name.replace('*', '') | |
187 | name = name.replace('\"', '\'') | |
188 | ||
189 | self.outfilebase = os.path.join( | |
190 | os.path.dirname(filename), | |
191 | name | |
192 | ) | |
193 | ||
7 | 194 | |
8 | 195 | def get_crop_option(self): |
10 | 196 | """ |
197 | parse the ffmpeg analyze output cropdetect lines | |
198 | returns None or valid crop string for ffmpeg video filter | |
199 | """ | |
17 | 200 | lines = filter_lines(self.info["msg_ffmpeg"], "[Parsed_cropdetect").split("\n") |
8 | 201 | option = None |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
202 | failcount = 0 |
8 | 203 | for line in lines: |
204 | tmp = line[line.find(" crop="):].strip() | |
205 | #print "DEBUG: " + tmp | |
206 | if not option: | |
207 | option = tmp | |
208 | else: | |
209 | if option != tmp: | |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
210 | failcount += 1 |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
211 | if failcount > 12: |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
212 | print "!!! Crop detect is inconsistent" |
17 | 213 | self.info["msg_prepare"] += "WARNING: cropdetect >50% inconsistent over scan time, disabling autocrop\n" |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
214 | return None |
17 | 215 | self.info["msg_prepare"] += "Crop detected: %s\n" % option |
8 | 216 | return option |
217 | ||
14 | 218 | def __get_audiomap(self, info): |
219 | """ | |
220 | Select the wanted german and english audio streams from ffmpeg info | |
221 | output: mapping list | |
222 | """ | |
223 | audiomap = [] | |
224 | audioall = filter_lines(info, "Audio:") | |
225 | audio = filter_lines(audioall, "(deu):") | |
226 | aidx = self.get_stream_index( | |
227 | filter_lines(audio, "ac3")) | |
228 | if aidx == "": | |
229 | print audioall | |
230 | print "No AC3 german audio stream found" | |
231 | # try to find the first german audio stream | |
232 | aidx = self.get_stream_index(audio.split("\n")[0]) | |
233 | if aidx == "": | |
234 | print "No other german audio streams, trying english..." | |
235 | else: | |
236 | print "Selecting first german stream." | |
237 | audiomap.append(aidx) | |
238 | else: | |
239 | audiomap.append(aidx) | |
240 | ||
241 | audio = filter_lines(audioall, "(eng):") | |
242 | aidx = self.get_stream_index( | |
243 | filter_lines(audio, "ac3")) | |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
244 | if aidx != "" and filter_lines(audio, "ac3").find(" 0 channels ") < 1: |
14 | 245 | # append english audio too! |
246 | print "Selecting english ac3 stream." | |
247 | audiomap.append(aidx) | |
20 | 248 | if len(audiomap) == 0 and self.config["firstaudio"]: |
249 | # append first audio stream as forced fallback | |
250 | aidx = self.get_stream_index(audioall) | |
251 | if aidx != "": | |
252 | print "Forcing first found audio stream: %s" % aidx | |
253 | audiomap.append(aidx) | |
14 | 254 | return audiomap |
255 | ||
17 | 256 | def __parse_info(self): |
257 | """ | |
258 | get total duration and fps from input stream | |
259 | output: sets local variables | |
260 | # Duration: 01:39:59.88, start: 93674.825111, bitrate: 9365 kb/s | |
261 | # Stream #0:1[0x1ff]: Video: h264 (High) ([27][0][0][0] / 0x001B), yuv420p(tv, bt709), 1920x1080 [SAR 1:1 DAR 16:9], 25 fps, 50 tbr, 90k tbn, 50 tbc | |
262 | """ | |
263 | tmp = filter_lines(self.info["msg_ffmpeg"], "Duration:").strip()[10:] | |
264 | tmp = tmp[0:tmp.find(",")].strip() | |
265 | print "Input duration: %s" % tmp | |
266 | try: | |
267 | self.info["frames_total"] = int(tmp[0:2]) * 3600 + \ | |
268 | int(tmp[3:5]) * 60 + int(tmp[6:8]) | |
269 | except ValueError: | |
270 | self.info["frames_total"] = 0 | |
271 | ||
272 | tmp = filter_lines(self.info["msg_ffmpeg"], "Stream #0:") | |
273 | tmp = filter_lines(tmp, "Video:").split(",") | |
274 | for fps in tmp: | |
275 | if fps.strip().endswith('fps'): | |
276 | try: | |
277 | self.info["fps"] = float(fps.strip().split(' ')[0]) | |
278 | except ValueError: | |
279 | self.info["fps"] = 0 | |
280 | break | |
281 | self.info["frames_total"] = round(self.info["frames_total"] * self.info["fps"], 0) | |
282 | print "Input framerate: %f fps" % self.info["fps"] | |
283 | print "Total frames of input file: %i" % (self.info["frames_total"]) | |
284 | ||
285 | ||
286 | def __get_ffmpeg_input_info(self, filename): | |
287 | """ | |
288 | Run ffmpeg for cropdetect and general input information | |
289 | """ | |
290 | cmd = [ | |
291 | "ffmpeg", "-hide_banner", | |
292 | "-ss 00:05:00", "-t 2", # search to 5 minutes, analyze 2 seconds | |
293 | "-i %s" % filename, | |
294 | "-vf \"cropdetect=24:2:0\"", # detect black bar crop on top and bottom | |
295 | "-f null", "-" # no output file | |
296 | ] | |
297 | p = subprocess.Popen(shlex.split(" ".join(cmd)), \ | |
298 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |
299 | out, err = p.communicate() | |
18 | 300 | self.info["msg_ffmpeg"] = out + "\n" + err |
301 | self.info["msg_ffmpeg"] = self.info["msg_ffmpeg"][self.info["msg_ffmpeg"].find("Input #0"):] | |
17 | 302 | |
9 | 303 | def get_ffmpeg_command(self): |
10 | 304 | """ |
305 | Too complex to describe, this does all the magic | |
306 | output: produces internal ffmpeg command list (empty command list on error) | |
307 | """ | |
9 | 308 | if not self.filename: |
309 | return None | |
310 | ||
17 | 311 | fn = { |
312 | "in": ffmpeg_filename(self.filename), | |
313 | "out": self.outfilebase + ".mkv" | |
314 | } | |
11 | 315 | |
316 | # double-check: pull the kill switch and exit if outfile exists already! | |
317 | # we do not want to overwrite files in accident (caused by automatic file naming) | |
17 | 318 | if not self.config["overwrite"] and len(glob.glob(fn["out"])) > 0: |
319 | print "Output file exists: %s" % fn["out"] | |
11 | 320 | print "NOT overwriting it!" |
321 | return None | |
7 | 322 | |
17 | 323 | # load input file to get informations about |
324 | self.__get_ffmpeg_input_info(fn["in"]) | |
8 | 325 | |
7 | 326 | # find "Stream #0:" lines |
17 | 327 | info = filter_lines(self.info["msg_ffmpeg"], "Stream #0:") |
7 | 328 | |
329 | v = self.get_stream_index( | |
330 | filter_lines(info, "Video:")) | |
331 | if v == "": | |
332 | print "No video stream found" | |
333 | return None | |
334 | ||
17 | 335 | self.__parse_info() |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
336 | |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
337 | # copy ALL subtitle streams if present! |
14 | 338 | # Stream #0:0[0x20](deu): Subtitle: dvb_teletext ([6][0][0][0] / 0x0006), 492x250 |
339 | submap = [] | |
340 | for tmp in filter_lines(info, "Subtitle: dvb_teletext").split("\n"): | |
341 | if self.get_stream_index(tmp): | |
342 | submap.append(self.get_stream_index(tmp)) | |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
343 | # Subtitles disabled, that doesnt work as expected, dreambox crashes on copied subtitle stream |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
344 | submap = [] |
7 | 345 | |
14 | 346 | # select audio streams |
347 | audiomap = self.__get_audiomap(info) | |
9 | 348 | if len(audiomap) == 0: |
349 | print "No suitable audio stream found, aborting." | |
350 | return None | |
7 | 351 | |
14 | 352 | # Old dreambox images did a file split: .ts .ts.001 .ts.002 etc. |
11 | 353 | # Find all these files and join them! |
17 | 354 | inputs = [fn["in"]] |
355 | if os.path.splitext(self.filename)[1].lower() == '.ts': | |
356 | for tmp in glob.glob(self.filename + '.' + ('[0-9]' * 3)): | |
357 | inputs.append(ffmpeg_filename(tmp)) | |
12 | 358 | |
359 | if len(inputs) > 1: | |
360 | # use ffmpeg input concat function | |
17 | 361 | # attention, ffmpeg concat protocol doesnt like escape sequences |
19 | 362 | for tmp in range(len(inputs)): |
363 | inputs[tmp] = inputs[tmp].replace(r"\ ", " ").replace(r"\'", "'")\ | |
364 | ||
365 | fn["in"] = "\"concat:" + "|".join(inputs) + "\"" | |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
366 | # no ETA calculation possible since we have only the length of first file |
19 | 367 | # we could estimate by multiplying with factor generated by input file sizes |
368 | totalbytes = 0.0 | |
369 | for tmp in inputs: | |
370 | totalbytes += os.path.getsize(tmp) | |
371 | print "estimating total frames for ETA based on file sizes (we have multiple inputs here)" | |
372 | self.info["frames_total"] *= totalbytes / os.path.getsize(inputs[0]) | |
12 | 373 | |
374 | idx = 0 | |
375 | for tmp in inputs: | |
17 | 376 | self.info["msg_prepare"] += "Input file #%i: %s\n" % ( |
12 | 377 | idx, os.path.basename(tmp)) |
378 | idx += 1 | |
11 | 379 | |
7 | 380 | cmd = [ |
14 | 381 | "ffmpeg", "-hide_banner", |
17 | 382 | "-i %s" % fn["in"], |
7 | 383 | ] |
14 | 384 | |
17 | 385 | if self.config["overwrite"]: |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
386 | cmd.append("-y") |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
387 | |
14 | 388 | for tmp in submap: |
17 | 389 | self.info["msg_prepare"] += "Subtitle Stream selected: Stream #%s\n" % tmp |
14 | 390 | cmd.append("-map %s" % tmp) |
391 | ||
392 | cmd.append("-map %s" % v) | |
17 | 393 | self.info["msg_prepare"] += "Video Stream selected: Stream #%s\n" % v |
14 | 394 | |
8 | 395 | flt = [] |
396 | crop = self.get_crop_option() | |
397 | if crop: | |
398 | flt.append(crop) | |
17 | 399 | if self.config["scaledown"]: |
10 | 400 | # -2 ensures division by two for codec |
401 | flt.append("scale='min(1280,iw)':-2'") | |
17 | 402 | self.info["msg_prepare"] += "Scaling output stream to 720p if width >1280\n" |
8 | 403 | if len(flt) > 0: |
404 | # append video filters | |
405 | cmd.append('-filter:v "%s"' % ",".join(flt)) | |
17 | 406 | |
14 | 407 | for tmp in audiomap: |
17 | 408 | self.info["msg_prepare"] += "Audio Stream selected: Stream #%s\n" % tmp |
14 | 409 | cmd.append("-map %s" % tmp) |
410 | if len(submap) > 0: | |
411 | cmd.append("-c:s dvdsub") | |
17 | 412 | cmd.extend(self.config["video"]) |
413 | cmd.extend(self.config["audio"]) | |
414 | cmd.append(ffmpeg_filename(fn["out"])) | |
7 | 415 | |
17 | 416 | return [" ".join(cmd)] |
7 | 417 | |
418 | def load(self, filename): | |
10 | 419 | """ |
420 | First step: setup, analyze & prepare for conversion | |
421 | """ | |
17 | 422 | self.__reset() |
13
cf5c5cec1b2b
bugfix: cleanup status messages when processing multiple files
mdd
parents:
12
diff
changeset
|
423 | |
7 | 424 | self.filename = filename |
9 | 425 | self.outfilebase = os.path.splitext(filename)[0] |
17 | 426 | self.__get_movie_description() |
9 | 427 | self.command = self.get_ffmpeg_command() |
7 | 428 | |
9 | 429 | def convert(self): |
10 | 430 | """ |
431 | Second step: write info text file and start ffmpeg conversion | |
432 | requires successful load as first step | |
433 | returns ffmpeg conversion exit status | |
434 | """ | |
9 | 435 | if not self.command: |
436 | return None | |
20 | 437 | if not self.info["msg_eit"]: |
438 | self.info["msg_eit"] = "No EIT file found, sorry - no description" | |
9 | 439 | fd = open(self.outfilebase + ".txt", "wb") |
17 | 440 | fd.write(self.info["msg_eit"]) |
7 | 441 | fd.write("\n\n# ---DEBUG---\n\n") |
17 | 442 | fd.write(self.info["msg_prepare"]) |
443 | fd.write(self.info["msg_ffmpeg"]) | |
7 | 444 | fd.close() |
17 | 445 | #print self.info["msg_ffmpeg"] |
7 | 446 | |
10 | 447 | for cmd in self.command: |
8 | 448 | print "Executing ffmpeg:\n%s\n" % cmd |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
449 | #return run_command(cmd, self.total_frames) |
17 | 450 | return run_ffmpeg_watch(cmd, frames_total=self.info["frames_total"]) |
9 | 451 | |
452 | ||
453 | ||
454 | if __name__ == "__main__": | |
455 | # parse command line options | |
10 | 456 | import argparse, glob |
9 | 457 | |
10 | 458 | parser = argparse.ArgumentParser(description='DVB-TS to MKV kung-fu') |
459 | parser.add_argument('--crf', type=int, default=19, \ | |
9 | 460 | help='h264 crf (default 19)') |
10 | 461 | parser.add_argument('--tune', default='film', \ |
9 | 462 | help='ffmpeg tune preset [film, animation] (default is film)') |
10 | 463 | parser.add_argument('--ns', action='store_true', default=False, \ |
9 | 464 | help='no rescaling (default is scale to 720p)') |
10 | 465 | parser.add_argument('--rename', action='store_true', default=False, \ |
9 | 466 | help='rename file basename to name and genre from EIT file if present') |
10 | 467 | parser.add_argument('input', metavar='input', nargs='+', \ |
9 | 468 | help='one or more files, glob style syntax') |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
469 | parser.add_argument('-f', action='store_true', default=False, \ |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
470 | help='force overwrite of existing file') |
20 | 471 | parser.add_argument('--fa', action='store_true', default=False, \ |
472 | help='use first audio stream found') | |
9 | 473 | |
474 | args = parser.parse_args() | |
17 | 475 | processor = ts2mkv(crf=args.crf, tune=args.tune) |
18 | 476 | processor.config["scaledown"] = not args.ns |
477 | processor.config["rename"] = args.rename | |
478 | processor.config["overwrite"] = args.f | |
20 | 479 | processor.config["firstaudio"] = args.fa |
9 | 480 | |
481 | for srcstr in args.input: | |
482 | src = glob.glob(srcstr) | |
10 | 483 | for srcfile in src: |
484 | print "Processing: %s" % srcfile | |
485 | processor.load(srcfile) | |
486 | processor.convert() | |
9 | 487 | |
488 |