Advertisement
dmfrey

dataheap.py

Jun 17th, 2014
236
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 50.97 KB | None | 0 0
  1. # -*- coding: utf-8 -*-
  2.  
  3. """
  4. Provides data access classes for accessing and managing MythTV data
  5. """
  6.  
  7. from MythTV.static import *
  8. from MythTV.exceptions import *
  9. from MythTV.altdict import DictData, DictInvertCI
  10. from MythTV.database import *
  11. from MythTV.system import Grabber, InternetMetadata, VideoMetadata
  12. from MythTV.mythproto import ftopen, FileOps, Program
  13. from MythTV.utility import CMPRecord, CMPVideo, MARKUPLIST, datetime, ParseSet
  14.  
  15. import re
  16. import locale
  17. import xml.etree.cElementTree as etree
  18. from datetime import date, time
  19.  
  20. _default_datetime = datetime(1900,1,1, tzinfo=datetime.UTCTZ())
  21.  
  22. from UserString import MutableString
  23. class Artwork( MutableString ):
  24.     _types = {'coverart':   'Coverart',
  25.               'coverfile':  'Coverart',
  26.               'fanart':     'Fanart',
  27.               'banner':     'Banners',
  28.               'screenshot': 'ScreenShots',
  29.               'trailer':    'Trailers'}
  30.  
  31.     @property
  32.     def data(self):
  33.         try:
  34.             val = self.parent[self.attr]
  35.         except:
  36.             raise RuntimeError("Artwork property must be used through an " +\
  37.                                "object, not independently.")
  38.         else:
  39.             if val is None:
  40.                 return ''
  41.             return val
  42.     @data.setter
  43.     def data(self, value):
  44.         try:
  45.             self.parent[self.attr] = value
  46.         except:
  47.             raise RuntimeError("Artwork property must be used through an " +\
  48.                                "object, not independently.")
  49.     @data.deleter
  50.     def data(self):
  51.         try:
  52.             self.parent[self.attr] = self.parent._defaults.get(self.attr, "")
  53.         except:
  54.             raise RuntimeError("Artwork property must be used through an " +\
  55.                                "object, not independently.")
  56.  
  57.     def __new__(cls, attr, parent=None, imagetype=None):
  58.         if (imagetype is None) and (attr not in cls._types):
  59.             # usage appears to be export from immutable UserString methods
  60.             # return a dumb string
  61.             return unicode.__new__(unicode, attr)
  62.         else:
  63.             return super(Artwork, cls).__new__(cls, attr, parent, imagetype)
  64.  
  65.     def __init__(self, attr, parent=None, imagetype=None):
  66.         # replace standard MutableString init to not overwrite self.data
  67.         from warnings import warnpy3k
  68.         warnpy3k('the class UserString.MutableString has been removed in '
  69.                     'Python 3.0', stacklevel=2)
  70.  
  71.         self.attr = attr
  72.         if imagetype is None:
  73.             imagetype = self._types[attr]
  74.         self.imagetype = imagetype
  75.         self.parent = parent
  76.  
  77.         if parent:
  78.             self.hostname = parent.get('hostname', parent.get('host', None))
  79.  
  80.     def __repr__(self):
  81.         return u"<{0.imagetype} '{0.data}'>".format(self)
  82.  
  83.     def __get__(self, inst, owner):
  84.         if inst is None:
  85.             return self
  86.         return Artwork(self.attr, inst, self.imagetype)
  87.  
  88.     def __set__(self, inst, value):
  89.         inst[self.attr] = value
  90.  
  91.     def __delete__(self, inst):
  92.         inst[self.attr] = inst._defaults.get(self.attr, "")
  93.  
  94.     @property
  95.     def exists(self):
  96.         be = FileOps(self.hostname, db = self.parent._db)
  97.         return be.fileExists(unicode(self), self.imagetype)
  98.  
  99.     def downloadFrom(self, url):
  100.         if self.parent is None:
  101.             raise RuntimeError("Artwork.downloadFrom must be called from "+\
  102.                                "object, not class.")
  103.         be = FileOps(self.hostname, db=self.parent._db)
  104.         be.downloadTo(url, self.imagetype, self)
  105.  
  106.     def open(self, mode='r'):
  107.         return ftopen((self.hostname, self.imagetype, str(self)), mode)
  108.  
  109. class Record( CMPRecord, DBDataWrite, RECTYPE ):
  110.     """
  111.    Record(id=None, db=None) -> Record object
  112.    """
  113.  
  114.     @classmethod
  115.     def _setClassDefs(cls, db=None):
  116.         db = DBCache(db)
  117.         super(Record, cls)._setClassDefs(db)
  118.         defaults = cls._template('Default', db=db)
  119.         for k,v in defaults.iteritems():
  120.             cls._defaults[k] = v
  121.  
  122.     _stored_templates = {}
  123.     @classmethod
  124.     def _template(cls, name, db=None):
  125.         if name not in cls._stored_templates:
  126.             db = DBCache(db)
  127.             cls._setClassDefs(db)
  128.             tmp = cls._fromQuery("WHERE title=?", (name + " (Template)",))\
  129.                                     .next().iteritems()
  130.             data = {}
  131.             for k,v in tmp:
  132.                 if k in ['type', 'category', 'profile', 'recpriority',
  133.                          'autoexpire', 'maxepisodes', 'startoffset',
  134.                          'endoffset', 'recgroup', 'dupmethod', 'dupin',
  135.                          'search', 'autotranscode', 'autocommflag',
  136.                          'autouserjob1', 'autouserjob2', 'autouserjob3',
  137.                          'autouserjob4', 'autometadata', 'findday',
  138.                          'findtime', 'inactive', 'transcoder', 'playgroup',
  139.                          'prefinput', 'storagegroup', 'avg_delay', 'filter']:
  140.                     data[k] = v
  141.             cls._stored_templates[name] = data
  142.         return cls._stored_templates[name]
  143.  
  144.     _defaults = {'title':u'Unknown', 'subtitle':u'', 'description':u'',
  145.                  'category':u'', 'station':u'', 'seriesid':u'', 'inetref':u'',
  146.                  'season':0, 'episode':0, 'last_record':_default_datetime,
  147.                  'next_record':_default_datetime,
  148.                  'last_delete':_default_datetime}
  149.     _artwork = None
  150.  
  151.     def __str__(self):
  152.         if self._wheredat is None:
  153.             return u"<Uninitialized Record Rule at %s>" % hex(id(self))
  154.         return u"<Record Rule '%s', Type %d at %s>" \
  155.                                     % (self.title, self.type, hex(id(self)))
  156.  
  157.     def __repr__(self):
  158.         return str(self).encode('utf-8')
  159.  
  160.     def __init__(self, data=None, db=None, template=None):
  161.         DBDataWrite.__init__(self, data, db)
  162.         if (data is None) and template:
  163.             dict.update(self, self._template(template, db=self._db))
  164.  
  165.     def create(self, data=None, wait=False):
  166.         """Record.create(data=None) -> Record object"""
  167.         DBDataWrite._create_autoincrement(self, data)
  168.         FileOps(db=self._db).reschedule(self.recordid, wait)
  169.         return self
  170.  
  171.     def delete(self, wait=False):
  172.         DBDataWrite.delete(self)
  173.         FileOps(db=self._db).reschedule(self.recordid, wait)
  174.  
  175.     def update(self, *args, **keywords):
  176.         wait = keywords.get('wait',False)
  177.         DBDataWrite.update(self, *args, **keywords)
  178.         FileOps(db=self._db).reschedule(self.recordid, wait)
  179.  
  180.     def getUpcoming(self, deactivated=False):
  181.         recstatus = None
  182.         if not deactivated:
  183.             recstatus=Program.rsWillRecord
  184.         return FileOps(db=self._db)._getSortedPrograms('QUERY_GETALLPENDING',
  185.                     header=1, recordid=self.recordid, recstatus=recstatus)
  186.  
  187.     @property
  188.     def artwork(self):
  189.         if self._artwork is None:
  190.             if (self.inetref is None) or (self.inetref == ""):
  191.                 raise MythError("Record cannot have artwork without inetref")
  192.  
  193.             try:
  194.                 self._artwork = \
  195.                     RecordedArtwork((self.inetref, self.season), self._db)
  196.             except MythError:
  197.                 #artwork does not exist, create new
  198.                 self._artwork = RecordedArtwork(db=self._db)
  199.                 self._artwork.inetref = self.inetref
  200.                 self._artwork.season = self.season
  201.                 self._artwork.host = self._db.getMasterBackend()
  202.                 self._artwork.create()
  203.         return self._artwork
  204.  
  205.     @classmethod
  206.     def fromGuide(cls, guide, type=RECTYPE.kAllRecord, wait=False):
  207.         if datetime.now() > guide.endtime:
  208.             raise MythError('Cannot create recording rule for past recording.')
  209.         rec = cls(None, db=guide._db)
  210.         for key in ('chanid','title','subtitle','description', 'category',
  211.                     'seriesid','programid'):
  212.             rec[key] = guide[key]
  213.  
  214.         rec.startdate = guide.starttime.date()
  215.         rec.starttime = guide.starttime-datetime.combine(rec.startdate, time())
  216.         rec.enddate = guide.endtime.date()
  217.         rec.endtime = guide.endtime-datetime.combine(rec.enddate, time())
  218.  
  219.         rec.station = Channel(guide.chanid, db=guide._db).callsign
  220.         rec.type = type
  221.         return rec.create(wait=wait)
  222.  
  223.     @classmethod
  224.     def fromProgram(cls, program, type=RECTYPE.kAllRecord, wait=False):
  225.         if datetime.now() > program.endtime:
  226.             raise MythError('Cannot create recording rule for past recording.')
  227.         rec = cls(None, db=program._db)
  228.         for key in ('chanid','title','subtitle','description','category',
  229.                     'seriesid','programid'):
  230.             rec[key] = program[key]
  231.         rec.station = program.callsign
  232.  
  233.         rec.startdate = program.starttime.date()
  234.         rec.starttime = program.starttime-datetime.combine(rec.startdate, time())
  235.         rec.enddate = program.endtime.date()
  236.         rec.endtime = program.endtime-datetime.combine(rec.enddate, time())
  237.  
  238.         if program.recordid:
  239.             rec.parentid = program.recordid
  240.             if program.recstatus == RECTYPE.kNotRecording:
  241.                 rec.type = RECTYPE.kOverrideRecord
  242.             else:
  243.                 rec.type = RECTYPE.kDontRecord
  244.         else:
  245.             rec.type = type
  246.         return rec.create(wait=wait)
  247.  
  248.     @classmethod
  249.     def fromPowerRule(cls, title='unnamed (Power Search)', where='', args=None,
  250.                            join='', db=None, type=RECTYPE.kAllRecord,
  251.                            searchtype=RECSEARCHTYPE.kPowerSearch, wait=False):
  252.  
  253.         if type not in (RECTYPE.kAllRecord,           RECTYPE.kDailyRecord,
  254.                         RECTYPE.kWeeklyRecord,        RECTYPE.kOneRecord):
  255.             raise MythDBError("Invalid 'type' set for power recording rule.")
  256.  
  257.         rec = cls(None, db=db)
  258.         if args is not None:
  259.             where = rec._db.literal(where, args)
  260.  
  261.         now = datetime.now()
  262.         rec.starttime = now.time()
  263.         rec.endtime = now.time()
  264.         rec.startdate = now.date()
  265.         rec.enddate = now.date()
  266.  
  267.         rec.title = title
  268.         rec.description = where
  269.         rec.subtitle = join
  270.         rec.type = type
  271.         rec.search = searchtype
  272.         return rec.create(wait=wait)
  273.  
  274. class Recorded( CMPRecord, DBDataWrite ):
  275.     """
  276.    Recorded(data=None, db=None) -> Recorded object
  277.            'data' is a tuple containing (chanid, storagegroup)
  278.    """
  279.     _defaults = {'title':u'Unknown', 'subtitle':'',          'description':'',
  280.                  'category':'',      'hostname':'',          'bookmark':0,
  281.                  'editing':0,        'cutlist':0,            'autoexpire':0,
  282.                  'commflagged':0,    'recgroup':'Default',   'seriesid':'',
  283.                  'programid':'',     'lastmodified':'CURRENT_TIMESTAMP',
  284.                  'filesize':0,       'stars':0,              'previouslyshown':0,
  285.                  'preserve':0,       'bookmarkupdate':0,
  286.                  'findid':0,         'deletepending':0,      'transcoder':0,
  287.                  'timestretch':1,    'recpriority':0,        'playgroup':'Default',
  288.                  'profile':'No',     'duplicate':1,          'transcoded':0,
  289.                  'watched':0,        'storagegroup':'Default',
  290.                  'inetref':'',       'season':0,            'episode':0}
  291.     _artwork = None
  292.  
  293.     class _Cast( DBDataCRef ):
  294.         _table = ['recordedcredits','people']
  295.         _ref = ['chanid','starttime']
  296.         _cref = ['person']
  297.  
  298.     class _Seek( DBDataRef, MARKUP ):
  299.         _table = 'recordedseek'
  300.         _ref = ['chanid','starttime']
  301.  
  302.     class _Markup( DBDataRef, MARKUP, MARKUPLIST ):
  303.         _table = 'recordedmarkup'
  304.         _ref = ['chanid','starttime']
  305.         def getskiplist(self):
  306.             return self._buildlist(self.MARK_COMM_START, self.MARK_COMM_END)
  307.         def getunskiplist(self):
  308.             return self._buildlist(self.MARK_COMM_END, self.MARK_COMM_START)
  309.         def getcutlist(self):
  310.             return self._buildlist(self.MARK_CUT_START, self.MARK_CUT_END)
  311.         def getuncutlist(self):
  312.             return self._buildlist(self.MARK_CUT_END, self.MARK_CUT_START)
  313.  
  314.     class _Rating( DBDataRef ):
  315.         _table = 'recordedrating'
  316.         _ref = ['chanid','starttime']
  317.        
  318.     def __str__(self):
  319.         if self._wheredat is None:
  320.             return u"<Uninitialized Recorded at %s>" % hex(id(self))
  321.         return u"<Recorded '%s','%s' at %s>" % (self.title,
  322.                 self.starttime.isoformat(' '), hex(id(self)))
  323.  
  324.     def __repr__(self):
  325.         return str(self).encode('utf-8')
  326.  
  327.     def __init__(self, data=None, db=None):
  328.         if data is not None:
  329.             if None not in data:
  330.                 data = [data[0], datetime.duck(data[1])]
  331.         DBDataWrite.__init__(self, data, db)
  332.  
  333.     def _postinit(self):
  334.         self.seek = self._Seek(self._wheredat, self._db)
  335.         self.markup = self._Markup(self._wheredat, self._db)
  336.         wheredat = (self.chanid, self.progstart)
  337.         self.cast = self._Cast(wheredat, self._db)
  338.         self.rating = self._Rating(wheredat, self._db)
  339.  
  340.     @classmethod
  341.     def fromProgram(cls, program):
  342.         return cls((program.chanid, program.recstartts), program._db)
  343.  
  344.     @classmethod
  345.     def fromJob(cls, job):
  346.         return cls((job.chanid, job.starttime), job._db)
  347.  
  348.     def _push(self):
  349.         DBDataWrite._push(self)
  350.         self.cast.commit()
  351.         self.seek.commit()
  352.         self.markup.commit()
  353.         self.rating.commit()
  354.  
  355.     def delete(self, force=False, rerecord=False):
  356.         """
  357.        Recorded.delete(force=False, rerecord=False) -> retcode
  358.                Informs backend to delete recording and all relevent data.
  359.                'force' forces a delete if the file cannot be found.
  360.                'rerecord' sets the file as recordable in oldrecorded
  361.        """
  362.         try:
  363.             return self.getProgram().delete(force, rerecord)
  364.         except AttributeError:
  365.             raise MythError("Program could not be found")
  366.  
  367.     def open(self, type='r'):
  368.         """Recorded.open(type='r') -> file or FileTransfer object"""
  369.         return ftopen((self.hostname, self.storagegroup, self.basename),
  370.                       type, db=self._db, chanid=self.chanid,
  371.                       starttime=self.starttime)
  372.  
  373.     def getProgram(self):
  374.         """Recorded.getProgram() -> Program object"""
  375.         return Program.fromRecorded(self)
  376.  
  377.     def getRecordedProgram(self):
  378.         """Recorded.getRecordedProgram() -> RecordedProgram object"""
  379.         return RecordedProgram.fromRecorded(self)
  380.  
  381.     @property
  382.     def artwork(self):
  383.         if self._artwork is None:
  384.             if (self.inetref is None) or (self.inetref == ""):
  385.                 raise MythError("Recorded cannot have artwork without inetref")
  386.  
  387.             try:
  388.                 self._artwork = \
  389.                     RecordedArtwork((self.inetref, self.season), self._db)
  390.             except MythError:
  391.                 #artwork does not exist, create new
  392.                 self._artwork = RecordedArtwork(db=self._db)
  393.                 self._artwork.inetref = self.inetref
  394.                 self._artwork.season = self.season
  395.                 self._artwork.host = self._db.getMasterBackend()
  396.                 self._artwork.create()
  397.         return self._artwork
  398.  
  399.  
  400.     def formatPath(self, path, replace=None):
  401.         """
  402.        Recorded.formatPath(path, replace=None) -> formatted path string
  403.                'path' string is formatted as per mythrename.pl
  404.        """
  405.         for (tag, data) in (('T','title'), ('S','subtitle'),
  406.                             ('R','description'), ('C','category'),
  407.                             ('U','recgroup'), ('hn','hostname'),
  408.                             ('c','chanid') ):
  409.             tmp = unicode(self[data]).replace('/','-')
  410.             path = path.replace('%'+tag, tmp)
  411.         for (data, pre) in (   ('starttime','%'), ('endtime','%e'),
  412.                                ('progstart','%p'),('progend','%pe') ):
  413.             for (tag, format) in (('y','%y'),('Y','%Y'),('n','%m'),('m','%m'),
  414.                                   ('j','%d'),('d','%d'),('g','%I'),('G','%H'),
  415.                                   ('h','%I'),('H','%H'),('i','%M'),('s','%S'),
  416.                                   ('a','%p'),('A','%p') ):
  417.                 path = path.replace(pre+tag, self[data].strftime(format))
  418.         if self.originalairdate is None:
  419.             airdate = date(1,1,1)
  420.         else:
  421.             airdate = self.originalairdate
  422.         for (tag, format) in (('y','%y'),('Y','%Y'),('n','%m'),('m','%m'),
  423.                               ('j','%d'),('d','%d')):
  424.             path = path.replace('%o'+tag, airdate.strftime(format))
  425.         path = path.replace('%-','-')
  426.         path = path.replace('%%','%')
  427.         path += '.'+self['basename'].split('.')[-1]
  428.  
  429.         # clean up for windows
  430.         if replace is not None:
  431.             for char in ('\\',':','*','?','"','<','>','|'):
  432.                 path = path.replace(char, replace)
  433.         return path
  434.  
  435.     def importMetadata(self, metadata, overwrite=False):
  436.         """Imports data from a VideoMetadata object."""
  437.         def _allow_change(self, tag, overwrite):
  438.             if overwrite: return True
  439.             if self[tag] is None: return True
  440.             if self[tag] == '': return True
  441.             if tag in self._defaults:
  442.                 if self[tag] == self._defaults[tag]:
  443.                     return True
  444.             return False
  445.  
  446.         # only work on existing entries
  447.         if self._wheredat is None:
  448.             return
  449.  
  450.         # pull direct matches
  451.         for tag in ('title', 'subtitle', 'description', 'season', 'episode',
  452.                     'chanid', 'seriesid', 'programid', 'inetref',
  453.                     'recgroup', 'playgroup', 'seriesid', 'programid',
  454.                     'storagegroup'):
  455.             if metadata[tag] and _allow_change(self, tag, overwrite):
  456.                 self[tag] = metadata[tag]
  457.  
  458.         # pull renamed matches
  459.         for tagf,tagt in (('userrating','stars'), ('filename', 'basename'),
  460.                           ('startts','progstart'),('endts','progend'),
  461.                           ('recstartts','starttime'),('recendts','endtime')):
  462.             if metadata[tagf] and _allow_change(self, tagt, overwrite):
  463.                 self[tagt] = metadata[tagf]
  464.  
  465.         # pull cast
  466.         trans = {'Author':'writer'}
  467.         for cast in metadata.people:
  468.             self.cast.append(unicode(cast.name),
  469.                              unicode(trans.get(cast.job,
  470.                                         cast.job.lower().replace(' ','_'))))
  471.  
  472.         # pull images
  473.         for image in metadata.images:
  474.             if not hasattr(self.artwork, image.type):
  475.                 pass
  476.             if getattr(self.artwork, image.type, ''):
  477.                 continue
  478.             setattr(self.artwork, image.type, image.filename)
  479.             getattr(self.artwork, image.type).downloadFrom(image.url)
  480.  
  481.         self.update()
  482.  
  483.     def exportMetadata(self):
  484.         """Exports data to a VideoMetadata object."""
  485.         # only work on existing entries
  486.         if self._wheredat is None:
  487.             return
  488.         metadata = VideoMetadata()
  489.  
  490.         # pull direct matches
  491.         for tag in ('title', 'subtitle', 'description', 'season', 'episode',
  492.                     'chanid', 'seriesid', 'programid', 'inetref',
  493.                     'recgroup', 'playgroup', 'seriesid', 'programid',
  494.                     'storagegroup'):
  495.             if self[tag]:
  496.                 metadata[tag] = self[tag]
  497.  
  498.         # pull translated matches
  499.         for tagt,tagf in (('userrating','stars'), ('filename', 'basename'),
  500.                           ('startts','progstart'),('endts','progend'),
  501.                           ('recstartts','starttime'),('recendts','endtime'),):
  502.             if self[tagf]:
  503.                 metadata[tagt] = self[tagf]
  504.  
  505.         # pull cast
  506.         for member in self.cast:
  507.             name = member.name
  508.             role = ' '.join([word.capitalize() for word in member.role.split('_')])
  509.             if role=='Writer': role = 'Author'
  510.             metadata.people.append(OrdDict((('name',name), ('job',role))))
  511.  
  512. #        for arttype in ['coverart', 'fanart', 'banner']:
  513. #            art = getattr(self.artwork, arttype)
  514. #            if art:
  515. #                metadata.images.append(OrdDict((('type',arttype), ('filename',art))))
  516.  
  517.         return metadata
  518.  
  519.     def __getstate__(self):
  520.         data = DBDataWrite.__getstate__(self)
  521.         data['cast'] = self.cast._picklelist()
  522.         data['seek'] = self.seek._picklelist()
  523.         data['markup'] = self.markup._picklelist()
  524.         data['rating'] = self.rating._picklelist()
  525.         return data
  526.  
  527.     def __setstate__(self, state):
  528.         DBDataWrite.__setstate__(self, state)
  529.         if self._wheredat is not None:
  530.             self.cast._populate(data=state['cast'])
  531.             self.seek._populate(data=state['seek'])
  532.             self.markup._populate(data=state['markup'])
  533.             self.rating._populate(data=state['rating'])
  534.  
  535.     def _playOnFe(self, fe):
  536.         return fe.send('play','program %d %s' % \
  537.                     (self.chanid, self.starttime.isoformat()))
  538.  
  539. class RecordedProgram( CMPRecord, DBDataWrite ):
  540.  
  541.     """
  542.    RecordedProgram(data=None, db=None) -> RecordedProgram object
  543.            'data' is a tuple containing (chanid, storagegroup)
  544.    """
  545.     _key   = ['chanid','starttime']
  546.     _defaults = {'title':'',     'subtitle':'',
  547.                  'category':'',  'category_type':'',     'airdate':0,
  548.                  'stars':0,      'previouslyshown':0,    'title_pronounce':'',
  549.                  'stereo':0,     'subtitled':0,          'hdtv':0,
  550.                  'partnumber':0, 'closecaptioned':0,     'parttotal':0,
  551.                  'seriesid':'',  'originalairdate':'',   'showtype':u'',
  552.                  'colorcode':'', 'syndicatedepisodenumber':'',
  553.                  'programid':'', 'manualid':0,           'generic':0,
  554.                  'first':0,      'listingsource':0,      'last':0,
  555.                  'audioprop':u'','videoprop':u'',        
  556.                  'subtitletypes':u''}
  557.  
  558.     def __str__(self):
  559.         if self._wheredat is None:
  560.             return u"<Uninitialized RecordedProgram at %s>" % hex(id(self))
  561.         return u"<RecordedProgram '%s','%s' at %s>" % (self.title,
  562.                 self.starttime.isoformat(' '), hex(id(self)))
  563.  
  564.     def __repr__(self):
  565.         return str(self).encode('utf-8')
  566.  
  567.     def __init__(self, data=None, db=None):
  568.         if data is not None:
  569.             if None not in data:
  570.                 data = [data[0], datetime.duck(data[1])]
  571.         DBDataWrite.__init__(self, data, db)
  572.  
  573.     def _postinit(self):
  574.         self.AudioProp = ParseSet(self, 'audioprop')
  575.         self.VideoProp = ParseSet(self, 'videoprop')
  576.         self.SubtitleTypes = ParseSet(self, 'subtitletypes')
  577.  
  578.     @classmethod
  579.     def fromRecorded(cls, recorded):
  580.         return cls((recorded.chanid, recorded.progstart), recorded._db)
  581.  
  582. class OldRecorded( CMPRecord, DBDataWrite, RECSTATUS ):
  583.     """
  584.    OldRecorded(data=None, db=None) -> OldRecorded object
  585.            'data' is a tuple containing (chanid, starttime)
  586.    """
  587.  
  588.     _key   = ['chanid','starttime']
  589.     _defaults = {'title':'',     'subtitle':'',      
  590.                  'category':'',  'seriesid':'',      'programid':'',
  591.                  'findid':0,     'recordid':0,       'station':'',
  592.                  'rectype':0,    'duplicate':0,      'recstatus':-3,
  593.                  'reactivate':0, 'generic':0,        'future':None,
  594.                  'inetref':'',   'season':0,         'episode':0}
  595.  
  596.     def __str__(self):
  597.         if self._wheredat is None:
  598.             return u"<Uninitialized OldRecorded at %s>" % hex(id(self))
  599.         return u"<OldRecorded '%s','%s' at %s>" % (self.title,
  600.                 self.starttime.isoformat(' '), hex(id(self)))
  601.  
  602.     def __repr__(self):
  603.         return str(self).encode('utf-8')
  604.  
  605.     def __init__(self, data=None, db=None):
  606.         if data is not None:
  607.             if None not in data:
  608.                 data = [data[0], datetime.duck(data[1])]
  609.         DBDataWrite.__init__(self, data, db)
  610.         if self.future:
  611.             raise MythDBError(MythError.DB_RESTRICT, "'future' OldRecorded " +\
  612.                         "instances are not usable from the bindings.")
  613.  
  614.     def setDuplicate(self, record=False):
  615.         """
  616.        OldRecorded.setDuplicate(record=False) -> None
  617.                Toggles re-recordability
  618.        """
  619.         with self._db.cursor(self._log) as cursor:
  620.             cursor.execute("""UPDATE oldrecorded SET duplicate=%%s
  621.                              WHERE %s""" % self._where, \
  622.                         tuple([record]+list(self._wheredat)))
  623.         FileOps(db=self._db).reschedule(0)
  624.  
  625.     def update(self, *args, **keywords):
  626.         """OldRecorded entries cannot be altered"""
  627.         return
  628.     def delete(self):
  629.         """OldRecorded entries cannot be deleted"""
  630.         return
  631.  
  632. class RecordedArtwork( DBDataWrite ):
  633.     """
  634.    RecordedArtwork(data=None, db=None)
  635.    """
  636.     _key = ('inetref', 'season')
  637.     _defaults = {'inetref':'',      'season':0,     'host':'',
  638.                  'coverart':'',     'fanart':'',    'banner':''}
  639.  
  640.     def __str__(self):
  641.         if self._wheredat is None:
  642.             return u"<Uninitialized Artwork at %s>" % hex(id(self))
  643.         return u"<RecordedArtwork '%s','%d' at %s>" % \
  644.                         (self.inetref, self.season, hex(id(self)))
  645.  
  646.     def __repr__(self):
  647.         return str(self).encode('utf-8')
  648.  
  649.     coverart = Artwork('coverart')
  650.     fanart   = Artwork('fanart')
  651.     banner   = Artwork('banner')
  652.  
  653. class Job( DBDataWrite, JOBTYPE, JOBCMD, JOBFLAG, JOBSTATUS ):
  654.     """
  655.    Job(id=None, db=None) -> Job object
  656.    """
  657.     _table = 'jobqueue'
  658.     _logmodule = 'Python Jobqueue'
  659.     _defaults = {'id':None,     'inserttime':datetime.now(),
  660.                  'hostname':'', 'status':JOBSTATUS.QUEUED,
  661.                  'comment':'',  'schedruntime':datetime.now()}
  662.  
  663.     def __str__(self):
  664.         if self._wheredat is None:
  665.             return u"<Uninitialized Job at %s>" % hex(id(self))
  666.         return u"<Job '%s' at %s>" % (self.id, hex(id(self)))
  667.  
  668.     def __repr__(self):
  669.         return str(self).encode('utf-8')
  670.  
  671.     def setComment(self,comment):
  672.         """Job.setComment(comment) -> None, updates comment"""
  673.         self.comment = comment
  674.         self.update()
  675.  
  676.     def setStatus(self,status):
  677.         """Job.setStatus(Status) -> None, updates status"""
  678.         self.status = status
  679.         self.update()
  680.  
  681.     @classmethod
  682.     def fromRecorded(cls, rec, type, status=None, schedruntime=None,
  683.                                hostname=None, args=None, flags=None):
  684.         job = cls(db=rec._db)
  685.         job.type = type
  686.         job.chanid = rec.chanid
  687.         job.starttime = rec.starttime
  688.         if status:
  689.             job.status = status
  690.         if schedruntime:
  691.             job.schedruntime = schedruntime
  692.         if hostname:
  693.             job.hostname = hostname
  694.         if args:
  695.             job.args = args
  696.         if flags:
  697.             job.flags = flags
  698.         job.create()
  699.  
  700.     @classmethod
  701.     def fromProgram(cls, prog, type, status=None, schedruntime=None,
  702.                                 hostname=None, args=None, flags=None):
  703.         if prog.rectype != prog.rsRecorded:
  704.             raise MythError('Invalid recording type for Job.')
  705.         job = cls(db=prog._db)
  706.         job.type = type
  707.         job.chanid = prog.chanid
  708.         job.starttime = prog.recstartts
  709.         if status:
  710.             job.status = status
  711.         if schedruntime:
  712.             job.schedruntime = schedruntime
  713.         if hostname:
  714.             job.hostname = hostname
  715.         if args:
  716.             job.args = args
  717.         if flags:
  718.             job.flags = flags
  719.         job.create()
  720.  
  721. class Channel( DBDataWrite ):
  722.     """Channel(chanid=None, db=None) -> Channel object"""
  723.     _defaults = {'icon':'none',          'videofilters':'',  'callsign':u'',
  724.                  'xmltvid':'',           'recpriority':0,    'contrast':32768,
  725.                  'brightness':32768,     'colour':32768,     'hue':32768,
  726.                  'tvformat':u'Default',  'visible':1,        'outputfilters':'',
  727.                  'useonairguide':0,      'atsc_major_chan':0,
  728.                  'tmoffset':0,           'default_authority':'',
  729.                  'commmethod':-1,        'atsc_minor_chan':0,
  730.                  'last_record':_default_datetime}
  731.  
  732.     def __str__(self):
  733.         if self._wheredat is None:
  734.             return u"<Uninitialized Channel at %s>" % hex(id(self))
  735.         return u"<Channel '%s','%s' at %s>" % \
  736.                         (self.chanid, self.name, hex(id(self)))
  737.  
  738.     def __repr__(self):
  739.         return str(self).encode('utf-8')
  740.  
  741. class Guide( CMPRecord, DBData ):
  742.     """
  743.    Guide(data=None, db=None) -> Guide object
  744.            Data is a tuple of (chanid, starttime).
  745.    """
  746.     _table = 'program'
  747.     _key   = ['chanid','starttime']
  748.    
  749.     def __str__(self):
  750.         if self._wheredat is None:
  751.             return u"<Uninitialized Guide at %s>" % hex(id(self))
  752.         return u"<Guide '%s','%s' at %s>" % (self.title,
  753.                 self.starttime.isoformat(' '), hex(id(self)))
  754.  
  755.     def __repr__(self):
  756.         return str(self).encode('utf-8')
  757.  
  758.     def getRecStatus(self):
  759.         be = FileOps(db=self._db)
  760.         for prog in be._getPrograms('QUERY_GETALLPENDING', header=1):
  761.             if (prog.chanid == self.chanid) and \
  762.                     (prog.starttime == self.starttime):
  763.                 return prog.recstatus
  764.         return 0
  765.  
  766.     def _postinit(self):
  767.         self.AudioProp = ParseSet(self, 'audioprop', False)
  768.         self.VideoProp = ParseSet(self, 'videoprop', False)
  769.         self.SubtitleTypes = ParseSet(self, 'subtitletypes', False)
  770.  
  771.     @classmethod
  772.     def fromEtree(cls, etree, db=None):
  773.         dat = {'chanid':etree[0]}
  774.         attrib = etree[1].attrib
  775.         for key in ('title','subTitle','category','seriesId',
  776.                     'hostname','programId','airdate'):
  777.             if key in attrib:
  778.                 dat[key.lower()] = attrib[key]
  779.         if 'stars' in attrib:
  780.             dat['stars'] = locale.atof(attrib['stars'])
  781.         if etree[1].text:
  782.             dat['description'] = etree[1].text.strip()
  783.         for key in ('startTime','endTime','lastModified'):
  784.             if key in attrib:
  785.                 dat[key.lower()] = datetime.fromIso(attrib[key])
  786.  
  787.         raw = []
  788.         for key in db.tablefields[cls._table]:
  789.             if key in dat:
  790.                 raw.append(dat[key])
  791.             else:
  792.                 raw.append(None)
  793.         return cls.fromRaw(raw, db)
  794.  
  795.     @classmethod
  796.     def fromJSON(cls, prog, db=None):
  797.         dat = {}
  798.         for key in ('ChanId','Title','SubTitle','Category'):
  799.             dat[key.lower()] = prog[key]
  800.         for key,key2 in (('CatType', 'category_type'),):
  801.             dat[key2] = prog[key]
  802.         for key in ('StartTime', 'EndTime'):
  803.             dat[key.lower()] = datetime.fromIso(prog[key])
  804.         dat['airdate'] = dat['starttime'].year
  805.  
  806.         raw = []
  807.         for key in db.tablefields[cls._table]:
  808.             if key in dat:
  809.                 raw.append(dat[key])
  810.             else:
  811.                 raw.append(None)
  812.         return cls.fromRaw(raw, db)
  813.  
  814. #### MYTHVIDEO ####
  815.  
  816. class Video( CMPVideo, VideoSchema, DBDataWrite ):
  817.     """Video(id=None, db=None, raw=None) -> Video object"""
  818.     _table = 'videometadata'
  819.     _defaults = {'subtitle':u'',             'director':u'Unknown',
  820.                  'rating':u'NR',             'inetref':u'00000000',
  821.                  'year':1895,                'userrating':0.0,
  822.                  'length':0,                 'showlevel':1,
  823.                  'coverfile':u'No Cover',    'host':u'',
  824.                  'homepage':u'',             'insertdate': datetime.now(),
  825.                  'watched':False,            'category':0,
  826.                  'browse':True,              'hash':u'',
  827.                  'season':0,                 'episode':0,
  828.                  'releasedate':date(1,1,1),  'childid':-1}
  829.     _cm_toid, _cm_toname = DictInvertCI.createPair({0:'none'})
  830.  
  831.     @classmethod
  832.     def _setClassDefs(cls, db=None):
  833.         db = DBCache(db)
  834.         super(Video, cls)._setClassDefs(db)
  835.         cls._fill_cm(db)
  836.  
  837.     @classmethod
  838.     def _fill_cm(cls, db=None):
  839.         db = DBCache(db)
  840.         with db.cursor() as cursor:
  841.             cursor.execute("""SELECT * FROM videocategory""")
  842.             for row in cursor:
  843.                 cls._cm_toname[row[0]] = row[1]
  844.  
  845.     def _cat_toname(self):
  846.         if self.category is not None:
  847.             try:
  848.                 self.category = self._cm_toname[int(self.category)]
  849.             except ValueError:
  850.                 # already a named category
  851.                 pass
  852.             except KeyError:
  853.                 self._fill_cm(self._db)
  854.                 if int(self.category) in self._cm_toname:
  855.                     self.category = self._cm_toname[int(self.category)]
  856.                 else:
  857.                     raise MythDBError('Video defined with unknown category id')
  858.         else:
  859.             self.category = 'none'
  860.  
  861.     def _cat_toid(self):
  862.         if self.category is not None:
  863.             try:
  864.                 if self.category.lower() not in self._cm_toid:
  865.                     self._fill_cm(self._db)
  866.                 if self.category.lower() not in self._cm_toid:
  867.                     with self._db.cursor(self._log) as cursor:
  868.                         cursor.execute("""INSERT INTO videocategory
  869.                                          SET category=%s""",
  870.                                       self.category)
  871.                         self._cm_toid[self.category] = cursor.lastrowid
  872.                 self.category = self._cm_toid[self.category]
  873.             except AttributeError:
  874.                 # already an integer category
  875.                 pass
  876.         else:
  877.             self.category = 0
  878.  
  879.     def _pull(self):
  880.         DBDataWrite._pull(self)
  881.         self._fill_cm()
  882.         self._cat_toname()
  883.  
  884.     def _push(self):
  885.         self._cat_toid()
  886.         DBDataWrite._push(self)
  887.         self._cat_toname()
  888.         self.cast.commit()
  889.         self.genre.commit()
  890.         self.country.commit()
  891.         self.markup.commit()
  892.  
  893.     def __repr__(self):
  894.         if self._wheredat is None:
  895.             return u"<Uninitialized Video at %s>" % hex(id(self))
  896.         res = self.title
  897.         if self.season and self.episode:
  898.             res += u' - %dx%02d' % (self.season, self.episode)
  899.         if self.subtitle:
  900.             res += u' - '+self.subtitle
  901.         return u"<Video '%s' at %s>" % (res, hex(id(self)))
  902.  
  903.     def _postinit(self):
  904.         self._fill_cm()
  905.         self._cat_toname()
  906.         self.cast = self._Cast(self._wheredat, self._db)
  907.         self.genre = self._Genre(self._wheredat, self._db)
  908.         self.country = self._Country(self._wheredat, self._db)
  909.         self.markup = self._Markup((self.filename,), self._db)
  910.  
  911.     def create(self, data=None):
  912.         """Video.create(data=None) -> Video object"""
  913.         if (self.host is not None) and (self.host != ''):
  914.             # check for pre-existing entry
  915.             if self.hash == '':
  916.                 self.hash = self.getHash()
  917.             with self._db as cursor:
  918.                 if cursor.execute("""SELECT intid FROM videometadata
  919.                                     WHERE hash=%s""", self.hash) > 0:
  920.                     id = cursor.fetchone()[0]
  921.                     self._evalwheredat([id])
  922.                     self._pull()
  923.                     self._postinit()
  924.                     return self
  925.  
  926.         # create new entry
  927.         self._import(data)
  928.         self._cat_toid()
  929.         return DBDataWrite._create_autoincrement(self)
  930.  
  931.     class _Cast( DBDataCRef ):
  932.         _table = ['videometadatacast','videocast']
  933.         _ref = ['idvideo']
  934.         _cref = ['idcast','intid']
  935.  
  936.     class _Genre( DBDataCRef ):
  937.         _table = ['videometadatagenre','videogenre']
  938.         _ref = ['idvideo']
  939.         _cref = ['idgenre','intid']
  940.  
  941.     class _Country( DBDataCRef ):
  942.         _table = ['videometadatacountry','videocountry']
  943.         _ref = ['idvideo']
  944.         _cref = ['idcountry','intid']
  945.  
  946.     class _Markup( DBDataRef, MARKUP ):
  947.         _table = 'filemarkup'
  948.         _ref = ['filename',]
  949.  
  950.     def delete(self):
  951.         """Video.delete() -> None"""
  952.         if (self._where is None) or \
  953.                 (self._wheredat is None):
  954.             return
  955.         self.cast.clean()
  956.         self.genre.clean()
  957.         self.country.clean()
  958.         DBDataWrite.delete(self)
  959.  
  960.     banner               = Artwork('banner')
  961.     coverfile = coverart = Artwork('coverfile')
  962.     fanart               = Artwork('fanart')
  963.     screenshot           = Artwork('screenshot')
  964.     trailer              = Artwork('trailer')
  965.  
  966.     def open(self, mode='r', nooverwrite=False):
  967.         return ftopen((self.host, 'Videos', self.filename),
  968.                     mode, False, nooverwrite, self._db)
  969.  
  970.     def getHash(self):
  971.         """Video.getHash() -> file hash"""
  972.         if self.host is None:
  973.             return None
  974.         be = FileOps(db=self._db)
  975.         hash = be.getHash(self.filename, 'Videos', self.host)
  976.         return hash
  977.  
  978.     def parseFilename(self):
  979.         filename = self.filename
  980.         filename = filename[:filename.rindex('.')]
  981.         for old in ('%20','_','.'):
  982.             filename = filename.replace(old, ' ')
  983.  
  984.         sep = '(?:\s?(?:-|/)?\s?)?'
  985.         regex1 = re.compile(
  986.             sep.join(['^(.*[^s0-9])',
  987.                       '(?:s|(?:Season))?',
  988.                       '(\d{1,4})',
  989.                       '(?:[ex/]|Episode)',
  990.                       '(\d{1,3})',
  991.                       '(.*)$']), re.I)
  992.  
  993.         regex2 = re.compile('(%s(?:Season%s\d*%s)*%s)$' \
  994.                             % (sep, sep, sep, sep), re.I)
  995.  
  996.         match1 = regex1.search(filename)
  997.         if match1:
  998.             title = match1.group(1)
  999.             season = int(match1.group(2))
  1000.             episode = int(match1.group(3))
  1001.             subtitle = match1.group(4)
  1002.  
  1003.             match2 = regex2.search(title)
  1004.             if match2:
  1005.                 title = title[:match2.start()]
  1006.                 title = title.rsplit('/',1)[-1]
  1007.         else:
  1008.             season = None
  1009.             episode = None
  1010.             subtitle = None
  1011.             title = filename.rsplit('/',1)[-1]
  1012.             for left,right in (('(',')'), ('[',']'), ('{','}')):
  1013.                 while left in title:
  1014.                     lin = title.index(left)
  1015.                     rin = title.index(right,lin)
  1016.                     title = title[:lin]+title[rin+1:]
  1017.             title = title
  1018.  
  1019.         return (title, season, episode, subtitle)
  1020.  
  1021.     def importMetadata(self, metadata, overwrite=False):
  1022.         """Imports data from a VideoMetadata object."""
  1023.         def _allow_change(self, tag, overwrite):
  1024.             if overwrite: return True
  1025.             if self[tag] is None: return True
  1026.             if self[tag] == '': return True
  1027.             if tag in self._defaults:
  1028.                 if self[tag] == self._defaults[tag]:
  1029.                     return True
  1030.             return False
  1031.  
  1032.         # only operate on existing entries
  1033.         if self._wheredat is None:
  1034.             return
  1035.  
  1036.         # pull direct tags
  1037.         for tag in ('title', 'subtitle', 'tagline', 'season', 'episode',
  1038.                     'inetref', 'homepage', 'trailer', 'userrating', 'year',
  1039.                     'releasedate'):
  1040.             if metadata[tag] and _allow_change(self, tag, overwrite):
  1041.                 self[tag] = metadata[tag]
  1042.  
  1043.         # pull tags needing renaming
  1044.         for tagf,tagt in (('description','plot'), ('runtime','length')):
  1045.             if metadata[tagf] and _allow_change(self, tagt, overwrite):
  1046.                 self[tagt] = metadata[tagf]
  1047.  
  1048.         # pull director
  1049.         try:
  1050.             if _allow_change(self, 'director', overwrite):
  1051.                 self.director = [person.name for person in metadata.people \
  1052.                                             if person.job=='Director'].pop(0)
  1053.         except IndexError: pass
  1054.  
  1055.         # pull actors
  1056.         for actor in [person for person in metadata.people \
  1057.                                   if person.job=='Actor']:
  1058.             self.cast.add(unicode(actor.name))
  1059.  
  1060.         # pull genres
  1061.         for category in metadata.categories:
  1062.             self.genre.add(unicode(category))
  1063.  
  1064.         # pull images (SG content only)
  1065.         if bool(self.host):
  1066.             for image in metadata.images:
  1067.                 if not hasattr(self, image.type):
  1068.                     continue
  1069.                 current = getattr(self, image.type)
  1070.                 if current and (current != 'No Cover') and not overwrite:
  1071.                     continue
  1072.                 setattr(self, image.type, image.filename)
  1073.                 getattr(self, image.type).downloadFrom(image.url)
  1074.  
  1075.         self.processed = True
  1076.         self.update()
  1077.  
  1078.     def exportMetadata(self):
  1079.         """Exports data to a VideoMetadata object."""
  1080.         # only work on entries from the database
  1081.         if self._wheredat is None:
  1082.             return
  1083.         metadata = VideoMetadata()
  1084.  
  1085.         # pull direct tags
  1086.         for tag in ('title', 'subtitle', 'tagline', 'season', 'episode',
  1087.                     'inetref', 'homepage', 'trailer', 'userrating', 'year',
  1088.                     'releasedate'):
  1089.             if self[tag]:
  1090.                 metadata[tag] = self[tag]
  1091.  
  1092.         # pull translated tags
  1093.         for tagf, tagt in (('plot', 'description'), ('length', 'runtime')):
  1094.             if self[tagf]:
  1095.                 metadata[tagt] = self[tagf]
  1096.  
  1097.         # pull director
  1098.         if self.director:
  1099.             metadata.people.append(OrdDict((('name',self.director), ('job','Director'))))
  1100.  
  1101.         # pull actors
  1102.         for actor in self.cast:
  1103.             metadata.people.append(OrdDict((('name',actor.cast), ('job','Actor'))))
  1104.  
  1105.         # pull genres
  1106.         for genre in self.genre:
  1107.             metadata.categories.append(genre.genre)
  1108.  
  1109.         # pull countries
  1110.         for country in self.country:
  1111.             metadata.countries.append(country.country)
  1112.  
  1113.         # pull images
  1114. #        for arttype in ['coverart', 'fanart', 'banner', 'screenshot']:
  1115. #            art = getattr(self, arttype)
  1116. #            if art:
  1117. #                metadata.images.append(OrdDict((('type',arttype), ('filename',art))))
  1118.  
  1119.         return metadata
  1120.  
  1121.     def __getstate__(self):
  1122.         data = DBDataWrite.__getstate__(self)
  1123.         data['cast'] = self.cast._picklelist()
  1124.         data['genre'] = self.genre._picklelist()
  1125.         data['markup'] = self.markup._picklelist()
  1126.         data['country'] = self.country._picklelist()
  1127.         return data
  1128.  
  1129.     def __setstate__(self, state):
  1130.         DBDataWrite.__setstate__(self, state)
  1131.         if self._wheredat is not None:
  1132.             self.cast._populate(data=state['cast'])
  1133.             self.genre._populate(data=state['genre'])
  1134.             self.markup._populate(data=state['markup'])
  1135.             self.country._populate(data=state['country'])
  1136.  
  1137.     @classmethod
  1138.     def fromFilename(cls, filename, db=None):
  1139.         vid = cls(db=db)
  1140.         vid.filename = filename
  1141.         vid.title, vid.season, vid.episode, vid.subtitle = \
  1142.                         vid.parseFilename()
  1143.         return vid
  1144.  
  1145.     def _playOnFe(self, fe):
  1146.         return fe.send('play','file myth://Videos@%s/%s' %
  1147.                     (self.host, self.filename))
  1148.  
  1149.     #### LEGACY ####
  1150.     # of course this will likely all get scrapped for 0.26...
  1151.     def openBanner(self, mode='r', nooverwrite=False):
  1152.         return self.banner.open(mode)
  1153.     def openCoverart(self, mode='r', nooverwrite=False):
  1154.         return self.coverfile.open(mode)
  1155.     def openFanart(self, mode='r', nooverwrite=False):
  1156.         return self.fanart.open(mode)
  1157.     def openScreenshot(self, mode='r', nooverwrite=False):
  1158.         return self.screenshot.open(mode)
  1159.     def openTrailer(self, mode='r', nooverwrite=False):
  1160.         return self.trailer.open(mode)
  1161.  
  1162.  
  1163. class VideoGrabber( Grabber ):
  1164.     """
  1165.    VideoGrabber(mode, lang='en', db=None) -> VideoGrabber object
  1166.            'mode' can be of either 'TV' or 'Movie'
  1167.    """
  1168.     logmodule = 'Python MythVideo Grabber'
  1169.     cls = VideoMetadata
  1170.  
  1171.     def __init__(self, mode, lang='en', db=None):
  1172.         dbvalue = {'tv':'TelevisionGrabber', 'movie':'MovieGrabber'}
  1173.         path = {'tv':'metadata/Television/ttvdb.py',
  1174.                 'movie':'metadata/Movie/tmdb3.py'}
  1175.         self.mode = mode.lower()
  1176.         try:
  1177.             Grabber.__init__(self, setting=dbvalue[self.mode], db=db,
  1178.                         path=path[self.mode],
  1179.                         prefix=os.path.join(INSTALL_PREFIX, 'share/mythtv'))
  1180.         except KeyError:
  1181.             raise MythError('Invalid MythVideo grabber')
  1182.         self.append('-l',lang)
  1183.  
  1184. #### MYTHNETVISION ####
  1185.  
  1186. class InternetContent( DBData ):
  1187.     _key = ['name']
  1188.  
  1189. class InternetContentArticles( DBData ):
  1190.     _key = ['feedtitle','title','subtitle']
  1191.  
  1192. class InternetSource( DictData ):
  1193.     logmodule = 'Python Internet Video Source'
  1194.     _field_order = ['name','author','thumbnail','command','type','description','version','search','tree']
  1195.     _field_type = 'Pass'
  1196.     xmlconn = None
  1197.  
  1198.     @classmethod
  1199.     def fromEtree(cls, etree, xmlconn):
  1200.         dat = {}
  1201.         for item in etree.getchildren():
  1202.             dat[item.tag] = item.text
  1203.  
  1204.         raw = []
  1205.         for field in cls._field_order:
  1206.             if field in dat:
  1207.                 raw.append(dat[field])
  1208.             else:
  1209.                 raw.append('')
  1210.         source = cls(raw)
  1211.         source.xmlconn = xmlconn
  1212.         return source
  1213.  
  1214.     def searchContent(self, query, page=1):
  1215.         if (self.xmlconn is None) or (self.search=='false'):
  1216.             return
  1217.         xmldat = self.xmlconn._queryTree('GetInternetSearch',
  1218.                     Grabber=self.command, Query=query, Page=page)
  1219.         xmldat = xmldat.find('channel')
  1220.         self.count = xmldat.find('numresults').text
  1221.         self.returned = xmldat.find('returned').text
  1222.         self.start = xmldat.find('startindex').text
  1223.         for item in xmldat.findall('item'):
  1224.             yield InternetMetadata(item)
  1225.  
  1226.  
  1227. #### MYTHMUSIC ####
  1228.  
  1229. class Song( MusicSchema, DBDataWrite ):
  1230.     _table = 'music_songs'
  1231.  
  1232.     @classmethod
  1233.     def fromAlbum(cls, album, db=None):
  1234.         """Returns iterable of songs from given album."""
  1235.         try:
  1236.             db = album._db
  1237.             album = album.album_id
  1238.         except AttributeError: pass
  1239.  
  1240.         return cls._fromQuery("WHERE album_id=%s", [album], db)
  1241.  
  1242.     @classmethod
  1243.     def fromArtist(cls, artist, db=None):
  1244.         """Returns iterable of songs from given artist."""
  1245.         try:
  1246.             db = artist._db
  1247.             artist = artist.artist_id
  1248.         except AttributeError: pass
  1249.  
  1250.         return cls._fromQuery("WHERE artist_id=%s", [artist], db)
  1251.  
  1252.     @classmethod
  1253.     def fromPlaylist(cls, playlist, db=None):
  1254.         """Returns iterable of songs from given playlist."""
  1255.         try:
  1256.             songs = playlist._songstring()
  1257.             db = playlist._db
  1258.         except AttributeError:
  1259.             db = DBCache(db)
  1260.             songs = MusicPlaylist(playlist, db)._songstring()
  1261.  
  1262.         return cls._fromQuery("WHERE LOCATE(song_id, %s)", songs, db)
  1263.  
  1264. class Album( MusicSchema, DBDataWrite ):
  1265.     _table = 'music_albums'
  1266.  
  1267.     @classmethod
  1268.     def fromArtist(cls, artist, db=None):
  1269.         """Returns iterable of albums from given artist."""
  1270.         try:
  1271.             db = artist._db
  1272.             artist = artist.artist_id
  1273.         except AttributeError:
  1274.             pass
  1275.         return cls._fromQuery("WHERE artist_id=%s", [artist], db)
  1276.  
  1277.     @classmethod
  1278.     def fromSong(cls, song, db=None):
  1279.         """Returns the album for the given song."""
  1280.         try:
  1281.             album = song.album_id
  1282.             db = song._db
  1283.         except AttributeError:
  1284.             db = DBCache(db)
  1285.             album = Song(song, db).album_id
  1286.         return cls(album, db)
  1287.  
  1288. class Artist( MusicSchema, DBDataWrite ):
  1289.     _table = 'music_artists'
  1290.  
  1291.     @classmethod
  1292.     def fromName(cls, name, db=None):
  1293.         db = MythDB(db)
  1294.         c = db.cursor()
  1295.         count = c.execute("""SELECT * FROM %s WHERE artist_name=%s""", (cls._table, name))
  1296.         if count > 1:
  1297.             raise MythDBError('Non-unique music_artist entry')
  1298.         elif count == 1:
  1299.             return cls.fromRaw(c.fetchone(), db)
  1300.         else:
  1301.             artist = cls(db=db)
  1302.             artist.artist_name = name
  1303.             return artist.create()
  1304.    
  1305.     @classmethod
  1306.     def fromSong(cls, song, db=None):
  1307.         """Returns the artist for the given song."""
  1308.         try:
  1309.             artist = song.artist_id
  1310.             db = song._db
  1311.         except AttributeError:
  1312.             db = DBCache(db)
  1313.             artist = Song(song, db).artist_id
  1314.         return cls(artist, db)
  1315.  
  1316.     @classmethod
  1317.     def fromAlbum(cls, album, db=None):
  1318.         """Returns the artist for the given album."""
  1319.         try:
  1320.             artist = album.artist_id
  1321.             db = album._db
  1322.         except AttributeError:
  1323.             db = DBCache(db)
  1324.             artist = Album(album, db).artist_id
  1325.         return cls(artist, db)
  1326.  
  1327. class MusicPlaylist( MusicSchema, DBDataWrite ):
  1328.     _table = 'music_playlists'
  1329.  
  1330.     def _pl_tolist(self):
  1331.         try:
  1332.             self.playlist_songs = \
  1333.                     [int(id) for id in self.playlist_songs.split(',')]
  1334.         except: pass
  1335.  
  1336.     def _pl_tostr(self):
  1337.         try:
  1338.             self.playlist_songs = \
  1339.                     ','.join(['%d' % id for id in self.playlist_songs])
  1340.         except: pass
  1341.  
  1342.     def _pull(self):
  1343.         DBDataWrite._pull(self)
  1344.         self._pl_tolist()
  1345.  
  1346.     def _push(self):
  1347.         self._pl_tostr()
  1348.         DBDataWrite._push(self)
  1349.         self._pl_tolist()
  1350.  
  1351.     def _evalwheredat(self, wheredat=None):
  1352.         DBDataWrite._evalwheredat(self, wheredat)
  1353.         self._pl_tolist()
  1354.  
  1355.     @classmethod
  1356.     def fromSong(cls, song, db=None):
  1357.         """Returns an iterable of playlists containing the given song."""
  1358.         try:
  1359.             db = song._db
  1360.             song = song.song_id
  1361.         except AttributeError:
  1362.             db = DBCache(db)
  1363.             song = Song(song, db).song_id
  1364.         return cls._fromQuery("WHERE LOCATE(%s, playlist_songs)", song, db)
  1365.  
  1366. class MusicDirectory( MusicSchema, DBDataWrite ):
  1367.     _table = 'music_directories'
  1368.  
  1369.     @classmethod
  1370.     def fromPath(cls, path, db=None):
  1371.         db = MythDB(db)
  1372.         c = db.cursor()
  1373.         count = c.execute("""SELECT * FROM %s WHERE path=%s""" \
  1374.                                     % cls._table, path)
  1375.         if count > 1:
  1376.             raise MythDBError('Multiple matches for MusicDirectory.fromPath')
  1377.         elif count == 1:
  1378.             return cls.fromRaw(c.fetchone())
  1379.         else:
  1380.             directory = cls()
  1381.             directory.path = path
  1382.             if path.find('/') != -1:
  1383.                 directory.parent_id = \
  1384.                         cls.fromPath(path.rsplit('/',1)[0]).directory_id
  1385.             return directory.create()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement