Tue, 28 Nov 2017 22:27:01 +0100
code cleanup
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) | |
248 | return audiomap | |
249 | ||
17 | 250 | def __parse_info(self): |
251 | """ | |
252 | get total duration and fps from input stream | |
253 | output: sets local variables | |
254 | # Duration: 01:39:59.88, start: 93674.825111, bitrate: 9365 kb/s | |
255 | # 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 | |
256 | """ | |
257 | tmp = filter_lines(self.info["msg_ffmpeg"], "Duration:").strip()[10:] | |
258 | tmp = tmp[0:tmp.find(",")].strip() | |
259 | print "Input duration: %s" % tmp | |
260 | try: | |
261 | self.info["frames_total"] = int(tmp[0:2]) * 3600 + \ | |
262 | int(tmp[3:5]) * 60 + int(tmp[6:8]) | |
263 | except ValueError: | |
264 | self.info["frames_total"] = 0 | |
265 | ||
266 | tmp = filter_lines(self.info["msg_ffmpeg"], "Stream #0:") | |
267 | tmp = filter_lines(tmp, "Video:").split(",") | |
268 | for fps in tmp: | |
269 | if fps.strip().endswith('fps'): | |
270 | try: | |
271 | self.info["fps"] = float(fps.strip().split(' ')[0]) | |
272 | except ValueError: | |
273 | self.info["fps"] = 0 | |
274 | break | |
275 | self.info["frames_total"] = round(self.info["frames_total"] * self.info["fps"], 0) | |
276 | print "Input framerate: %f fps" % self.info["fps"] | |
277 | print "Total frames of input file: %i" % (self.info["frames_total"]) | |
278 | ||
279 | ||
280 | def __get_ffmpeg_input_info(self, filename): | |
281 | """ | |
282 | Run ffmpeg for cropdetect and general input information | |
283 | """ | |
284 | cmd = [ | |
285 | "ffmpeg", "-hide_banner", | |
286 | "-ss 00:05:00", "-t 2", # search to 5 minutes, analyze 2 seconds | |
287 | "-i %s" % filename, | |
288 | "-vf \"cropdetect=24:2:0\"", # detect black bar crop on top and bottom | |
289 | "-f null", "-" # no output file | |
290 | ] | |
291 | p = subprocess.Popen(shlex.split(" ".join(cmd)), \ | |
292 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |
293 | out, err = p.communicate() | |
294 | with self.info["msg_ffmpeg"] as msg: | |
295 | msg = out + "\n" + err | |
296 | msg = msg[msg.find("Input #0"):] | |
297 | ||
9 | 298 | def get_ffmpeg_command(self): |
10 | 299 | """ |
300 | Too complex to describe, this does all the magic | |
301 | output: produces internal ffmpeg command list (empty command list on error) | |
302 | """ | |
9 | 303 | if not self.filename: |
304 | return None | |
305 | ||
17 | 306 | fn = { |
307 | "in": ffmpeg_filename(self.filename), | |
308 | "out": self.outfilebase + ".mkv" | |
309 | } | |
11 | 310 | |
311 | # double-check: pull the kill switch and exit if outfile exists already! | |
312 | # we do not want to overwrite files in accident (caused by automatic file naming) | |
17 | 313 | if not self.config["overwrite"] and len(glob.glob(fn["out"])) > 0: |
314 | print "Output file exists: %s" % fn["out"] | |
11 | 315 | print "NOT overwriting it!" |
316 | return None | |
7 | 317 | |
17 | 318 | # load input file to get informations about |
319 | self.__get_ffmpeg_input_info(fn["in"]) | |
8 | 320 | |
7 | 321 | # find "Stream #0:" lines |
17 | 322 | info = filter_lines(self.info["msg_ffmpeg"], "Stream #0:") |
7 | 323 | |
324 | v = self.get_stream_index( | |
325 | filter_lines(info, "Video:")) | |
326 | if v == "": | |
327 | print "No video stream found" | |
328 | return None | |
329 | ||
17 | 330 | self.__parse_info() |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
331 | |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
332 | # copy ALL subtitle streams if present! |
14 | 333 | # Stream #0:0[0x20](deu): Subtitle: dvb_teletext ([6][0][0][0] / 0x0006), 492x250 |
334 | submap = [] | |
335 | for tmp in filter_lines(info, "Subtitle: dvb_teletext").split("\n"): | |
336 | if self.get_stream_index(tmp): | |
337 | 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
|
338 | # 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
|
339 | submap = [] |
7 | 340 | |
14 | 341 | # select audio streams |
342 | audiomap = self.__get_audiomap(info) | |
9 | 343 | if len(audiomap) == 0: |
344 | print "No suitable audio stream found, aborting." | |
345 | return None | |
7 | 346 | |
14 | 347 | # Old dreambox images did a file split: .ts .ts.001 .ts.002 etc. |
11 | 348 | # Find all these files and join them! |
17 | 349 | inputs = [fn["in"]] |
350 | if os.path.splitext(self.filename)[1].lower() == '.ts': | |
351 | for tmp in glob.glob(self.filename + '.' + ('[0-9]' * 3)): | |
352 | inputs.append(ffmpeg_filename(tmp)) | |
12 | 353 | |
354 | if len(inputs) > 1: | |
355 | # use ffmpeg input concat function | |
17 | 356 | # attention, ffmpeg concat protocol doesnt like escape sequences |
357 | fn["in"] = "\"concat:" + \ | |
12 | 358 | "|".join(inputs)\ |
17 | 359 | .replace(r"\ ", " ")\ |
360 | .replace(r"\'", "'")\ | |
12 | 361 | + "\"" |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
362 | # no ETA calculation possible since we have only the length of first file |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
363 | # TODO: we COULD estimate by multiplying with factor generated by input file sizes |
17 | 364 | print "No ETA info possible yet on split input" |
365 | self.info["frames_total"] = 0 | |
12 | 366 | |
367 | idx = 0 | |
368 | for tmp in inputs: | |
17 | 369 | self.info["msg_prepare"] += "Input file #%i: %s\n" % ( |
12 | 370 | idx, os.path.basename(tmp)) |
371 | idx += 1 | |
11 | 372 | |
7 | 373 | cmd = [ |
14 | 374 | "ffmpeg", "-hide_banner", |
17 | 375 | "-i %s" % fn["in"], |
7 | 376 | ] |
14 | 377 | |
17 | 378 | if self.config["overwrite"]: |
15
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
379 | cmd.append("-y") |
82361ad7b3fe
some changes, also implemented ffmpeg progress info and added force overwrite mode
mdd
parents:
14
diff
changeset
|
380 | |
14 | 381 | for tmp in submap: |
17 | 382 | self.info["msg_prepare"] += "Subtitle Stream selected: Stream #%s\n" % tmp |
14 | 383 | cmd.append("-map %s" % tmp) |
384 | ||
385 | cmd.append("-map %s" % v) | |
17 | 386 | self.info["msg_prepare"] += "Video Stream selected: Stream #%s\n" % v |
14 | 387 | |
8 | 388 | flt = [] |
389 | crop = self.get_crop_option() | |
390 | if crop: | |
391 | flt.append(crop) | |
17 | 392 | if self.config["scaledown"]: |
10 | 393 | # -2 ensures division by two for codec |
394 | flt.append("scale='min(1280,iw)':-2'") | |
17 | 395 | self.info["msg_prepare"] += "Scaling output stream to 720p if width >1280\n" |
8 | 396 | if len(flt) > 0: |
397 | # append video filters | |
398 | cmd.append('-filter:v "%s"' % ",".join(flt)) | |
17 | 399 | |
14 | 400 | for tmp in audiomap: |
17 | 401 | self.info["msg_prepare"] += "Audio Stream selected: Stream #%s\n" % tmp |
14 | 402 | cmd.append("-map %s" % tmp) |
403 | if len(submap) > 0: | |
404 | cmd.append("-c:s dvdsub") | |
17 | 405 | cmd.extend(self.config["video"]) |
406 | cmd.extend(self.config["audio"]) | |
407 | cmd.append(ffmpeg_filename(fn["out"])) | |
7 | 408 | |
17 | 409 | return [" ".join(cmd)] |
7 | 410 | |
411 | def load(self, filename): | |
10 | 412 | """ |
413 | First step: setup, analyze & prepare for conversion | |
414 | """ | |
17 | 415 | self.__reset() |
13
cf5c5cec1b2b
bugfix: cleanup status messages when processing multiple files
mdd
parents:
12
diff
changeset
|
416 | |
7 | 417 | self.filename = filename |
9 | 418 | self.outfilebase = os.path.splitext(filename)[0] |
17 | 419 | self.__get_movie_description() |
9 | 420 | self.command = self.get_ffmpeg_command() |
7 | 421 | |
9 | 422 | def convert(self): |
10 | 423 | """ |
424 | Second step: write info text file and start ffmpeg conversion | |
425 | requires successful load as first step | |
426 | returns ffmpeg conversion exit status | |
427 | """ | |
9 | 428 | if not self.command: |
429 | return None | |
430 | fd = open(self.outfilebase + ".txt", "wb") | |
17 | 431 | fd.write(self.info["msg_eit"]) |
7 | 432 | fd.write("\n\n# ---DEBUG---\n\n") |
17 | 433 | fd.write(self.info["msg_prepare"]) |
434 | fd.write(self.info["msg_ffmpeg"]) | |
7 | 435 | fd.close() |
17 | 436 | #print self.info["msg_ffmpeg"] |
7 | 437 | |
10 | 438 | for cmd in self.command: |
8 | 439 | 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
|
440 | #return run_command(cmd, self.total_frames) |
17 | 441 | return run_ffmpeg_watch(cmd, frames_total=self.info["frames_total"]) |
9 | 442 | |
443 | ||
444 | ||
445 | if __name__ == "__main__": | |
446 | # parse command line options | |
10 | 447 | import argparse, glob |
9 | 448 | |
10 | 449 | parser = argparse.ArgumentParser(description='DVB-TS to MKV kung-fu') |
450 | parser.add_argument('--crf', type=int, default=19, \ | |
9 | 451 | help='h264 crf (default 19)') |
10 | 452 | parser.add_argument('--tune', default='film', \ |
9 | 453 | help='ffmpeg tune preset [film, animation] (default is film)') |
10 | 454 | parser.add_argument('--ns', action='store_true', default=False, \ |
9 | 455 | help='no rescaling (default is scale to 720p)') |
10 | 456 | parser.add_argument('--rename', action='store_true', default=False, \ |
9 | 457 | help='rename file basename to name and genre from EIT file if present') |
10 | 458 | parser.add_argument('input', metavar='input', nargs='+', \ |
9 | 459 | 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
|
460 | 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
|
461 | help='force overwrite of existing file') |
9 | 462 | |
463 | args = parser.parse_args() | |
17 | 464 | processor = ts2mkv(crf=args.crf, tune=args.tune) |
465 | with processor.config as c: | |
466 | c["scaledown"] = not args.ns | |
467 | c["rename"] = args.rename | |
468 | c["overwrite"] = args.f | |
9 | 469 | |
470 | for srcstr in args.input: | |
471 | src = glob.glob(srcstr) | |
10 | 472 | for srcfile in src: |
473 | print "Processing: %s" % srcfile | |
474 | processor.load(srcfile) | |
475 | processor.convert() | |
9 | 476 | |
477 |