eit.py

changeset 2
319f8c3fd394
parent 1
88b1fefb7c92
child 6
1420abafd049
equal deleted inserted replaced
1:88b1fefb7c92 2:319f8c3fd394
1 #!/usr/bin/python 1 #!/usr/bin/python
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 # iso-8859-2 3 # iso-8859-2
4 # 4 """
5 # EitSupport 5 EitSupport
6 # Copyright (C) 2011 betonme 6 Copyright (C) 2011 betonme
7 # Copyright (C) 2016 Wolfgang Fahl 7 Copyright (C) 2016 Wolfgang Fahl
8 # 8 Cleanup 2017 by mdd
9 """
9 # This EITParser is based on: 10 # This EITParser is based on:
10 # https://github.com/betonme/e2openplugin-EnhancedMovieCenter/blob/master/src/EitSupport.py 11 # https://github.com/betonme/e2openplugin-EnhancedMovieCenter/blob/master/src/EitSupport.py
11 # 12 #
12 # In case of reuse of this source code please do not remove this copyright. 13 # In case of reuse of this source code please do not remove this copyright.
13 # 14 #
22 # GNU General Public License for more details. 23 # GNU General Public License for more details.
23 # 24 #
24 # For more information on the GNU General Public License see: 25 # For more information on the GNU General Public License see:
25 # <http://www.gnu.org/licenses/>. 26 # <http://www.gnu.org/licenses/>.
26 # 27 #
28
29 # seite 36, inhalt der for schleife!
30 # https://www.dvb.org/resources/public/standards/a38_dvb-si_specification.pdf
27 31
28 import os 32 import os
29 import struct 33 import struct
30 import sys 34 import sys
31 import getopt 35 import getopt
45 # if ((byte>>bit)&1) ^ z32: 49 # if ((byte>>bit)&1) ^ z32:
46 # crc = crc ^ poly 50 # crc = crc ^ poly
47 # crc = crc & 0xffffffffL 51 # crc = crc & 0xffffffffL
48 # return crc 52 # return crc
49 53
50 decoding_charSpecHR = { 54 EIT_SHORT_EVENT_DESCRIPTOR = 0x4d
55 EIT_EXTENDED_EVENT_DESCRIPOR = 0x4e
56
57 CHARSPEC_HR = {
51 u'Ć': u'\u0106', u'æ': u'\u0107', u'®': u'\u017D', u'¾': u'\u017E', 58 u'Ć': u'\u0106', u'æ': u'\u0107', u'®': u'\u017D', u'¾': u'\u017E',
52 u'©': u'\u0160', u'¹': u'\u0161', u'Č': u'\u010C', u'è': u'\u010D', u'ð': u'\u0111' 59 u'©': u'\u0160', u'¹': u'\u0161', u'Č': u'\u010C', u'è': u'\u010D', u'ð': u'\u0111'
53 } 60 }
54 61
55 decoding_charSpecCZSK = { 62 CHARSPEC_CZSK = {
56 u'Ï'+u'C': u'Č', u'Ï'+u'E': u'Ě', u'Ï'+u'L': u'Ľ', u'Ï'+u'N': u'Ň', u'Ï'+u'R': u'Ř', 63 u'Ï'+u'C': u'Č', u'Ï'+u'E': u'Ě', u'Ï'+u'L': u'Ľ', u'Ï'+u'N': u'Ň', u'Ï'+u'R': u'Ř',
57 u'Ï'+u'S': u'Š', u'Ï'+u'T': u'Ť', u'Ï'+u'Z': u'Ž', u'Ï'+u'c': u'č', u'Ï'+u'd': u'ď', 64 u'Ï'+u'S': u'Š', u'Ï'+u'T': u'Ť', u'Ï'+u'Z': u'Ž', u'Ï'+u'c': u'č', u'Ï'+u'd': u'ď',
58 u'Ï'+u'e': u'ě', u'Ï'+u'l': u'ľ', u'Ï'+u'n': u'ň', u'Ï'+u'r': u'ř', u'Ï'+u's': u'š', 65 u'Ï'+u'e': u'ě', u'Ï'+u'l': u'ľ', u'Ï'+u'n': u'ň', u'Ï'+u'r': u'ř', u'Ï'+u's': u'š',
59 u'Ï'+u't': u'ť', u'Ï'+u'z': u'ž', u'Ï'+u'D': u'Ď', u'Â'+u'A': u'Á', u'Â'+u'E': u'É', 66 u'Ï'+u't': u'ť', u'Ï'+u'z': u'ž', u'Ï'+u'D': u'Ď', u'Â'+u'A': u'Á', u'Â'+u'E': u'É',
60 u'Â'+u'I': u'Í', u'Â'+u'O': u'Ó', u'Â'+u'U': u'Ú', u'Â'+u'a': u'á', u'Â'+u'e': u'é', 67 u'Â'+u'I': u'Í', u'Â'+u'O': u'Ó', u'Â'+u'U': u'Ú', u'Â'+u'a': u'á', u'Â'+u'e': u'é',
62 u'Ã'+u'O': u'Ô', u'Ê'+u'u': u'ů', u'Ê'+u'U': u'Ů', u'È'+u'A': u'Ä', u'È'+u'E': u'Ë', 69 u'Ã'+u'O': u'Ô', u'Ê'+u'u': u'ů', u'Ê'+u'U': u'Ů', u'È'+u'A': u'Ä', u'È'+u'E': u'Ë',
63 u'È'+u'I': u'Ï', u'È'+u'O': u'Ö', u'È'+u'U': u'Ü', u'È'+u'Y': u'Ÿ', u'È'+u'a': u'ä', 70 u'È'+u'I': u'Ï', u'È'+u'O': u'Ö', u'È'+u'U': u'Ü', u'È'+u'Y': u'Ÿ', u'È'+u'a': u'ä',
64 u'È'+u'e': u'ë', u'È'+u'i': u'ï', u'È'+u'o': u'ö', u'È'+u'u': u'ü', u'È'+u'y': u'ÿ' 71 u'È'+u'e': u'ë', u'È'+u'i': u'ï', u'È'+u'o': u'ö', u'È'+u'u': u'ü', u'È'+u'y': u'ÿ'
65 } 72 }
66 73
67 def emcDebugOut(msg): 74 def convert_charspec_hr(text):
68 print msg 75 for i, j in CHARSPEC_HR.iteritems():
69
70 def convertCharSpecHR(text):
71 for i, j in decoding_charSpecHR.iteritems():
72 text = text.replace(i, j) 76 text = text.replace(i, j)
73 return text 77 return text
74 78
75 def convertCharSpecCZSK(text): 79 def convert_charspec_czsk(text):
76 for i, j in decoding_charSpecCZSK.iteritems(): 80 for i, j in CHARSPEC_CZSK.iteritems():
77 text = text.replace(i, j) 81 text = text.replace(i, j)
78 return text 82 return text
79 83
80 def parseMJD(mjd): 84 def parse_mjd(mjd):
81 """Parse 16 bit unsigned int containing Modified Julian Date, 85 """Parse 16 bit unsigned int containing Modified Julian Date,
82 as per DVB-SI spec 86 as per DVB-SI spec
83 returning year,month,day""" 87 returning year,month,day"""
84 year = int((mjd - 15078.2) / 365.25) 88 year = int((mjd - 15078.2) / 365.25)
85 month = int((mjd - 14956.1 - int(year * 365.25)) / 30.6001) 89 month = int((mjd - 14956.1 - int(year * 365.25)) / 30.6001)
86 day = mjd - 14956 - int(year * 365.25) - int(month * 30.6001) 90 day = mjd - 14956 - int(year * 365.25) - int(month * 30.6001)
87 correction = 0 91 correction = 0
88 if month == 14 or month == 15: 92 if month == 14 or month == 15:
89 correction = 1 93 correction = 1
90 return (1900 + year + correction), (month - 1 - correction * 12), day 94 return (1900 + year + correction), (month - 1 - correction * 12), day
91 95
92 def bcd2dec(byte): 96 def bcd2dec(byte):
93 return (byte >> 4) * 10 + (byte & 0xf) 97 return (byte >> 4) * 10 + (byte & 0xf)
98
99
100 def mkint(data):
101 """
102 Convert string to Integer
103 """
104 return int(data) if data else 0
105
106 def todate(sdate, stime):
107 """
108 Convert date and time to datetime tuple
109 """
110 if sdate and stime:
111 try:
112 return datetime(
113 int(sdate[0]), int(sdate[1]), int(sdate[2]),
114 int(stime[0]), int(stime[1]))
115 except ValueError:
116 return None
117 else:
118 return None
119
120 def cleanstring(data):
121 """remove nonprintable chars from short desc
122 """
123 for char in ['\x10', '\x00', '\x02', '\x15']:
124 data = data.replace(char, '')
125 return data
94 126
95 def language_iso639_2to3(alpha2): 127 def language_iso639_2to3(alpha2):
96 ret = alpha2 128 ret = alpha2
97 if alpha2 in LanguageCodes: 129 if alpha2 in LanguageCodes:
98 language = LanguageCodes[alpha2] 130 language = LanguageCodes[alpha2]
100 if name == language: 132 if name == language:
101 if len(alpha) == 3: 133 if len(alpha) == 3:
102 return alpha 134 return alpha
103 return ret 135 return ret
104 136
105 class EitList(): 137 class EitList(object):
106 """Eit File support class 138 """Eit File support class
107 Description 139 Description
108 http://de.wikipedia.org/wiki/Event_Information_Table 140 http://de.wikipedia.org/wiki/Event_Information_Table
109 """ 141 """
110 EIT_SHORT_EVENT_DESCRIPTOR = 0x4d
111 EIT_EXTENDED_EVENT_DESCRIPOR = 0x4e
112
113 def __init__(self, path=None): 142 def __init__(self, path=None):
114 self.eit_file = None 143 self.eit_file = None
115 144
116 self.eit = {} 145 self.eit = {}
117 self.iso = None 146 self.iso = None
118 147
119 self.__newPath(path) 148 self.load(path)
120 self.__readEitFile() 149
121 150 def load(self, path):
122 def __newPath(self, path):
123 name = None
124 if path: 151 if path:
125 if self.eit_file != path: 152 self.eit_file = path
126 self.eit_file = path 153 self._read_file()
127 154
128 def __mk_int(self, s): 155 def get_genre(self):
129 return int(s) if s else 0 156 return self.eit.get('genre', "")
130 157
131 def __toDate(self, d, t): 158 def get_components(self):
132 if d and t: 159 return self.eit.get('components', "")
133 try: 160
134 return datetime( 161 def get_startdate(self):
135 int(d[0]), int(d[1]), int(d[2]),
136 int(t[0]), int(t[1]))
137 except ValueError:
138 return None
139 else:
140 return None
141
142 def getEitsid(self):
143 return self.eit.get('service', "") #TODO
144
145 def getEitTsId(self):
146 return self.eit.get('transportstream', "") #TODO
147
148 def getEitWhen(self):
149 return self.eit.get('when', "")
150
151 def getEitStartDate(self):
152 return self.eit.get('startdate', "") 162 return self.eit.get('startdate', "")
153 163
154 def getEitStartTime(self): 164 def get_starttime(self):
155 return self.eit.get('starttime', "") 165 return self.eit.get('starttime', "")
156 166
157 def getEitDuration(self): 167 def get_duration(self):
158 return self.eit.get('duration', "") 168 return self.eit.get('duration', "")
159 169
160 def getEitName(self): 170 def get_name(self):
161 return self.eit.get('name', "").strip() 171 return self.eit.get('name', "").strip()
162 172
163 def getEitDescription(self): 173 def get_description(self):
164 return self.eit.get('description', "").strip() 174 return self.eit.get('description', "").strip()
165 175
166 # Wrapper 176 def get_duration_seconds(self):
167 def getEitShortDescription(self):
168 return self.getEitName()
169
170 def getEitExtendedDescription(self):
171 return self.getEitDescription()
172
173 def getEitLengthInSeconds(self):
174 length = self.eit.get('duration', "") 177 length = self.eit.get('duration', "")
175 if len(length) > 2: 178 if len(length) > 2:
176 return self.__mk_int((length[0] * 60 + length[1]) * 60 + length[2]) 179 return mkint((length[0] * 60 + length[1]) * 60 + length[2])
177 elif len(length) > 1: 180 elif len(length) > 1:
178 return self.__mk_int(length[0] * 60 + length[1]) 181 return mkint(length[0] * 60 + length[1])
179 else: 182 else:
180 return self.__mk_int(length) 183 return mkint(length)
181 184
182 def getEitDate(self): 185 def get_date(self):
183 return self.__toDate(self.getEitStartDate(), self.getEitStartTime()) 186 return todate(self.get_startdate(), self.get_starttime())
184 187
185 def dumpEit(self): 188 def dumpEit(self):
186 print self.eit 189 print self.eit
187 190
188 ############################################################################## 191 ##############################################################################
189 ## File IO Functions 192 ## File IO Functions
190 def __readEitFile(self): 193 def _read_file(self):
191 data = "" 194 data = ""
192 path = self.eit_file 195 path = self.eit_file
193 196
194 lang = language_iso639_2to3( "de" ) 197 lang = language_iso639_2to3("de")
195 198
196 if path and os.path.exists(path): 199 if path and os.path.exists(path):
197 print "Reading Event Information Table " + str(path) 200 print "Reading Event Information Table " + str(path)
198 201
199 # Read data from file 202 # Read data from file
212 if data and 12 <= len(data): 215 if data and 12 <= len(data):
213 # go through events 216 # go through events
214 pos = 0 217 pos = 0
215 e = struct.unpack(">HHBBBBBBH", data[pos:pos + 12]) 218 e = struct.unpack(">HHBBBBBBH", data[pos:pos + 12])
216 event_id = e[0] 219 event_id = e[0]
217 date = parseMJD(e[1]) # Y, M, D 220 date = parse_mjd(e[1]) # Y, M, D
218 time = bcd2dec(e[2]), bcd2dec(e[3]), bcd2dec(e[4]) # HH, MM, SS 221 time = bcd2dec(e[2]), bcd2dec(e[3]), bcd2dec(e[4]) # HH, MM, SS
219 duration = bcd2dec(e[5]), bcd2dec(e[6]), bcd2dec(e[7]) # HH, MM, SS 222 duration = bcd2dec(e[5]), bcd2dec(e[6]), bcd2dec(e[7]) # HH, MM, SS
220 running_status = (e[8] & 0xe000) >> 13 223 #running_status = (e[8] & 0xe000) >> 13
221 free_CA_mode = e[8] & 0x1000 224 #free_CA_mode = e[8] & 0x1000
222 descriptors_len = e[8] & 0x0fff 225 descriptors_len = e[8] & 0x0fff
223 226
224 if running_status in [1, 2]: 227 #if running_status in [1, 2]:
225 self.eit['when'] = "NEXT" 228 # self.eit['when'] = "NEXT"
226 elif running_status in [3, 4]: 229 #elif running_status in [3, 4]:
227 self.eit['when'] = "NOW" 230 # self.eit['when'] = "NOW"
228 231
229 self.eit['startdate'] = date 232 self.eit['startdate'] = date
230 self.eit['starttime'] = time 233 self.eit['starttime'] = time
231 self.eit['duration'] = duration 234 self.eit['duration'] = duration
232 235
246 if rec == 0x4D: 249 if rec == 0x4D:
247 descriptor_tag = ord(data[pos + 1]) 250 descriptor_tag = ord(data[pos + 1])
248 descriptor_length = ord(data[pos + 2]) 251 descriptor_length = ord(data[pos + 2])
249 ISO_639_language_code = str(data[pos + 3:pos + 5]) 252 ISO_639_language_code = str(data[pos + 3:pos + 5])
250 event_name_length = ord(data[pos + 5]) 253 event_name_length = ord(data[pos + 5])
251 short_event_description = data[pos + 6:pos + 6 + event_name_length] 254 short_event_description = cleanstring(data[pos + 6:pos + 6 + event_name_length])
255
256 tmp_length = ord(data[pos + 6 + event_name_length])
257 self.eit['genre'] = cleanstring(data[pos + 7 + event_name_length:pos + 7 + tmp_length + event_name_length])
258
252 if ISO_639_language_code == lang: 259 if ISO_639_language_code == lang:
253 short_event_descriptor.append(short_event_description) 260 short_event_descriptor.append(short_event_description)
254 short_event_descriptor_multi.append(short_event_description) 261 short_event_descriptor_multi.append(short_event_description)
255 elif rec == 0x4E: 262 elif rec == 0x4E:
256 ISO_639_language_code = str(data[pos + 3:pos + 5]) 263 ISO_639_language_code = str(data[pos + 3:pos + 5])
257 extended_event_description = "" 264 extended_event_description = ""
258 extended_event_description_multi = "" 265 extended_event_description_multi = ""
259 for i in range (pos+8,pos+length): 266 for i in range(pos + 8, pos + length):
260 if str(ord(data[i]))=="138": 267 if str(ord(data[i])) == "138":
261 extended_event_description += '\n' 268 extended_event_description += '\n'
262 extended_event_description_multi += '\n' 269 extended_event_description_multi += '\n'
263 else: 270 elif data[i] not in ['\x10', '\x00', '\x02', '\x15']:
264 if data[i]=='\x10' or data[i]=='\x00' or data[i]=='\x02': 271 extended_event_description += data[i]
265 pass 272 extended_event_description_multi += data[i]
266 else:
267 extended_event_description += data[i]
268 extended_event_description_multi += data[i]
269 if ISO_639_language_code == lang: 273 if ISO_639_language_code == lang:
270 extended_event_descriptor.append(extended_event_description) 274 extended_event_descriptor.append(extended_event_description)
271 extended_event_descriptor_multi.append(extended_event_description) 275 extended_event_descriptor_multi.append(extended_event_description)
272 elif rec == 0x50: 276 elif rec == 0x50:
273 component_descriptor.append(data[pos + 8:pos + length]) 277 #tmp_type = ord(data[pos + 3:pos + 4])
278 #print "type: %x" % tmp_type
279 component_descriptor.append(cleanstring(data[pos + 8:pos + length]))
274 elif rec == 0x54: 280 elif rec == 0x54:
275 content_descriptor.append(data[pos + 8:pos + length]) 281 content_descriptor.append(cleanstring(data[pos + 8:pos + length]))
276 elif rec == 0x4A: 282 elif rec == 0x4A:
277 linkage_descriptor.append(data[pos + 8:pos + length]) 283 linkage_descriptor.append(cleanstring(data[pos + 8:pos + length]))
278 elif rec == 0x55: 284 elif rec == 0x55:
279 parental_rating_descriptor.append(data[pos + 2:pos + length]) 285 parental_rating_descriptor.append(cleanstring(data[pos + 2:pos + length]))
280 else: 286 else:
281 print "unsupported descriptor: %x %x" % (rec, pos + 12) 287 print "unsupported descriptor: %x %x" % (rec, pos + 12)
282 #print data[pos:pos+length] 288 print data[pos:pos + length]
283 pass 289
284 pos += length 290 pos += length
291
292 self.eit['components'] = ", ".join(component_descriptor)
293
294
285 295
286 # Very bad but there can be both encodings 296 # Very bad but there can be both encodings
287 # User files can be in cp1252 297 # User files can be in cp1252
288 # Is there no other way? 298 # Is there no other way?
289 if short_event_descriptor: 299 if short_event_descriptor:
303 except UnicodeDecodeError: 313 except UnicodeDecodeError:
304 # do nothing, otherwise cyrillic wont properly displayed 314 # do nothing, otherwise cyrillic wont properly displayed
305 #short_event_descriptor = short_event_descriptor.decode("iso-8859-1").encode("utf-8") 315 #short_event_descriptor = short_event_descriptor.decode("iso-8859-1").encode("utf-8")
306 pass 316 pass
307 if (lang == "cs") or (lang == "sk"): 317 if (lang == "cs") or (lang == "sk"):
308 short_event_descriptor = str(convertCharSpecCZSK(short_event_descriptor)) 318 short_event_descriptor = str(convert_charspec_czsk(short_event_descriptor))
309 if lang == "hr": 319 if lang == "hr":
310 short_event_descriptor = str(convertCharSpecHR(short_event_descriptor)) 320 short_event_descriptor = str(convert_charspec_hr(short_event_descriptor))
311 self.eit['name'] = short_event_descriptor 321 self.eit['name'] = short_event_descriptor
312 322
313 # Very bad but there can be both encodings 323 # Very bad but there can be both encodings
314 # User files can be in cp1252 324 # User files can be in cp1252
315 # Is there no other way? 325 # Is there no other way?
330 except UnicodeDecodeError: 340 except UnicodeDecodeError:
331 # do nothing, otherwise cyrillic wont properly displayed 341 # do nothing, otherwise cyrillic wont properly displayed
332 #extended_event_descriptor = extended_event_descriptor.decode("iso-8859-1").encode("utf-8") 342 #extended_event_descriptor = extended_event_descriptor.decode("iso-8859-1").encode("utf-8")
333 pass 343 pass
334 if (lang == "cs") or (lang == "sk"): 344 if (lang == "cs") or (lang == "sk"):
335 extended_event_descriptor = str(convertCharSpecCZSK(extended_event_descriptor)) 345 extended_event_descriptor = str(convert_charspec_czsk(extended_event_descriptor))
336 if lang == "hr": 346 if lang == "hr":
337 extended_event_descriptor = str(convertCharSpecHR(extended_event_descriptor)) 347 extended_event_descriptor = str(convert_charspec_hr(extended_event_descriptor))
338 self.eit['description'] = extended_event_descriptor 348 self.eit['description'] = extended_event_descriptor
339 349
340 else: 350 else:
341 # No data clear all 351 # No data clear all
342 self.eit = {} 352 self.eit = {}
345 def readeit(eitfile): 355 def readeit(eitfile):
346 """Module docstring. 356 """Module docstring.
347 Read Eit File and show the information. 357 Read Eit File and show the information.
348 """ 358 """
349 eitlist = EitList(eitfile) 359 eitlist = EitList(eitfile)
350 print "Name: ", eitlist.getEitName() 360 print "Name: ", eitlist.get_name()
351 print "StartDate: ", eitlist.getEitStartDate() 361 print "Genre: ", eitlist.get_genre()
352 print "Description: ", eitlist.getEitDescription() 362 print "Components: ", eitlist.get_components()
353 print "Duration: ", eitlist.getEitDuration() 363 print "StartDate: ", eitlist.get_date()
354 print "Seconds: ", eitlist.getEitLengthInSeconds() 364 print "Description: ", eitlist.get_description()
365 print "Duration: ", eitlist.get_duration()
366 print "Minutes: ", eitlist.get_duration_seconds() / 60
355 367
356 #eitlist.dumpEit() 368 #eitlist.dumpEit()
357 369
358 def main(): 370 def main():
359 # parse command line options 371 # parse command line options

mercurial