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 |