Sat, 25 Nov 2017 12:24:17 +0100
do not overwrite output file if exists
0 | 1 | #!/usr/bin/python |
1 | 2 | # -*- coding: utf-8 -*- |
3 | # iso-8859-2 | |
2 | 4 | """ |
5 | EitSupport | |
6 | Copyright (C) 2011 betonme | |
7 | Copyright (C) 2016 Wolfgang Fahl | |
8 | Cleanup 2017 by mdd | |
9 | """ | |
0 | 10 | # This EITParser is based on: |
11 | # https://github.com/betonme/e2openplugin-EnhancedMovieCenter/blob/master/src/EitSupport.py | |
12 | # | |
13 | # In case of reuse of this source code please do not remove this copyright. | |
14 | # | |
15 | # This program is free software: you can redistribute it and/or modify | |
16 | # it under the terms of the GNU General Public License as published by | |
17 | # the Free Software Foundation, either version 3 of the License, or | |
18 | # (at your option) any later version. | |
19 | # | |
20 | # This program is distributed in the hope that it will be useful, | |
21 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
22 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
23 | # GNU General Public License for more details. | |
24 | # | |
25 | # For more information on the GNU General Public License see: | |
26 | # <http://www.gnu.org/licenses/>. | |
27 | # | |
28 | ||
2 | 29 | # seite 36, inhalt der for schleife! |
30 | # https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf | |
31 | ||
6 | 32 | #pylint: disable=missing-docstring |
33 | #pylint: disable=line-too-long | |
34 | ||
0 | 35 | import os |
36 | import struct | |
1 | 37 | import sys |
38 | import getopt | |
0 | 39 | |
40 | from datetime import datetime | |
41 | ||
42 | from ISO639 import LanguageCodes | |
43 | ||
44 | #def crc32(data): | |
45 | # poly = 0x4c11db7 | |
46 | # crc = 0xffffffffL | |
47 | # for byte in data: | |
48 | # byte = ord(byte) | |
49 | # for bit in range(7,-1,-1): # MSB to LSB | |
50 | # z32 = crc>>31 # top bit | |
51 | # crc = crc << 1 | |
52 | # if ((byte>>bit)&1) ^ z32: | |
53 | # crc = crc ^ poly | |
54 | # crc = crc & 0xffffffffL | |
55 | # return crc | |
56 | ||
2 | 57 | EIT_SHORT_EVENT_DESCRIPTOR = 0x4d |
58 | EIT_EXTENDED_EVENT_DESCRIPOR = 0x4e | |
59 | ||
60 | CHARSPEC_HR = { | |
1 | 61 | u'Ć': u'\u0106', u'æ': u'\u0107', u'®': u'\u017D', u'¾': u'\u017E', |
62 | u'©': u'\u0160', u'¹': u'\u0161', u'Č': u'\u010C', u'è': u'\u010D', u'ð': u'\u0111' | |
63 | } | |
0 | 64 | |
2 | 65 | CHARSPEC_CZSK = { |
1 | 66 | u'Ï'+u'C': u'Č', u'Ï'+u'E': u'Ě', u'Ï'+u'L': u'Ľ', u'Ï'+u'N': u'Ň', u'Ï'+u'R': u'Ř', |
67 | u'Ï'+u'S': u'Š', u'Ï'+u'T': u'Ť', u'Ï'+u'Z': u'Ž', u'Ï'+u'c': u'č', u'Ï'+u'd': u'ď', | |
68 | u'Ï'+u'e': u'ě', u'Ï'+u'l': u'ľ', u'Ï'+u'n': u'ň', u'Ï'+u'r': u'ř', u'Ï'+u's': u'š', | |
69 | u'Ï'+u't': u'ť', u'Ï'+u'z': u'ž', u'Ï'+u'D': u'Ď', u'Â'+u'A': u'Á', u'Â'+u'E': u'É', | |
70 | u'Â'+u'I': u'Í', u'Â'+u'O': u'Ó', u'Â'+u'U': u'Ú', u'Â'+u'a': u'á', u'Â'+u'e': u'é', | |
71 | u'Â'+u'i': u'í', u'Â'+u'o': u'ó', u'Â'+u'u': u'ú', u'Â'+u'y': u'ý', u'Ã'+u'o': u'ô', | |
72 | u'Ã'+u'O': u'Ô', u'Ê'+u'u': u'ů', u'Ê'+u'U': u'Ů', u'È'+u'A': u'Ä', u'È'+u'E': u'Ë', | |
73 | u'È'+u'I': u'Ï', u'È'+u'O': u'Ö', u'È'+u'U': u'Ü', u'È'+u'Y': u'Ÿ', u'È'+u'a': u'ä', | |
74 | u'È'+u'e': u'ë', u'È'+u'i': u'ï', u'È'+u'o': u'ö', u'È'+u'u': u'ü', u'È'+u'y': u'ÿ' | |
75 | } | |
76 | ||
2 | 77 | def convert_charspec_hr(text): |
78 | for i, j in CHARSPEC_HR.iteritems(): | |
0 | 79 | text = text.replace(i, j) |
80 | return text | |
81 | ||
2 | 82 | def convert_charspec_czsk(text): |
83 | for i, j in CHARSPEC_CZSK.iteritems(): | |
0 | 84 | text = text.replace(i, j) |
85 | return text | |
86 | ||
2 | 87 | def parse_mjd(mjd): |
1 | 88 | """Parse 16 bit unsigned int containing Modified Julian Date, |
89 | as per DVB-SI spec | |
90 | returning year,month,day""" | |
91 | year = int((mjd - 15078.2) / 365.25) | |
92 | month = int((mjd - 14956.1 - int(year * 365.25)) / 30.6001) | |
2 | 93 | day = mjd - 14956 - int(year * 365.25) - int(month * 30.6001) |
1 | 94 | correction = 0 |
95 | if month == 14 or month == 15: | |
96 | correction = 1 | |
97 | return (1900 + year + correction), (month - 1 - correction * 12), day | |
0 | 98 | |
1 | 99 | def bcd2dec(byte): |
100 | return (byte >> 4) * 10 + (byte & 0xf) | |
0 | 101 | |
2 | 102 | |
103 | def mkint(data): | |
104 | """ | |
105 | Convert string to Integer | |
106 | """ | |
107 | return int(data) if data else 0 | |
108 | ||
109 | def todate(sdate, stime): | |
110 | """ | |
111 | Convert date and time to datetime tuple | |
112 | """ | |
113 | if sdate and stime: | |
114 | try: | |
115 | return datetime( | |
116 | int(sdate[0]), int(sdate[1]), int(sdate[2]), | |
117 | int(stime[0]), int(stime[1])) | |
118 | except ValueError: | |
119 | return None | |
120 | else: | |
121 | return None | |
122 | ||
123 | def cleanstring(data): | |
124 | """remove nonprintable chars from short desc | |
125 | """ | |
126 | for char in ['\x10', '\x00', '\x02', '\x15']: | |
127 | data = data.replace(char, '') | |
128 | return data | |
129 | ||
0 | 130 | def language_iso639_2to3(alpha2): |
131 | ret = alpha2 | |
132 | if alpha2 in LanguageCodes: | |
133 | language = LanguageCodes[alpha2] | |
134 | for alpha, name in LanguageCodes.items(): | |
135 | if name == language: | |
136 | if len(alpha) == 3: | |
137 | return alpha | |
138 | return ret | |
139 | ||
6 | 140 | class eitinfo(object): |
0 | 141 | """Eit File support class |
142 | Description | |
143 | http://de.wikipedia.org/wiki/Event_Information_Table | |
144 | """ | |
145 | def __init__(self, path=None): | |
146 | self.eit_file = None | |
147 | ||
148 | self.eit = {} | |
149 | self.iso = None | |
150 | ||
2 | 151 | self.load(path) |
0 | 152 | |
2 | 153 | def load(self, path): |
0 | 154 | if path: |
2 | 155 | self.eit_file = path |
156 | self._read_file() | |
0 | 157 | |
2 | 158 | def get_genre(self): |
159 | return self.eit.get('genre', "") | |
0 | 160 | |
2 | 161 | def get_components(self): |
162 | return self.eit.get('components', "") | |
0 | 163 | |
2 | 164 | def get_startdate(self): |
0 | 165 | return self.eit.get('startdate', "") |
166 | ||
2 | 167 | def get_starttime(self): |
0 | 168 | return self.eit.get('starttime', "") |
169 | ||
2 | 170 | def get_duration(self): |
0 | 171 | return self.eit.get('duration', "") |
172 | ||
2 | 173 | def get_name(self): |
0 | 174 | return self.eit.get('name', "").strip() |
175 | ||
2 | 176 | def get_description(self): |
0 | 177 | return self.eit.get('description', "").strip() |
178 | ||
2 | 179 | def get_duration_seconds(self): |
0 | 180 | length = self.eit.get('duration', "") |
1 | 181 | if len(length) > 2: |
2 | 182 | return mkint((length[0] * 60 + length[1]) * 60 + length[2]) |
1 | 183 | elif len(length) > 1: |
2 | 184 | return mkint(length[0] * 60 + length[1]) |
0 | 185 | else: |
2 | 186 | return mkint(length) |
0 | 187 | |
2 | 188 | def get_date(self): |
189 | return todate(self.get_startdate(), self.get_starttime()) | |
0 | 190 | |
191 | def dumpEit(self): | |
192 | print self.eit | |
193 | ||
194 | ############################################################################## | |
195 | ## File IO Functions | |
2 | 196 | def _read_file(self): |
0 | 197 | data = "" |
198 | path = self.eit_file | |
199 | ||
2 | 200 | lang = language_iso639_2to3("de") |
0 | 201 | |
202 | if path and os.path.exists(path): | |
1 | 203 | print "Reading Event Information Table " + str(path) |
0 | 204 | |
205 | # Read data from file | |
1 | 206 | fd = None |
0 | 207 | try: |
1 | 208 | fd = open(path, 'rb') |
0 | 209 | #lines = f.readlines() |
1 | 210 | data = fd.read() |
211 | except Exception, err: | |
212 | print "[META] Exception in readEitFile: " + str(err) | |
0 | 213 | finally: |
1 | 214 | if fd is not None: |
215 | fd.close() | |
0 | 216 | |
217 | # Parse the data | |
218 | if data and 12 <= len(data): | |
219 | # go through events | |
220 | pos = 0 | |
1 | 221 | e = struct.unpack(">HHBBBBBBH", data[pos:pos + 12]) |
0 | 222 | event_id = e[0] |
2 | 223 | date = parse_mjd(e[1]) # Y, M, D |
1 | 224 | time = bcd2dec(e[2]), bcd2dec(e[3]), bcd2dec(e[4]) # HH, MM, SS |
225 | duration = bcd2dec(e[5]), bcd2dec(e[6]), bcd2dec(e[7]) # HH, MM, SS | |
2 | 226 | #running_status = (e[8] & 0xe000) >> 13 |
227 | #free_CA_mode = e[8] & 0x1000 | |
0 | 228 | descriptors_len = e[8] & 0x0fff |
229 | ||
2 | 230 | #if running_status in [1, 2]: |
231 | # self.eit['when'] = "NEXT" | |
232 | #elif running_status in [3, 4]: | |
233 | # self.eit['when'] = "NOW" | |
0 | 234 | |
235 | self.eit['startdate'] = date | |
236 | self.eit['starttime'] = time | |
237 | self.eit['duration'] = duration | |
238 | ||
239 | pos = pos + 12 | |
240 | short_event_descriptor = [] | |
241 | short_event_descriptor_multi = [] | |
242 | extended_event_descriptor = [] | |
243 | extended_event_descriptor_multi = [] | |
244 | component_descriptor = [] | |
245 | content_descriptor = [] | |
246 | linkage_descriptor = [] | |
247 | parental_rating_descriptor = [] | |
248 | endpos = len(data) - 1 | |
249 | while pos < endpos: | |
250 | rec = ord(data[pos]) | |
1 | 251 | length = ord(data[pos + 1]) + 2 |
0 | 252 | if rec == 0x4D: |
1 | 253 | descriptor_tag = ord(data[pos + 1]) |
254 | descriptor_length = ord(data[pos + 2]) | |
255 | ISO_639_language_code = str(data[pos + 3:pos + 5]) | |
256 | event_name_length = ord(data[pos + 5]) | |
2 | 257 | short_event_description = cleanstring(data[pos + 6:pos + 6 + event_name_length]) |
258 | ||
259 | tmp_length = ord(data[pos + 6 + event_name_length]) | |
260 | self.eit['genre'] = cleanstring(data[pos + 7 + event_name_length:pos + 7 + tmp_length + event_name_length]) | |
261 | ||
0 | 262 | if ISO_639_language_code == lang: |
263 | short_event_descriptor.append(short_event_description) | |
264 | short_event_descriptor_multi.append(short_event_description) | |
265 | elif rec == 0x4E: | |
1 | 266 | ISO_639_language_code = str(data[pos + 3:pos + 5]) |
0 | 267 | extended_event_description = "" |
268 | extended_event_description_multi = "" | |
2 | 269 | for i in range(pos + 8, pos + length): |
270 | if str(ord(data[i])) == "138": | |
0 | 271 | extended_event_description += '\n' |
272 | extended_event_description_multi += '\n' | |
2 | 273 | elif data[i] not in ['\x10', '\x00', '\x02', '\x15']: |
274 | extended_event_description += data[i] | |
275 | extended_event_description_multi += data[i] | |
0 | 276 | if ISO_639_language_code == lang: |
277 | extended_event_descriptor.append(extended_event_description) | |
278 | extended_event_descriptor_multi.append(extended_event_description) | |
279 | elif rec == 0x50: | |
2 | 280 | #tmp_type = ord(data[pos + 3:pos + 4]) |
281 | #print "type: %x" % tmp_type | |
282 | component_descriptor.append(cleanstring(data[pos + 8:pos + length])) | |
0 | 283 | elif rec == 0x54: |
2 | 284 | content_descriptor.append(cleanstring(data[pos + 8:pos + length])) |
0 | 285 | elif rec == 0x4A: |
2 | 286 | linkage_descriptor.append(cleanstring(data[pos + 8:pos + length])) |
0 | 287 | elif rec == 0x55: |
2 | 288 | parental_rating_descriptor.append(cleanstring(data[pos + 2:pos + length])) |
0 | 289 | else: |
1 | 290 | print "unsupported descriptor: %x %x" % (rec, pos + 12) |
2 | 291 | print data[pos:pos + length] |
292 | ||
0 | 293 | pos += length |
294 | ||
2 | 295 | self.eit['components'] = ", ".join(component_descriptor) |
296 | ||
297 | ||
298 | ||
1 | 299 | # Very bad but there can be both encodings |
300 | # User files can be in cp1252 | |
301 | # Is there no other way? | |
302 | if short_event_descriptor: | |
303 | short_event_descriptor = "".join(short_event_descriptor) | |
304 | else: | |
305 | short_event_descriptor = "".join(short_event_descriptor_multi) | |
306 | if short_event_descriptor: | |
307 | #try: | |
308 | # short_event_descriptor = short_event_descriptor.decode("iso-8859-1").encode("utf-8") | |
309 | #except UnicodeDecodeError: | |
310 | # pass | |
311 | try: | |
312 | short_event_descriptor.decode('utf-8') | |
313 | except UnicodeDecodeError: | |
0 | 314 | try: |
1 | 315 | short_event_descriptor = short_event_descriptor.decode("cp1252").encode("utf-8") |
0 | 316 | except UnicodeDecodeError: |
1 | 317 | # do nothing, otherwise cyrillic wont properly displayed |
318 | #short_event_descriptor = short_event_descriptor.decode("iso-8859-1").encode("utf-8") | |
319 | pass | |
320 | if (lang == "cs") or (lang == "sk"): | |
2 | 321 | short_event_descriptor = str(convert_charspec_czsk(short_event_descriptor)) |
1 | 322 | if lang == "hr": |
2 | 323 | short_event_descriptor = str(convert_charspec_hr(short_event_descriptor)) |
1 | 324 | self.eit['name'] = short_event_descriptor |
0 | 325 | |
1 | 326 | # Very bad but there can be both encodings |
327 | # User files can be in cp1252 | |
328 | # Is there no other way? | |
329 | if extended_event_descriptor: | |
330 | extended_event_descriptor = "".join(extended_event_descriptor) | |
331 | else: | |
332 | extended_event_descriptor = "".join(extended_event_descriptor_multi) | |
333 | if extended_event_descriptor: | |
334 | #try: | |
335 | # extended_event_descriptor = extended_event_descriptor.decode("iso-8859-1").encode("utf-8") | |
336 | #except UnicodeDecodeError: | |
337 | # pass | |
338 | try: | |
339 | extended_event_descriptor.decode('utf-8') | |
340 | except UnicodeDecodeError: | |
0 | 341 | try: |
1 | 342 | extended_event_descriptor = extended_event_descriptor.decode("cp1252").encode("utf-8") |
0 | 343 | except UnicodeDecodeError: |
1 | 344 | # do nothing, otherwise cyrillic wont properly displayed |
345 | #extended_event_descriptor = extended_event_descriptor.decode("iso-8859-1").encode("utf-8") | |
346 | pass | |
347 | if (lang == "cs") or (lang == "sk"): | |
2 | 348 | extended_event_descriptor = str(convert_charspec_czsk(extended_event_descriptor)) |
1 | 349 | if lang == "hr": |
2 | 350 | extended_event_descriptor = str(convert_charspec_hr(extended_event_descriptor)) |
1 | 351 | self.eit['description'] = extended_event_descriptor |
0 | 352 | |
1 | 353 | else: |
354 | # No data clear all | |
355 | self.eit = {} | |
0 | 356 | |
357 | ||
358 | def readeit(eitfile): | |
1 | 359 | """Module docstring. |
360 | Read Eit File and show the information. | |
361 | """ | |
6 | 362 | eitlist = eitinfo(eitfile) |
9 | 363 | if len(eitlist.eit) == 0: |
364 | return None | |
6 | 365 | out = "Movie name: %s" % eitlist.get_name() |
366 | out += "\nGenre: %s" % eitlist.get_genre() | |
367 | out += "\nComponents: %s" % eitlist.get_components() | |
368 | out += "\nStartDate: %s" % eitlist.get_date() | |
369 | out += "\nDescription: %s" % eitlist.get_description() | |
370 | out += "\nDuration: %02i:%02i:%02i" % eitlist.get_duration() | |
371 | out += " (%s minutes)" % (eitlist.get_duration_seconds() / 60) | |
0 | 372 | |
6 | 373 | print out |
374 | return out | |
0 | 375 | |
376 | def main(): | |
377 | # parse command line options | |
378 | try: | |
379 | opts, args = getopt.getopt(sys.argv[1:], "h", ["help"]) | |
380 | except getopt.error, msg: | |
381 | print msg | |
382 | print "for help use --help" | |
383 | sys.exit(2) | |
384 | # process options | |
385 | for o, a in opts: | |
386 | if o in ("-h", "--help"): | |
387 | print __doc__ | |
388 | sys.exit(0) | |
389 | # process arguments | |
390 | for arg in args: | |
391 | readeit(arg) # process() is defined elsewhere | |
392 | ||
393 | if __name__ == "__main__": | |
394 | main() | |
395 |