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