Ticket #2: xml.py

File xml.py, 8.3 kB (added by anonymous, 1 year ago)

incomplete test of this

Line 
1 import transcode, os, socket, re
2 from Cheetah.Template import Template
3 from plugin import Plugin
4 from urllib import unquote_plus, quote, unquote
5 from urlparse import urlparse
6 from xml.sax.saxutils import escape
7 from xml.sax import saxexts, saxlib, saxutils
8 from lrucache import LRUCache
9 from UserDict import DictMixin
10 from datetime import datetime, timedelta
11 import config
12
13 SCRIPTDIR = os.path.dirname(__file__)
14
15 CLASS_NAME = 'XML'
16
17 class XML(Plugin):
18
19     CONTENT_TYPE = 'x-container/tivo-videos'
20
21     def send_file(self, handler, container, name):
22        
23         #No longer a 'cheep' hack :p
24         if handler.headers.getheader('Range') and not handler.headers.getheader('Range') == 'bytes=0-':
25             handler.send_response(206)
26             handler.send_header('Connection', 'close')
27             handler.send_header('Content-Type', 'video/x-tivo-mpeg')
28             handler.send_header('Transfer-Encoding', 'chunked')
29             handler.send_header('Server', 'TiVo Server/1.4.257.475')
30             handler.end_headers()
31             handler.wfile.write("\x30\x0D\x0A")
32             return
33
34         tsn =  handler.headers.getheader('tsn', '')
35
36         o = urlparse("http://fake.host" + handler.path)
37         path = unquote_plus(o[2])
38         handler.send_response(200)
39         handler.end_headers()
40         transcode.output_video(container['path'] + path[len(name)+1:], handler.wfile, tsn)
41        
42     def __isdir(self, full_path):
43         return os.path.isdir(full_path)
44
45     def __duration(self, full_path):
46         return transcode.video_info(full_path)[4]
47
48     def __est_size(self, full_path):
49         #Size is estimated by taking audio and video bit rate adding 2%
50
51         if transcode.tivo_compatable(full_path):  # Is TiVo compatible mpeg2
52             return int(os.stat(full_path).st_size)
53         else# Must be re-encoded
54             audioBPS = strtod(config.getAudioBR())
55             videoBPS = strtod(config.getVideoBR())
56             bitrate =  audioBPS + videoBPS
57             return int((self.__duration(full_path)/1000)*(bitrate * 1.02 / 8))
58    
59     def __getMetadateFromTxt(self, full_path):
60         metadata = {}
61
62         description_file = full_path + '.txt'
63         if os.path.exists(description_file):
64             for line in open(description_file):
65                 if line.strip().startswith('#'):
66                     continue
67                 if not ':' in line:
68                     continue
69
70                 key, value = line.split(':', 1)
71                 key = key.strip()
72                 value = value.strip()
73
74                 if key.startswith('v'):
75                     if key in metadata:
76                         metadata[key].append(value)
77                     else:
78                         metadata[key] = [value]
79                 else:
80                     metadata[key] = value
81
82         return metadata
83
84     def __metadata(self, full_path):
85
86         metadata = {}
87
88         base_path, title = os.path.split(full_path)
89         now = datetime.now()
90         originalAirDate = datetime.fromtimestamp(os.stat(full_path).st_ctime)
91         duration = self.__duration(full_path)
92         duration_delta = timedelta(milliseconds = duration)
93        
94         metadata['title'] = '.'.join(title.split('.')[:-1])
95         metadata['seriesTitle'] = os.path.split(base_path)[1]
96         metadata['originalAirDate'] = originalAirDate.isoformat()
97         metadata['time'] = now.isoformat()
98         metadata['startTime'] = now.isoformat()
99         metadata['stopTime'] = (now + duration_delta).isoformat()
100
101         metadata.update( self.__getMetadateFromTxt(full_path) )
102        
103         metadata['size'] = self.__est_size(full_path)
104         metadata['duration'] = duration
105
106         min = duration_delta.seconds / 60
107         sec = duration_delta.seconds % 60
108         hours = min / 60
109         min = min % 60
110         metadata['iso_durarion'] = 'P' + str(duration_delta.days) + 'DT' + str(hours) + 'H' + str(min) + 'M' + str(sec) + 'S'
111
112         return metadata
113            
114     def QueryContainer(self, handler, query):
115        
116         subcname = query['Container'][0]
117         cname = subcname.split('/')[0]
118        
119         #had to delete some things that might make it unusable
120         
121         format, type, value = line.split(':', 2)
122         format = format.strip()
123         value = value.strip()
124         type = type.strip()
125         if format == 'url' or format == 'file':
126              transcode.output_video(container['path'] + path[len(name)+1:], handler.wfile, tsn)
127              return           
128         elif format == 'rss':
129              parser = saxexts.make_parser()
130              if type.contains('youtube') or type.contains('yt'):
131                  parser.setDocumentHandler(DocumentHandler.ytHandler())
132              elif type.contains('videocast'):
133                  parser.setDocumentHandler(DocumentHandler.videocastHandler())
134              parser.parse(full_path)
135              
136         else:
137              return
138
139         videos = []
140         for file in files:
141             path = self.get_local_path(handler, query)
142             full_path = os.path.join(path, file)
143            
144             video = VideoDetails()
145             video['name'] = file
146             video['title'] = file
147             video['is_dir'] = self.__isdir(full_path)
148             if not  video['is_dir']:
149                 video['title'] = '.'.join(file.split('.')[:-1])
150                 video.update(self.__metadata(full_path))
151
152             videos.append(video)
153
154         handler.send_response(200)
155         handler.end_headers()
156         t = Template(file=os.path.join(SCRIPTDIR,'templates', 'container.tmpl'))
157         t.name = subcname
158         t.total = total
159         t.start = start
160         t.videos = videos
161         t.quote = quote
162         t.escape = escape
163         handler.wfile.write(t)
164
165     def TVBusQuery(self, handler, query):
166        
167         file = query['File'][0]
168         path = self.get_local_path(handler, query)
169         file_path = os.path.join(path, file)
170
171        
172         file_info = VideoDetails()
173         file_info.update(self.__metadata(file_path))
174
175         print file_info
176
177         handler.send_response(200)
178         handler.end_headers()
179         t = Template(file=os.path.join(SCRIPTDIR,'templates', 'TvBus.tmpl'))
180         t.video = file_info
181         t.escape = escape
182         handler.wfile.write(t)
183
184
185
186
187
188    
189 class VideoDetails(DictMixin):
190    
191     def __init__(self, d = None):
192         if d:
193             self.d = d
194         else:
195             self.d = {}
196
197     def __getitem__(self, key):
198         if key not in self.d:
199             self.d[key] = self.default(key)
200         return self.d[key]
201
202     def __contains__(self, key):
203         return True
204
205     def __setitem__(self, key, value):
206         self.d[key] = value
207
208     def __delitem__(self):
209         del self.d[key]
210    
211     def keys(self):
212         return self.d.keys()
213    
214     def __iter__(self):
215         return self.d.__iter__()
216
217     def iteritems(self):
218         return self.d.iteritems()
219
220     def default(self, key):
221         defaults = {
222             'showingBits' : '0',
223             'episodeNumber' : '0',
224             'displayMajorNumber' : '0',
225             'displayMinorNumber' : '0',
226             'isEpisode' : 'true',
227             'colorCode' : ('COLOR', '4'),
228             'showType' : ('SERIES', '5'),
229             'tvRating' : ('NR', '7'),
230         }
231         if key in defaults:
232             return defaults[key]
233         elif key.startswith('v'):
234             return []
235         else:
236             return ''           
237
238 # Parse a bitrate using the SI/IEEE suffix values as if by ffmpeg
239 # For example, 2K==2000, 2Ki==2048, 2MB==16000000, 2MiB==16777216
240 # Algorithm: http://svn.mplayerhq.hu/ffmpeg/trunk/libavcodec/eval.c
241 def strtod(value):
242     prefixes = {"y":-24,"z":-21,"a":-18,"f":-15,"p":-12,"n":-9,"u":-6,"m":-3,"c":-2,"d":-1,"h":2,"k":3,"K":3,"M":6,"G":9,"T":12,"P":15,"E":18,"Z":21,"Y":24}
243     p = re.compile(r'^(\d+)(?:([yzafpnumcdhkKMGTPEZY])(i)?)?([Bb])?$')
244     m = p.match(value)
245     if m is None:
246         raise SyntaxError('Invalid bit value syntax')
247     (coef, prefix, power, byte) = m.groups()
248     if prefix is None:
249         value = float(coef)
250     else:
251         exponent = float(prefixes[prefix])
252         if power == "i":
253             # Use powers of 2
254             value = float(coef) * pow(2.0, exponent/0.3)
255         else:
256             # Use powers of 10
257             value = float(coef) * pow(10.0, exponent)
258     if byte == "B": # B==Byte, b=bit
259         value *= 8;
260     return value