A Python UPnP Media Server

377 lines
11 KiB

  1. #!/usr/bin/env python
  2. from cdvdread import *
  3. import itertools
  4. import sys
  5. class DVDReadError(Exception):
  6. pass
  7. __all__ = [ 'DVD',
  8. ]
  9. dvd_domain_valid = [ DVD_READ_INFO_FILE, DVD_READ_INFO_BACKUP_FILE,
  10. DVD_READ_MENU_VOBS, DVD_READ_TITLE_VOBS, ]
  11. dvd_domain_valid_names = [ 'DVD_READ_INFO_FILE', 'DVD_READ_INFO_BACKUP_FILE',
  12. 'DVD_READ_MENU_VOBS', 'DVD_READ_TITLE_VOBS', ]
  13. try:
  14. dvd_domain_valid = set(dvd_domain_valid)
  15. except NameError:
  16. pass
  17. # Make sure the library matches what we are compiled for
  18. assert DVDREAD_VERSION == DVDVersion()
  19. DVDInit()
  20. def attribreprlist(obj, attrs):
  21. return map(lambda x, y = obj: '%s: %s' % (x, repr(getattr(y, x))), itertools.ifilter(lambda x, y = obj: hasattr(y, x), attrs))
  22. def bcdtoint(bcd):
  23. base = 1
  24. ret = 0
  25. while bcd:
  26. assert bcd % 16 < 10, 'invalid bcd digit in: %#x' % bcd
  27. ret += bcd % 16 * base
  28. base *= 10
  29. bcd /= 16
  30. return ret
  31. class DVDTime:
  32. '''Should be able to perform math, though until I get the frame rate bit info, I really can't do anything about it.'''
  33. __slots__ = [ '_hour', '_minute', '_second', '_frame_u', '_rate', '_seconds', '_frames' ]
  34. hour = property(lambda x: x._hour)
  35. minute = property(lambda x: x._minute)
  36. second = property(lambda x: x._second)
  37. frame = property(lambda x: x._frame)
  38. rate = property(lambda x: x._rate)
  39. ratestr = property(lambda x: x._ratestr)
  40. seconds = property(lambda x: x._seconds)
  41. frames = property(lambda x: x._frames)
  42. def __init__(self, dt):
  43. '''Take a dvd time object that has the attributes hour, minute, second and frame_u (bits 6-7 are frame rate, bits 0-5 is frame.'''
  44. self._hour = bcdtoint(dt.hour)
  45. self._minute = bcdtoint(dt.minute)
  46. self._second = bcdtoint(dt.second)
  47. self._frame = bcdtoint(dt.frame_u & 0x3f)
  48. fr = (dt.frame_u & 0xc0) >> 6
  49. assert fr in (1, 3), 'Unknown frame rate: %d' % fr
  50. self._rate = [-1, 25.0, -1, 29.97 ][fr]
  51. self._ratestr = [-1, '25.0', -1, '29.97' ][fr]
  52. self._seconds = (self._hour * 60 + self._minute) * 60 + self._second + self._frame / self._rate
  53. self._frames = self._seconds * self._rate
  54. def __int__(self):
  55. return int(self._seconds)
  56. def __float__(self):
  57. return self._seconds
  58. def __repr__(self):
  59. return '%s@%s' % (str(self), self.ratestr)
  60. def hhmmss(self):
  61. return '%02d:%02d:%02d' % (self._hour, self._minute, self._second)
  62. def __str__(self):
  63. return '%02d:%02d:%02d.%02d' % (self._hour, self._minute, self._second, self._frame)
  64. class audio_attr:
  65. pos = property(lambda x: x._pos)
  66. lang = property(lambda x: x._lang)
  67. audio_format = property(lambda x: x._audio_format)
  68. lang_type = property(lambda x: x._lang_type)
  69. application_mode = property(lambda x: x._application_mode)
  70. quantization = property(lambda x: x._quantization)
  71. sample_frequency = property(lambda x: x._sample_frequency)
  72. channels = property(lambda x: x._channels)
  73. def __init__(self, audioattr, pos):
  74. self._pos = pos
  75. lc = audioattr.lang_code
  76. self._lang = chr(lc>>8) + chr(lc & 255)
  77. self._audio_format = audioattr.audio_format
  78. self._lang_type = audioattr.lang_type
  79. self._application_mode = audioattr.application_mode
  80. self._quantization = audioattr.quantization
  81. self._sample_frequency = audioattr.sample_frequency
  82. self._channels = audioattr.channels
  83. def __repr__(self):
  84. v = [ 'pos', 'lang', 'sample_frequency', 'channels' ]
  85. return '<audio_attr: %s>' % ', '.join(attribreprlist(self, v))
  86. class IFO:
  87. ifodata = None # __del__
  88. # VMGI
  89. vmgi_mat = property(lambda x: x.ifodata.vmgi_mat)
  90. tt_srpt = property(lambda x: x.ifodata.tt_srpt)
  91. first_play_pgc = property(lambda x: x.ifodata.first_play_pgc)
  92. ptl_mait = property(lambda x: x.ifodata.ptl_mait)
  93. vts_atrt = property(lambda x: x.ifodata.vts_atrt)
  94. txtdt_mgi = property(lambda x: x.ifodata.txtdt_mgi)
  95. # Common
  96. pgci_ut = property(lambda x: x.ifodata.pgci_ut)
  97. menu_c_adt = property(lambda x: x.ifodata.menu_c_adt)
  98. menu_vobu_admap = property(lambda x: x.ifodata.menu_vobu_admap)
  99. # VTSI
  100. vtsi_mat = property(lambda x: x.ifodata.vtsi_mat)
  101. vts_ptt_srpt = property(lambda x: x.ifodata.vts_ptt_srpt)
  102. vts_pgcit = property(lambda x: x.ifodata.vts_pgcit)
  103. vts_tmapt = property(lambda x: x.ifodata.vts_tmapt)
  104. vts_c_adt = property(lambda x: x.ifodata.vts_c_adt)
  105. vts_vobu_admap = property(lambda x: x.ifodata.vts_vobu_admap)
  106. def __init__(self, dvd, i):
  107. self.ifodata = ifoOpen(dvd.dvdreader, i)
  108. self.dvd = dvd
  109. self.audio = {}
  110. if i:
  111. # we are a VTS, populate some data
  112. for i in xrange(self.vtsi_mat.nr_of_vts_audio_streams):
  113. j = audio_attr(audio_attr_getitem(
  114. self.vtsi_mat.vts_audio_attr, i), i)
  115. self.audio[j.lang] = j
  116. def __del__(self):
  117. if self.ifodata:
  118. ifoClose(self.ifodata)
  119. self.ifodata = None
  120. class DVDProgram:
  121. '''This represents a chapter in a title.'''
  122. time = property(lambda x: x._time)
  123. size = property(lambda x: x._size)
  124. def __init__(self, dvdtitle, cpb):
  125. self.dvdtitle = dvdtitle
  126. self.cpb = cpb
  127. self._time = DVDTime(self.cpb.playback_time)
  128. self._size = (self.cpb.last_sector - self.cpb.first_sector +
  129. 1) * self.dvdtitle.dvd.blocksize
  130. def blockiter(self, blkcnt = 16):
  131. # last isn't inclusive
  132. last = self.cpb.last_sector + 1
  133. for i in xrange(self.cpb.first_sector, last, blkcnt):
  134. yield self.dvdtitle.vob.pread(min(blkcnt, last - i), i)
  135. def __iter__(self):
  136. blklen = self.dvdtitle.dvd.blocksize
  137. for i in self.blockiter():
  138. for j in xrange(0, len(i), blklen):
  139. yield i[j:j + blklen]
  140. def __repr__(self):
  141. return '<DVDProgram: Time: %s>' % \
  142. DVDTime(self.cpb.playback_time)
  143. class DVDFile:
  144. dvdfile = None # __del__
  145. def __init__(self, dvd, vts, dom):
  146. assert dom in dvd_domain_valid, 'Must be one of: %s' % `dvd_domain_valid_names`
  147. self.dvdfile = DVDOpenFile(dvd.dvdreader, vts, dom)
  148. if self.dvdfile is None:
  149. raise ValueError, 'DVD file (%d, %d) does not exist' % (vts, dom)
  150. self.vts = vts
  151. self.dom = dom
  152. self.dvd = dvd
  153. def __del__(self):
  154. if self.dvdfile:
  155. DVDCloseFile(self.dvdfile)
  156. self.dvdfile = None
  157. def pread(self, nblocks, blkoff):
  158. assert self.dom in (DVD_READ_MENU_VOBS, DVD_READ_TITLE_VOBS), \
  159. 'Must be of type DVD_READ_MENU_VOBS or DVD_READ_TITLE_VOBS.'
  160. buf = malloc_void(nblocks * self.dvd.blocksize)
  161. assert buf, 'buf allocation failed'
  162. try:
  163. b = DVDReadBlocks(self.dvdfile, blkoff, nblocks,
  164. voidptr_to_ucharptr(buf))
  165. ret = cdata(buf, b * self.dvd.blocksize)
  166. return ret
  167. finally:
  168. free_void(buf)
  169. def seek(self, pos, whence = 0):
  170. assert whence == 0, 'Only SEEK_SET is supported'
  171. return DVDFileSeek(self.dvdfile, pos)
  172. def read(self, *args):
  173. if len(args) == 0:
  174. #read it all
  175. res = []
  176. data = 1
  177. while data:
  178. data = self.read(65536)
  179. res.append(data)
  180. return ''.join(res)
  181. assert len(args) == 1, 'Only takes one argument: count'
  182. buf = malloc_void(*args)
  183. assert buf, 'buf allocation failed'
  184. try:
  185. b = DVDReadBytes(self.dvdfile, buf, *args)
  186. ret = cdata(buf, b)
  187. return ret
  188. finally:
  189. free_void(buf)
  190. def __len__(self):
  191. return DVDFileSize(self.dvdfile) * self.dvd.blocksize
  192. class DVDTitle:
  193. '''This is a title.'''
  194. time = property(lambda x: x._time)
  195. audio = property(lambda x: x.vts.audio)
  196. def selectaudio(self, lang):
  197. if isinstance(lang, basestring):
  198. lang = [ lang ]
  199. for l in lang:
  200. try:
  201. return self.audio[l]
  202. except KeyError:
  203. pass
  204. for l in self.lang:
  205. if l.pos == 0:
  206. return l
  207. def __init__(self, dvd, vts, ttu):
  208. '''dvd is of class DVD, vts is the vts number one based, and ttu is the sub-vts one based.'''
  209. self.dvd = dvd
  210. self.vts = dvd.getifo(vts)
  211. assert ttu > 0 and ttu <= self.vts.vts_pgcit.nr_of_pgci_srp
  212. self.pgci = pgci_srp_getitem(self.vts.vts_pgcit.pgci_srp, ttu - 1).pgc
  213. self.ttu = ttu
  214. self.vob = DVDFile(dvd, vts, DVD_READ_TITLE_VOBS)
  215. self._time = DVDTime(self.pgci.playback_time)
  216. def __len__(self):
  217. return self.pgci.nr_of_programs
  218. def __getitem__(self, key):
  219. if key < 0 or key >= len(self):
  220. raise IndexError
  221. assert key < self.pgci.nr_of_cells, \
  222. 'key cannot be mapped from program to cell(%d)' % \
  223. (self.pgci.nr_of_programs, self.pgci.nr_of_cells)
  224. # cell is stored starting at 1, adjust to 0
  225. cell = uchar_getitem(self.pgci.program_map, key) - 1
  226. cell_playback = cell_playback_getitem(self.pgci.cell_playback,
  227. cell)
  228. return DVDProgram(self, cell_playback)
  229. def __repr__(self):
  230. return '<DVDTitle: Chapters: %d, Time: %s>' % (len(self), self.time)
  231. def data(self):
  232. '''Returns the data in blocks for the title.'''
  233. for i in self:
  234. for j in i:
  235. yield j
  236. class discid:
  237. __slots__ = [ '_discid' ]
  238. discid = property(lambda x: x._discid)
  239. def __init__(self, discid):
  240. self._discid = discid
  241. def __str__(self):
  242. return ''.join(map(lambda x: '%02x' % ord(x), self.discid))
  243. def __repr__(self):
  244. return '<DVD ID: %s>' % ''.join(map(lambda x: '%02x' % ord(x), self.discid))
  245. class DVD:
  246. '''Children must keep a reference to this object so that we don't close
  247. the dvd before all the files are closed. This does mean children will
  248. need to know the insides of this class, but that is fine since it's all
  249. internal to this implementation anyways.'''
  250. dvdreader = None # __del__
  251. blocksize = property(lambda x: x._blocksize)
  252. volid = property(lambda x: x._volid)
  253. volsetid = property(lambda x: x._volsetid)
  254. cache = property(lambda x: DVDUDFCacheLevel(x.dvdreader, -1), lambda x, y: DVDUDFCacheLevel(x.dvdreader, bool(y)))
  255. def __init__(self, path):
  256. self.dvdreader = DVDOpen(path)
  257. if self.dvdreader is None:
  258. raise ValueError, 'path is not a DVD'
  259. self._discidval = None
  260. # XXX - this may need to be dynamicly probed
  261. self._blocksize = 2048
  262. # pull in the volid and volsetid
  263. r, volid, volsetid = DVDUDFVolumeInfo(self.dvdreader, 32, 128)
  264. if r != 0:
  265. self._volid = volid[:volid.index('\x00')].decode('latin-1')
  266. self._volsetid = volsetid
  267. else:
  268. # Fall back to ISO, shouldn't happen as all
  269. # DVD's are UDF
  270. r, volid, voldsetid = DVDISOVolumeInfo(self.dvdreader, 33, 128)
  271. assert r == 0
  272. # Techinically more restrictive [A-Z0-9_]
  273. self._volid = volid[:volid.index('\x00')].decode('ascii')
  274. self._volsetid = volsetid
  275. self.vmg = self.getifo(0)
  276. self._len = self.vmg.tt_srpt.nr_of_srpts
  277. def __del__(self):
  278. if self.dvdreader:
  279. DVDClose(self.dvdreader)
  280. self.dvdreader = None
  281. def getifo(self, i):
  282. return IFO(self, i)
  283. def _discid(self):
  284. if self._discidval is not None:
  285. return self._discidval
  286. buf = malloc_void(16)
  287. assert buf, 'buf allocation failed'
  288. try:
  289. r = DVDDiscID(self.dvdreader, voidptr_to_ucharptr(buf))
  290. if r == -1:
  291. raise DVDReadError, "failed to compute disc id"
  292. self._discidval = discid(cdata(buf, 16))
  293. return self._discidval
  294. finally:
  295. free_void(buf)
  296. discid = property(_discid)
  297. def __len__(self):
  298. return self._len
  299. def __getitem__(self, key):
  300. if key < 0 or key >= len(self):
  301. raise IndexError
  302. title = title_info_getitem(self.vmg.tt_srpt.title, key)
  303. return DVDTitle(self, title.title_set_nr, title.vts_ttn)
  304. def __repr__(self):
  305. return '<DVD: %s, ID: %s, Titles: %s>' % (self._volid, self.discid, len(self))