| 1 |
# -*- coding: utf-8 -*- |
|---|
| 2 |
|
|---|
| 3 |
# Licensed under the MIT license |
|---|
| 4 |
# http://opensource.org/licenses/mit-license.php |
|---|
| 5 |
|
|---|
| 6 |
# Copyright 2008, Benjamin Kampmann <ben.kampmann@googlemail.com> |
|---|
| 7 |
|
|---|
| 8 |
""" |
|---|
| 9 |
This is a Media Backend that allows you to access the Trailers from Apple.com |
|---|
| 10 |
""" |
|---|
| 11 |
|
|---|
| 12 |
from coherence.backend import BackendItem, BackendStore |
|---|
| 13 |
from coherence.upnp.core import DIDLLite |
|---|
| 14 |
from coherence.upnp.core.utils import ReverseProxyUriResource |
|---|
| 15 |
from twisted.web import client |
|---|
| 16 |
from twisted.internet import task, reactor |
|---|
| 17 |
|
|---|
| 18 |
from coherence.extern.et import parse_xml |
|---|
| 19 |
|
|---|
| 20 |
# XML_URL = "http://www.apple.com/trailers/home/xml/current.xml" |
|---|
| 21 |
XML_URL = "http://www.apple.com/trailers/home/xml/current_720p.xml" |
|---|
| 22 |
|
|---|
| 23 |
ROOT_ID = 0 |
|---|
| 24 |
|
|---|
| 25 |
class AppleTrailerProxy(ReverseProxyUriResource): |
|---|
| 26 |
|
|---|
| 27 |
def __init__(self, uri): |
|---|
| 28 |
ReverseProxyUriResource.__init__(self, uri) |
|---|
| 29 |
|
|---|
| 30 |
def render(self, request): |
|---|
| 31 |
request.received_headers['user-agent'] = 'QuickTime/7.6.2 (qtver=7.6.2;os=Windows NT 5.1Service Pack 3)' |
|---|
| 32 |
return ReverseProxyUriResource.render(self, request) |
|---|
| 33 |
|
|---|
| 34 |
|
|---|
| 35 |
class Trailer(BackendItem): |
|---|
| 36 |
|
|---|
| 37 |
def __init__(self, parent_id, urlbase, id=None, name=None, cover=None, |
|---|
| 38 |
url=None): |
|---|
| 39 |
self.parentid = parent_id |
|---|
| 40 |
self.id = id |
|---|
| 41 |
self.name = name |
|---|
| 42 |
self.cover = cover |
|---|
| 43 |
if( len(urlbase) and urlbase[-1] != '/'): |
|---|
| 44 |
urlbase += '/' |
|---|
| 45 |
self.url = urlbase + str(self.id) |
|---|
| 46 |
self.location = AppleTrailerProxy(url) |
|---|
| 47 |
self.item = DIDLLite.VideoItem(id, parent_id, self.name) |
|---|
| 48 |
self.item.albumArtURI = self.cover |
|---|
| 49 |
|
|---|
| 50 |
def get_path(self): |
|---|
| 51 |
return self.url |
|---|
| 52 |
|
|---|
| 53 |
|
|---|
| 54 |
class Container(BackendItem): |
|---|
| 55 |
|
|---|
| 56 |
logCategory = 'apple_trailers' |
|---|
| 57 |
|
|---|
| 58 |
def __init__(self, id, parent_id, name, store=None, \ |
|---|
| 59 |
children_callback=None): |
|---|
| 60 |
self.id = id |
|---|
| 61 |
self.parent_id = parent_id |
|---|
| 62 |
self.name = name |
|---|
| 63 |
self.mimetype = 'directory' |
|---|
| 64 |
self.update_id = 0 |
|---|
| 65 |
self.children = [] |
|---|
| 66 |
|
|---|
| 67 |
self.item = DIDLLite.Container(id, parent_id, self.name) |
|---|
| 68 |
self.item.childCount = None #self.get_child_count() |
|---|
| 69 |
|
|---|
| 70 |
def get_children(self, start=0, end=0): |
|---|
| 71 |
if(end - start > 25 or |
|---|
| 72 |
start - end == start or |
|---|
| 73 |
end - start == 0): |
|---|
| 74 |
end = start+25 |
|---|
| 75 |
if end != 0: |
|---|
| 76 |
return self.children[start:end] |
|---|
| 77 |
return self.children[start:] |
|---|
| 78 |
|
|---|
| 79 |
def get_child_count(self): |
|---|
| 80 |
return len(self.children) |
|---|
| 81 |
|
|---|
| 82 |
def get_item(self): |
|---|
| 83 |
return self.item |
|---|
| 84 |
|
|---|
| 85 |
def get_name(self): |
|---|
| 86 |
return self.name |
|---|
| 87 |
|
|---|
| 88 |
def get_id(self): |
|---|
| 89 |
return self.id |
|---|
| 90 |
|
|---|
| 91 |
class AppleTrailersStore(BackendStore): |
|---|
| 92 |
|
|---|
| 93 |
logCategory = 'apple_trailers' |
|---|
| 94 |
implements = ['MediaServer'] |
|---|
| 95 |
|
|---|
| 96 |
def __init__(self, server, *args, **kwargs): |
|---|
| 97 |
BackendStore.__init__(self,server,**kwargs) |
|---|
| 98 |
self.next_id = 1000 |
|---|
| 99 |
self.name = kwargs.get('name','Apple Trailers') |
|---|
| 100 |
self.refresh = int(kwargs.get('refresh', 8)) * (60 *60) |
|---|
| 101 |
|
|---|
| 102 |
self.trailers = {} |
|---|
| 103 |
|
|---|
| 104 |
self.wmc_mapping = {'15': 0} |
|---|
| 105 |
|
|---|
| 106 |
dfr = self.update_data() |
|---|
| 107 |
# first get the first bunch of data before sending init_completed |
|---|
| 108 |
dfr.addCallback(lambda x: self.init_completed()) |
|---|
| 109 |
|
|---|
| 110 |
def queue_update(self, result): |
|---|
| 111 |
reactor.callLater(self.refresh, self.update_data) |
|---|
| 112 |
return result |
|---|
| 113 |
|
|---|
| 114 |
def update_data(self): |
|---|
| 115 |
dfr = client.getPage(XML_URL) |
|---|
| 116 |
dfr.addCallback(parse_xml) |
|---|
| 117 |
dfr.addCallback(self.parse_data) |
|---|
| 118 |
dfr.addCallback(self.queue_update) |
|---|
| 119 |
return dfr |
|---|
| 120 |
|
|---|
| 121 |
def parse_data(self, xml_data): |
|---|
| 122 |
|
|---|
| 123 |
def iterate(root): |
|---|
| 124 |
for item in root.findall('./movieinfo'): |
|---|
| 125 |
trailer = self._parse_into_trailer(item) |
|---|
| 126 |
yield trailer |
|---|
| 127 |
|
|---|
| 128 |
root = xml_data.getroot() |
|---|
| 129 |
return task.coiterate(iterate(root)) |
|---|
| 130 |
|
|---|
| 131 |
def _parse_into_trailer(self, item): |
|---|
| 132 |
""" |
|---|
| 133 |
info = item.find('info') |
|---|
| 134 |
|
|---|
| 135 |
for attr in ('title', 'runtime', 'rating', 'studio', 'postdate', |
|---|
| 136 |
'releasedate', 'copyright', 'director', 'description'): |
|---|
| 137 |
setattr(trailer, attr, info.find(attr).text) |
|---|
| 138 |
""" |
|---|
| 139 |
|
|---|
| 140 |
data = {} |
|---|
| 141 |
data['id'] = item.get('id') |
|---|
| 142 |
data['name'] = item.find('./info/title').text |
|---|
| 143 |
data['cover'] = item.find('./poster/location').text |
|---|
| 144 |
data['url'] = item.find('./preview/large').text.replace('movies.','',1) |
|---|
| 145 |
|
|---|
| 146 |
trailer = Trailer(ROOT_ID, self.urlbase, **data) |
|---|
| 147 |
duration = None |
|---|
| 148 |
try: |
|---|
| 149 |
hours = 0 |
|---|
| 150 |
minutes = 0 |
|---|
| 151 |
seconds = 0 |
|---|
| 152 |
duration = item.find('./info/runtime').text |
|---|
| 153 |
try: |
|---|
| 154 |
hours,minutes,seconds = duration.split(':') |
|---|
| 155 |
except ValueError: |
|---|
| 156 |
try: |
|---|
| 157 |
minutes,seconds = duration.split(':') |
|---|
| 158 |
except ValueError: |
|---|
| 159 |
seconds = duration |
|---|
| 160 |
duration = "%d:%02d:%02d" % (int(hours), int(minutes), int(seconds)) |
|---|
| 161 |
except: |
|---|
| 162 |
pass |
|---|
| 163 |
|
|---|
| 164 |
try: |
|---|
| 165 |
trailer.item.director = item.find('./info/director').text |
|---|
| 166 |
except: |
|---|
| 167 |
pass |
|---|
| 168 |
|
|---|
| 169 |
try: |
|---|
| 170 |
trailer.item.description = item.find('./info/description').text |
|---|
| 171 |
except: |
|---|
| 172 |
pass |
|---|
| 173 |
|
|---|
| 174 |
res = DIDLLite.Resource(trailer.get_path(), 'http-get:*:video/mp4:*') |
|---|
| 175 |
res.duration = duration |
|---|
| 176 |
try: |
|---|
| 177 |
res.size = item.find('./preview/large').get('filesize',None) |
|---|
| 178 |
except: |
|---|
| 179 |
pass |
|---|
| 180 |
trailer.item.res.append(res) |
|---|
| 181 |
|
|---|
| 182 |
if self.server.coherence.config.get('transcoding', 'no') == 'yes': |
|---|
| 183 |
# dlna_pn = 'DLNA.ORG_PN=AVC_TS_BL_CIF15_AAC' |
|---|
| 184 |
# dlna_tags = DIDLLite.simple_dlna_tags[:] |
|---|
| 185 |
# dlna_tags[2] = 'DLNA.ORG_CI=1' |
|---|
| 186 |
# url = self.urlbase + str(trailer.id)+'?transcoded=mp4' |
|---|
| 187 |
# new_res = DIDLLite.Resource(url, |
|---|
| 188 |
# 'http-get:*:%s:%s' % ('video/mp4', ';'.join([dlna_pn]+dlna_tags))) |
|---|
| 189 |
# new_res.size = None |
|---|
| 190 |
# res.duration = duration |
|---|
| 191 |
# trailer.item.res.append(new_res) |
|---|
| 192 |
|
|---|
| 193 |
dlna_pn = 'DLNA.ORG_PN=JPEG_TN' |
|---|
| 194 |
dlna_tags = DIDLLite.simple_dlna_tags[:] |
|---|
| 195 |
dlna_tags[2] = 'DLNA.ORG_CI=1' |
|---|
| 196 |
dlna_tags[3] = 'DLNA.ORG_FLAGS=00f00000000000000000000000000000' |
|---|
| 197 |
url = self.urlbase + str(trailer.id)+'?attachment=poster&transcoded=thumb&type=jpeg' |
|---|
| 198 |
new_res = DIDLLite.Resource(url, |
|---|
| 199 |
'http-get:*:%s:%s' % ('image/jpeg', ';'.join([dlna_pn] + dlna_tags))) |
|---|
| 200 |
new_res.size = None |
|---|
| 201 |
#new_res.resolution = "160x160" |
|---|
| 202 |
trailer.item.res.append(new_res) |
|---|
| 203 |
if not hasattr(trailer.item, 'attachments'): |
|---|
| 204 |
trailer.item.attachments = {} |
|---|
| 205 |
trailer.item.attachments['poster'] = data['cover'] |
|---|
| 206 |
|
|---|
| 207 |
self.trailers[trailer.id] = trailer |
|---|
| 208 |
|
|---|
| 209 |
def get_by_id(self, id): |
|---|
| 210 |
try: |
|---|
| 211 |
if int(id) == 0: |
|---|
| 212 |
return self.container |
|---|
| 213 |
else: |
|---|
| 214 |
return self.trailers.get(id,None) |
|---|
| 215 |
except: |
|---|
| 216 |
return None |
|---|
| 217 |
|
|---|
| 218 |
def upnp_init(self): |
|---|
| 219 |
if self.server: |
|---|
| 220 |
self.server.connection_manager_server.set_variable( \ |
|---|
| 221 |
0, 'SourceProtocolInfo', ['http-get:*:video/quicktime:*','http-get:*:video/mp4:*','http-get:*:video/m4v:*']) |
|---|
| 222 |
self.container = Container(ROOT_ID, -1, self.name) |
|---|
| 223 |
trailers = self.trailers.values() |
|---|
| 224 |
trailers.sort(cmp=lambda x,y : cmp(x.get_name().lower(),y.get_name().lower())) |
|---|
| 225 |
self.container.children = trailers |
|---|
| 226 |
|
|---|
| 227 |
def __repr__(self): |
|---|
| 228 |
return self.__class__.__name__ |
|---|