Pastebin
API
tools
faq
paste
Login
Sign up
Please fix the following errors:
New Paste
Syntax Highlighting
# -*- coding: utf-8 -*- """ Provides data access classes for accessing and managing MythTV data """ from MythTV.static import * from MythTV.exceptions import * from MythTV.altdict import DictData, DictInvertCI from MythTV.database import * from MythTV.system import Grabber, InternetMetadata, VideoMetadata from MythTV.mythproto import ftopen, FileOps, Program from MythTV.utility import CMPRecord, CMPVideo, MARKUPLIST, datetime, ParseSet import re import locale import xml.etree.cElementTree as etree from datetime import date, time _default_datetime = datetime(1900,1,1, tzinfo=datetime.UTCTZ()) from UserString import MutableString class Artwork( MutableString ): _types = {'coverart': 'Coverart', 'coverfile': 'Coverart', 'fanart': 'Fanart', 'banner': 'Banners', 'screenshot': 'ScreenShots', 'trailer': 'Trailers'} @property def data(self): try: val = self.parent[self.attr] except: raise RuntimeError("Artwork property must be used through an " +\ "object, not independently.") else: if val is None: return '' return val @data.setter def data(self, value): try: self.parent[self.attr] = value except: raise RuntimeError("Artwork property must be used through an " +\ "object, not independently.") @data.deleter def data(self): try: self.parent[self.attr] = self.parent._defaults.get(self.attr, "") except: raise RuntimeError("Artwork property must be used through an " +\ "object, not independently.") def __new__(cls, attr, parent=None, imagetype=None): if (imagetype is None) and (attr not in cls._types): # usage appears to be export from immutable UserString methods # return a dumb string return unicode.__new__(unicode, attr) else: return super(Artwork, cls).__new__(cls, attr, parent, imagetype) def __init__(self, attr, parent=None, imagetype=None): # replace standard MutableString init to not overwrite self.data from warnings import warnpy3k warnpy3k('the class UserString.MutableString has been removed in ' 'Python 3.0', stacklevel=2) self.attr = attr if imagetype is None: imagetype = self._types[attr] self.imagetype = imagetype self.parent = parent if parent: self.hostname = parent.get('hostname', parent.get('host', None)) def __repr__(self): return u"<{0.imagetype} '{0.data}'>".format(self) def __get__(self, inst, owner): if inst is None: return self return Artwork(self.attr, inst, self.imagetype) def __set__(self, inst, value): inst[self.attr] = value def __delete__(self, inst): inst[self.attr] = inst._defaults.get(self.attr, "") @property def exists(self): be = FileOps(self.hostname, db = self.parent._db) return be.fileExists(unicode(self), self.imagetype) def downloadFrom(self, url): if self.parent is None: raise RuntimeError("Artwork.downloadFrom must be called from "+\ "object, not class.") be = FileOps(self.hostname, db=self.parent._db) be.downloadTo(url, self.imagetype, self) def open(self, mode='r'): return ftopen((self.hostname, self.imagetype, str(self)), mode) class Record( CMPRecord, DBDataWrite, RECTYPE ): """ Record(id=None, db=None) -> Record object """ @classmethod def _setClassDefs(cls, db=None): db = DBCache(db) super(Record, cls)._setClassDefs(db) defaults = cls._template('Default', db=db) for k,v in defaults.iteritems(): cls._defaults[k] = v _stored_templates = {} @classmethod def _template(cls, name, db=None): if name not in cls._stored_templates: db = DBCache(db) cls._setClassDefs(db) tmp = cls._fromQuery("WHERE title=?", (name + " (Template)",))\ .next().iteritems() data = {} for k,v in tmp: if k in ['type', 'category', 'profile', 'recpriority', 'autoexpire', 'maxepisodes', 'startoffset', 'endoffset', 'recgroup', 'dupmethod', 'dupin', 'search', 'autotranscode', 'autocommflag', 'autouserjob1', 'autouserjob2', 'autouserjob3', 'autouserjob4', 'autometadata', 'findday', 'findtime', 'inactive', 'transcoder', 'playgroup', 'prefinput', 'storagegroup', 'avg_delay', 'filter']: data[k] = v cls._stored_templates[name] = data return cls._stored_templates[name] _defaults = {'title':u'Unknown', 'subtitle':u'', 'description':u'', 'category':u'', 'station':u'', 'seriesid':u'', 'inetref':u'', 'season':0, 'episode':0, 'last_record':_default_datetime, 'next_record':_default_datetime, 'last_delete':_default_datetime} _artwork = None def __str__(self): if self._wheredat is None: return u"<Uninitialized Record Rule at %s>" % hex(id(self)) return u"<Record Rule '%s', Type %d at %s>" \ % (self.title, self.type, hex(id(self))) def __repr__(self): return str(self).encode('utf-8') def __init__(self, data=None, db=None, template=None): DBDataWrite.__init__(self, data, db) if (data is None) and template: dict.update(self, self._template(template, db=self._db)) def create(self, data=None, wait=False): """Record.create(data=None) -> Record object""" DBDataWrite._create_autoincrement(self, data) FileOps(db=self._db).reschedule(self.recordid, wait) return self def delete(self, wait=False): DBDataWrite.delete(self) FileOps(db=self._db).reschedule(self.recordid, wait) def update(self, *args, **keywords): wait = keywords.get('wait',False) DBDataWrite.update(self, *args, **keywords) FileOps(db=self._db).reschedule(self.recordid, wait) def getUpcoming(self, deactivated=False): recstatus = None if not deactivated: recstatus=Program.rsWillRecord return FileOps(db=self._db)._getSortedPrograms('QUERY_GETALLPENDING', header=1, recordid=self.recordid, recstatus=recstatus) @property def artwork(self): if self._artwork is None: if (self.inetref is None) or (self.inetref == ""): raise MythError("Record cannot have artwork without inetref") try: self._artwork = \ RecordedArtwork((self.inetref, self.season), self._db) except MythError: #artwork does not exist, create new self._artwork = RecordedArtwork(db=self._db) self._artwork.inetref = self.inetref self._artwork.season = self.season self._artwork.host = self._db.getMasterBackend() self._artwork.create() return self._artwork @classmethod def fromGuide(cls, guide, type=RECTYPE.kAllRecord, wait=False): if datetime.now() > guide.endtime: raise MythError('Cannot create recording rule for past recording.') rec = cls(None, db=guide._db) for key in ('chanid','title','subtitle','description', 'category', 'seriesid','programid'): rec[key] = guide[key] rec.startdate = guide.starttime.date() rec.starttime = guide.starttime-datetime.combine(rec.startdate, time()) rec.enddate = guide.endtime.date() rec.endtime = guide.endtime-datetime.combine(rec.enddate, time()) rec.station = Channel(guide.chanid, db=guide._db).callsign rec.type = type return rec.create(wait=wait) @classmethod def fromProgram(cls, program, type=RECTYPE.kAllRecord, wait=False): if datetime.now() > program.endtime: raise MythError('Cannot create recording rule for past recording.') rec = cls(None, db=program._db) for key in ('chanid','title','subtitle','description','category', 'seriesid','programid'): rec[key] = program[key] rec.station = program.callsign rec.startdate = program.starttime.date() rec.starttime = program.starttime-datetime.combine(rec.startdate, time()) rec.enddate = program.endtime.date() rec.endtime = program.endtime-datetime.combine(rec.enddate, time()) if program.recordid: rec.parentid = program.recordid if program.recstatus == RECTYPE.kNotRecording: rec.type = RECTYPE.kOverrideRecord else: rec.type = RECTYPE.kDontRecord else: rec.type = type return rec.create(wait=wait) @classmethod def fromPowerRule(cls, title='unnamed (Power Search)', where='', args=None, join='', db=None, type=RECTYPE.kAllRecord, searchtype=RECSEARCHTYPE.kPowerSearch, wait=False): if type not in (RECTYPE.kAllRecord, RECTYPE.kDailyRecord, RECTYPE.kWeeklyRecord, RECTYPE.kOneRecord): raise MythDBError("Invalid 'type' set for power recording rule.") rec = cls(None, db=db) if args is not None: where = rec._db.literal(where, args) now = datetime.now() rec.starttime = now.time() rec.endtime = now.time() rec.startdate = now.date() rec.enddate = now.date() rec.title = title rec.description = where rec.subtitle = join rec.type = type rec.search = searchtype return rec.create(wait=wait) class Recorded( CMPRecord, DBDataWrite ): """ Recorded(data=None, db=None) -> Recorded object 'data' is a tuple containing (chanid, storagegroup) """ _defaults = {'title':u'Unknown', 'subtitle':'', 'description':'', 'category':'', 'hostname':'', 'bookmark':0, 'editing':0, 'cutlist':0, 'autoexpire':0, 'commflagged':0, 'recgroup':'Default', 'seriesid':'', 'programid':'', 'lastmodified':'CURRENT_TIMESTAMP', 'filesize':0, 'stars':0, 'previouslyshown':0, 'preserve':0, 'bookmarkupdate':0, 'findid':0, 'deletepending':0, 'transcoder':0, 'timestretch':1, 'recpriority':0, 'playgroup':'Default', 'profile':'No', 'duplicate':1, 'transcoded':0, 'watched':0, 'storagegroup':'Default', 'inetref':'', 'season':0, 'episode':0} _artwork = None class _Cast( DBDataCRef ): _table = ['recordedcredits','people'] _ref = ['chanid','starttime'] _cref = ['person'] class _Seek( DBDataRef, MARKUP ): _table = 'recordedseek' _ref = ['chanid','starttime'] class _Markup( DBDataRef, MARKUP, MARKUPLIST ): _table = 'recordedmarkup' _ref = ['chanid','starttime'] def getskiplist(self): return self._buildlist(self.MARK_COMM_START, self.MARK_COMM_END) def getunskiplist(self): return self._buildlist(self.MARK_COMM_END, self.MARK_COMM_START) def getcutlist(self): return self._buildlist(self.MARK_CUT_START, self.MARK_CUT_END) def getuncutlist(self): return self._buildlist(self.MARK_CUT_END, self.MARK_CUT_START) class _Rating( DBDataRef ): _table = 'recordedrating' _ref = ['chanid','starttime'] def __str__(self): if self._wheredat is None: return u"<Uninitialized Recorded at %s>" % hex(id(self)) return u"<Recorded '%s','%s' at %s>" % (self.title, self.starttime.isoformat(' '), hex(id(self))) def __repr__(self): return str(self).encode('utf-8') def __init__(self, data=None, db=None): if data is not None: if None not in data: data = [data[0], datetime.duck(data[1])] DBDataWrite.__init__(self, data, db) def _postinit(self): self.seek = self._Seek(self._wheredat, self._db) self.markup = self._Markup(self._wheredat, self._db) wheredat = (self.chanid, self.progstart) self.cast = self._Cast(wheredat, self._db) self.rating = self._Rating(wheredat, self._db) @classmethod def fromProgram(cls, program): return cls((program.chanid, program.recstartts), program._db) @classmethod def fromJob(cls, job): return cls((job.chanid, job.starttime), job._db) def _push(self): DBDataWrite._push(self) self.cast.commit() self.seek.commit() self.markup.commit() self.rating.commit() def delete(self, force=False, rerecord=False): """ Recorded.delete(force=False, rerecord=False) -> retcode Informs backend to delete recording and all relevent data. 'force' forces a delete if the file cannot be found. 'rerecord' sets the file as recordable in oldrecorded """ try: return self.getProgram().delete(force, rerecord) except AttributeError: raise MythError("Program could not be found") def open(self, type='r'): """Recorded.open(type='r') -> file or FileTransfer object""" return ftopen((self.hostname, self.storagegroup, self.basename), type, db=self._db, chanid=self.chanid, starttime=self.starttime) def getProgram(self): """Recorded.getProgram() -> Program object""" return Program.fromRecorded(self) def getRecordedProgram(self): """Recorded.getRecordedProgram() -> RecordedProgram object""" return RecordedProgram.fromRecorded(self) @property def artwork(self): if self._artwork is None: if (self.inetref is None) or (self.inetref == ""): raise MythError("Recorded cannot have artwork without inetref") try: self._artwork = \ RecordedArtwork((self.inetref, self.season), self._db) except MythError: #artwork does not exist, create new self._artwork = RecordedArtwork(db=self._db) self._artwork.inetref = self.inetref self._artwork.season = self.season self._artwork.host = self._db.getMasterBackend() self._artwork.create() return self._artwork def formatPath(self, path, replace=None): """ Recorded.formatPath(path, replace=None) -> formatted path string 'path' string is formatted as per mythrename.pl """ for (tag, data) in (('T','title'), ('S','subtitle'), ('R','description'), ('C','category'), ('U','recgroup'), ('hn','hostname'), ('c','chanid') ): tmp = unicode(self[data]).replace('/','-') path = path.replace('%'+tag, tmp) for (data, pre) in ( ('starttime','%'), ('endtime','%e'), ('progstart','%p'),('progend','%pe') ): for (tag, format) in (('y','%y'),('Y','%Y'),('n','%m'),('m','%m'), ('j','%d'),('d','%d'),('g','%I'),('G','%H'), ('h','%I'),('H','%H'),('i','%M'),('s','%S'), ('a','%p'),('A','%p') ): path = path.replace(pre+tag, self[data].strftime(format)) if self.originalairdate is None: airdate = date(1,1,1) else: airdate = self.originalairdate for (tag, format) in (('y','%y'),('Y','%Y'),('n','%m'),('m','%m'), ('j','%d'),('d','%d')): path = path.replace('%o'+tag, airdate.strftime(format)) path = path.replace('%-','-') path = path.replace('%%','%') path += '.'+self['basename'].split('.')[-1] # clean up for windows if replace is not None: for char in ('\\',':','*','?','"','<','>','|'): path = path.replace(char, replace) return path def importMetadata(self, metadata, overwrite=False): """Imports data from a VideoMetadata object.""" def _allow_change(self, tag, overwrite): if overwrite: return True if self[tag] is None: return True if self[tag] == '': return True if tag in self._defaults: if self[tag] == self._defaults[tag]: return True return False # only work on existing entries if self._wheredat is None: return # pull direct matches for tag in ('title', 'subtitle', 'description', 'season', 'episode', 'chanid', 'seriesid', 'programid', 'inetref', 'recgroup', 'playgroup', 'seriesid', 'programid', 'storagegroup'): if metadata[tag] and _allow_change(self, tag, overwrite): self[tag] = metadata[tag] # pull renamed matches for tagf,tagt in (('userrating','stars'), ('filename', 'basename'), ('startts','progstart'),('endts','progend'), ('recstartts','starttime'),('recendts','endtime')): if metadata[tagf] and _allow_change(self, tagt, overwrite): self[tagt] = metadata[tagf] # pull cast trans = {'Author':'writer'} for cast in metadata.people: self.cast.append(unicode(cast.name), unicode(trans.get(cast.job, cast.job.lower().replace(' ','_')))) # pull images for image in metadata.images: if not hasattr(self.artwork, image.type): pass if getattr(self.artwork, image.type, ''): continue setattr(self.artwork, image.type, image.filename) getattr(self.artwork, image.type).downloadFrom(image.url) self.update() def exportMetadata(self): """Exports data to a VideoMetadata object.""" # only work on existing entries if self._wheredat is None: return metadata = VideoMetadata() # pull direct matches for tag in ('title', 'subtitle', 'description', 'season', 'episode', 'chanid', 'seriesid', 'programid', 'inetref', 'recgroup', 'playgroup', 'seriesid', 'programid', 'storagegroup'): if self[tag]: metadata[tag] = self[tag] # pull translated matches for tagt,tagf in (('userrating','stars'), ('filename', 'basename'), ('startts','progstart'),('endts','progend'), ('recstartts','starttime'),('recendts','endtime'),): if self[tagf]: metadata[tagt] = self[tagf] # pull cast for member in self.cast: name = member.name role = ' '.join([word.capitalize() for word in member.role.split('_')]) if role=='Writer': role = 'Author' metadata.people.append(OrdDict((('name',name), ('job',role)))) # for arttype in ['coverart', 'fanart', 'banner']: # art = getattr(self.artwork, arttype) # if art: # metadata.images.append(OrdDict((('type',arttype), ('filename',art)))) return metadata def __getstate__(self): data = DBDataWrite.__getstate__(self) data['cast'] = self.cast._picklelist() data['seek'] = self.seek._picklelist() data['markup'] = self.markup._picklelist() data['rating'] = self.rating._picklelist() return data def __setstate__(self, state): DBDataWrite.__setstate__(self, state) if self._wheredat is not None: self.cast._populate(data=state['cast']) self.seek._populate(data=state['seek']) self.markup._populate(data=state['markup']) self.rating._populate(data=state['rating']) def _playOnFe(self, fe): return fe.send('play','program %d %s' % \ (self.chanid, self.starttime.isoformat())) class RecordedProgram( CMPRecord, DBDataWrite ): """ RecordedProgram(data=None, db=None) -> RecordedProgram object 'data' is a tuple containing (chanid, storagegroup) """ _key = ['chanid','starttime'] _defaults = {'title':'', 'subtitle':'', 'category':'', 'category_type':'', 'airdate':0, 'stars':0, 'previouslyshown':0, 'title_pronounce':'', 'stereo':0, 'subtitled':0, 'hdtv':0, 'partnumber':0, 'closecaptioned':0, 'parttotal':0, 'seriesid':'', 'originalairdate':'', 'showtype':u'', 'colorcode':'', 'syndicatedepisodenumber':'', 'programid':'', 'manualid':0, 'generic':0, 'first':0, 'listingsource':0, 'last':0, 'audioprop':u'','videoprop':u'', 'subtitletypes':u''} def __str__(self): if self._wheredat is None: return u"<Uninitialized RecordedProgram at %s>" % hex(id(self)) return u"<RecordedProgram '%s','%s' at %s>" % (self.title, self.starttime.isoformat(' '), hex(id(self))) def __repr__(self): return str(self).encode('utf-8') def __init__(self, data=None, db=None): if data is not None: if None not in data: data = [data[0], datetime.duck(data[1])] DBDataWrite.__init__(self, data, db) def _postinit(self): self.AudioProp = ParseSet(self, 'audioprop') self.VideoProp = ParseSet(self, 'videoprop') self.SubtitleTypes = ParseSet(self, 'subtitletypes') @classmethod def fromRecorded(cls, recorded): return cls((recorded.chanid, recorded.progstart), recorded._db) class OldRecorded( CMPRecord, DBDataWrite, RECSTATUS ): """ OldRecorded(data=None, db=None) -> OldRecorded object 'data' is a tuple containing (chanid, starttime) """ _key = ['chanid','starttime'] _defaults = {'title':'', 'subtitle':'', 'category':'', 'seriesid':'', 'programid':'', 'findid':0, 'recordid':0, 'station':'', 'rectype':0, 'duplicate':0, 'recstatus':-3, 'reactivate':0, 'generic':0, 'future':None, 'inetref':'', 'season':0, 'episode':0} def __str__(self): if self._wheredat is None: return u"<Uninitialized OldRecorded at %s>" % hex(id(self)) return u"<OldRecorded '%s','%s' at %s>" % (self.title, self.starttime.isoformat(' '), hex(id(self))) def __repr__(self): return str(self).encode('utf-8') def __init__(self, data=None, db=None): if data is not None: if None not in data: data = [data[0], datetime.duck(data[1])] DBDataWrite.__init__(self, data, db) if self.future: raise MythDBError(MythError.DB_RESTRICT, "'future' OldRecorded " +\ "instances are not usable from the bindings.") def setDuplicate(self, record=False): """ OldRecorded.setDuplicate(record=False) -> None Toggles re-recordability """ with self._db.cursor(self._log) as cursor: cursor.execute("""UPDATE oldrecorded SET duplicate=%%s WHERE %s""" % self._where, \ tuple([record]+list(self._wheredat))) FileOps(db=self._db).reschedule(0) def update(self, *args, **keywords): """OldRecorded entries cannot be altered""" return def delete(self): """OldRecorded entries cannot be deleted""" return class RecordedArtwork( DBDataWrite ): """ RecordedArtwork(data=None, db=None) """ _key = ('inetref', 'season') _defaults = {'inetref':'', 'season':0, 'host':'', 'coverart':'', 'fanart':'', 'banner':''} def __str__(self): if self._wheredat is None: return u"<Uninitialized Artwork at %s>" % hex(id(self)) return u"<RecordedArtwork '%s','%d' at %s>" % \ (self.inetref, self.season, hex(id(self))) def __repr__(self): return str(self).encode('utf-8') coverart = Artwork('coverart') fanart = Artwork('fanart') banner = Artwork('banner') class Job( DBDataWrite, JOBTYPE, JOBCMD, JOBFLAG, JOBSTATUS ): """ Job(id=None, db=None) -> Job object """ _table = 'jobqueue' _logmodule = 'Python Jobqueue' _defaults = {'id':None, 'inserttime':datetime.now(), 'hostname':'', 'status':JOBSTATUS.QUEUED, 'comment':'', 'schedruntime':datetime.now()} def __str__(self): if self._wheredat is None: return u"<Uninitialized Job at %s>" % hex(id(self)) return u"<Job '%s' at %s>" % (self.id, hex(id(self))) def __repr__(self): return str(self).encode('utf-8') def setComment(self,comment): """Job.setComment(comment) -> None, updates comment""" self.comment = comment self.update() def setStatus(self,status): """Job.setStatus(Status) -> None, updates status""" self.status = status self.update() @classmethod def fromRecorded(cls, rec, type, status=None, schedruntime=None, hostname=None, args=None, flags=None): job = cls(db=rec._db) job.type = type job.chanid = rec.chanid job.starttime = rec.starttime if status: job.status = status if schedruntime: job.schedruntime = schedruntime if hostname: job.hostname = hostname if args: job.args = args if flags: job.flags = flags job.create() @classmethod def fromProgram(cls, prog, type, status=None, schedruntime=None, hostname=None, args=None, flags=None): if prog.rectype != prog.rsRecorded: raise MythError('Invalid recording type for Job.') job = cls(db=prog._db) job.type = type job.chanid = prog.chanid job.starttime = prog.recstartts if status: job.status = status if schedruntime: job.schedruntime = schedruntime if hostname: job.hostname = hostname if args: job.args = args if flags: job.flags = flags job.create() class Channel( DBDataWrite ): """Channel(chanid=None, db=None) -> Channel object""" _defaults = {'icon':'none', 'videofilters':'', 'callsign':u'', 'xmltvid':'', 'recpriority':0, 'contrast':32768, 'brightness':32768, 'colour':32768, 'hue':32768, 'tvformat':u'Default', 'visible':1, 'outputfilters':'', 'useonairguide':0, 'atsc_major_chan':0, 'tmoffset':0, 'default_authority':'', 'commmethod':-1, 'atsc_minor_chan':0, 'last_record':_default_datetime} def __str__(self): if self._wheredat is None: return u"<Uninitialized Channel at %s>" % hex(id(self)) return u"<Channel '%s','%s' at %s>" % \ (self.chanid, self.name, hex(id(self))) def __repr__(self): return str(self).encode('utf-8') class Guide( CMPRecord, DBData ): """ Guide(data=None, db=None) -> Guide object Data is a tuple of (chanid, starttime). """ _table = 'program' _key = ['chanid','starttime'] def __str__(self): if self._wheredat is None: return u"<Uninitialized Guide at %s>" % hex(id(self)) return u"<Guide '%s','%s' at %s>" % (self.title, self.starttime.isoformat(' '), hex(id(self))) def __repr__(self): return str(self).encode('utf-8') def getRecStatus(self): be = FileOps(db=self._db) for prog in be._getPrograms('QUERY_GETALLPENDING', header=1): if (prog.chanid == self.chanid) and \ (prog.starttime == self.starttime): return prog.recstatus return 0 def _postinit(self): self.AudioProp = ParseSet(self, 'audioprop', False) self.VideoProp = ParseSet(self, 'videoprop', False) self.SubtitleTypes = ParseSet(self, 'subtitletypes', False) @classmethod def fromEtree(cls, etree, db=None): dat = {'chanid':etree[0]} attrib = etree[1].attrib for key in ('title','subTitle','category','seriesId', 'hostname','programId','airdate'): if key in attrib: dat[key.lower()] = attrib[key] if 'stars' in attrib: dat['stars'] = locale.atof(attrib['stars']) if etree[1].text: dat['description'] = etree[1].text.strip() for key in ('startTime','endTime','lastModified'): if key in attrib: dat[key.lower()] = datetime.fromIso(attrib[key]) raw = [] for key in db.tablefields[cls._table]: if key in dat: raw.append(dat[key]) else: raw.append(None) return cls.fromRaw(raw, db) @classmethod def fromJSON(cls, prog, db=None): dat = {} for key in ('ChanId','Title','SubTitle','Category'): dat[key.lower()] = prog[key] for key,key2 in (('CatType', 'category_type'),): dat[key2] = prog[key] for key in ('StartTime', 'EndTime'): dat[key.lower()] = datetime.fromIso(prog[key]) dat['airdate'] = dat['starttime'].year raw = [] for key in db.tablefields[cls._table]: if key in dat: raw.append(dat[key]) else: raw.append(None) return cls.fromRaw(raw, db) #### MYTHVIDEO #### class Video( CMPVideo, VideoSchema, DBDataWrite ): """Video(id=None, db=None, raw=None) -> Video object""" _table = 'videometadata' _defaults = {'subtitle':u'', 'director':u'Unknown', 'rating':u'NR', 'inetref':u'00000000', 'year':1895, 'userrating':0.0, 'length':0, 'showlevel':1, 'coverfile':u'No Cover', 'host':u'', 'homepage':u'', 'insertdate': datetime.now(), 'watched':False, 'category':0, 'browse':True, 'hash':u'', 'season':0, 'episode':0, 'releasedate':date(1,1,1), 'childid':-1} _cm_toid, _cm_toname = DictInvertCI.createPair({0:'none'}) @classmethod def _setClassDefs(cls, db=None): db = DBCache(db) super(Video, cls)._setClassDefs(db) cls._fill_cm(db) @classmethod def _fill_cm(cls, db=None): db = DBCache(db) with db.cursor() as cursor: cursor.execute("""SELECT * FROM videocategory""") for row in cursor: cls._cm_toname[row[0]] = row[1] def _cat_toname(self): if self.category is not None: try: self.category = self._cm_toname[int(self.category)] except ValueError: # already a named category pass except KeyError: self._fill_cm(self._db) if int(self.category) in self._cm_toname: self.category = self._cm_toname[int(self.category)] else: raise MythDBError('Video defined with unknown category id') else: self.category = 'none' def _cat_toid(self): if self.category is not None: try: if self.category.lower() not in self._cm_toid: self._fill_cm(self._db) if self.category.lower() not in self._cm_toid: with self._db.cursor(self._log) as cursor: cursor.execute("""INSERT INTO videocategory SET category=%s""", self.category) self._cm_toid[self.category] = cursor.lastrowid self.category = self._cm_toid[self.category] except AttributeError: # already an integer category pass else: self.category = 0 def _pull(self): DBDataWrite._pull(self) self._fill_cm() self._cat_toname() def _push(self): self._cat_toid() DBDataWrite._push(self) self._cat_toname() self.cast.commit() self.genre.commit() self.country.commit() self.markup.commit() def __repr__(self): if self._wheredat is None: return u"<Uninitialized Video at %s>" % hex(id(self)) res = self.title if self.season and self.episode: res += u' - %dx%02d' % (self.season, self.episode) if self.subtitle: res += u' - '+self.subtitle return u"<Video '%s' at %s>" % (res, hex(id(self))) def _postinit(self): self._fill_cm() self._cat_toname() self.cast = self._Cast(self._wheredat, self._db) self.genre = self._Genre(self._wheredat, self._db) self.country = self._Country(self._wheredat, self._db) self.markup = self._Markup((self.filename,), self._db) def create(self, data=None): """Video.create(data=None) -> Video object""" if (self.host is not None) and (self.host != ''): # check for pre-existing entry if self.hash == '': self.hash = self.getHash() with self._db as cursor: if cursor.execute("""SELECT intid FROM videometadata WHERE hash=%s""", self.hash) > 0: id = cursor.fetchone()[0] self._evalwheredat([id]) self._pull() self._postinit() return self # create new entry self._import(data) self._cat_toid() return DBDataWrite._create_autoincrement(self) class _Cast( DBDataCRef ): _table = ['videometadatacast','videocast'] _ref = ['idvideo'] _cref = ['idcast','intid'] class _Genre( DBDataCRef ): _table = ['videometadatagenre','videogenre'] _ref = ['idvideo'] _cref = ['idgenre','intid'] class _Country( DBDataCRef ): _table = ['videometadatacountry','videocountry'] _ref = ['idvideo'] _cref = ['idcountry','intid'] class _Markup( DBDataRef, MARKUP ): _table = 'filemarkup' _ref = ['filename',] def delete(self): """Video.delete() -> None""" if (self._where is None) or \ (self._wheredat is None): return self.cast.clean() self.genre.clean() self.country.clean() DBDataWrite.delete(self) banner = Artwork('banner') coverfile = coverart = Artwork('coverfile') fanart = Artwork('fanart') screenshot = Artwork('screenshot') trailer = Artwork('trailer') def open(self, mode='r', nooverwrite=False): return ftopen((self.host, 'Videos', self.filename), mode, False, nooverwrite, self._db) def getHash(self): """Video.getHash() -> file hash""" if self.host is None: return None be = FileOps(db=self._db) hash = be.getHash(self.filename, 'Videos', self.host) return hash def parseFilename(self): filename = self.filename filename = filename[:filename.rindex('.')] for old in ('%20','_','.'): filename = filename.replace(old, ' ') sep = '(?:\s?(?:-|/)?\s?)?' regex1 = re.compile( sep.join(['^(.*[^s0-9])', '(?:s|(?:Season))?', '(\d{1,4})', '(?:[ex/]|Episode)', '(\d{1,3})', '(.*)$']), re.I) regex2 = re.compile('(%s(?:Season%s\d*%s)*%s)$' \ % (sep, sep, sep, sep), re.I) match1 = regex1.search(filename) if match1: title = match1.group(1) season = int(match1.group(2)) episode = int(match1.group(3)) subtitle = match1.group(4) match2 = regex2.search(title) if match2: title = title[:match2.start()] title = title.rsplit('/',1)[-1] else: season = None episode = None subtitle = None title = filename.rsplit('/',1)[-1] for left,right in (('(',')'), ('[',']'), ('{','}')): while left in title: lin = title.index(left) rin = title.index(right,lin) title = title[:lin]+title[rin+1:] title = title return (title, season, episode, subtitle) def importMetadata(self, metadata, overwrite=False): """Imports data from a VideoMetadata object.""" def _allow_change(self, tag, overwrite): if overwrite: return True if self[tag] is None: return True if self[tag] == '': return True if tag in self._defaults: if self[tag] == self._defaults[tag]: return True return False # only operate on existing entries if self._wheredat is None: return # pull direct tags for tag in ('title', 'subtitle', 'tagline', 'season', 'episode', 'inetref', 'homepage', 'trailer', 'userrating', 'year', 'releasedate'): if metadata[tag] and _allow_change(self, tag, overwrite): self[tag] = metadata[tag] # pull tags needing renaming for tagf,tagt in (('description','plot'), ('runtime','length')): if metadata[tagf] and _allow_change(self, tagt, overwrite): self[tagt] = metadata[tagf] # pull director try: if _allow_change(self, 'director', overwrite): self.director = [person.name for person in metadata.people \ if person.job=='Director'].pop(0) except IndexError: pass # pull actors for actor in [person for person in metadata.people \ if person.job=='Actor']: self.cast.add(unicode(actor.name)) # pull genres for category in metadata.categories: self.genre.add(unicode(category)) # pull images (SG content only) if bool(self.host): for image in metadata.images: if not hasattr(self, image.type): continue current = getattr(self, image.type) if current and (current != 'No Cover') and not overwrite: continue setattr(self, image.type, image.filename) getattr(self, image.type).downloadFrom(image.url) self.processed = True self.update() def exportMetadata(self): """Exports data to a VideoMetadata object.""" # only work on entries from the database if self._wheredat is None: return metadata = VideoMetadata() # pull direct tags for tag in ('title', 'subtitle', 'tagline', 'season', 'episode', 'inetref', 'homepage', 'trailer', 'userrating', 'year', 'releasedate'): if self[tag]: metadata[tag] = self[tag] # pull translated tags for tagf, tagt in (('plot', 'description'), ('length', 'runtime')): if self[tagf]: metadata[tagt] = self[tagf] # pull director if self.director: metadata.people.append(OrdDict((('name',self.director), ('job','Director')))) # pull actors for actor in self.cast: metadata.people.append(OrdDict((('name',actor.cast), ('job','Actor')))) # pull genres for genre in self.genre: metadata.categories.append(genre.genre) # pull countries for country in self.country: metadata.countries.append(country.country) # pull images # for arttype in ['coverart', 'fanart', 'banner', 'screenshot']: # art = getattr(self, arttype) # if art: # metadata.images.append(OrdDict((('type',arttype), ('filename',art)))) return metadata def __getstate__(self): data = DBDataWrite.__getstate__(self) data['cast'] = self.cast._picklelist() data['genre'] = self.genre._picklelist() data['markup'] = self.markup._picklelist() data['country'] = self.country._picklelist() return data def __setstate__(self, state): DBDataWrite.__setstate__(self, state) if self._wheredat is not None: self.cast._populate(data=state['cast']) self.genre._populate(data=state['genre']) self.markup._populate(data=state['markup']) self.country._populate(data=state['country']) @classmethod def fromFilename(cls, filename, db=None): vid = cls(db=db) vid.filename = filename vid.title, vid.season, vid.episode, vid.subtitle = \ vid.parseFilename() return vid def _playOnFe(self, fe): return fe.send('play','file myth://Videos@%s/%s' % (self.host, self.filename)) #### LEGACY #### # of course this will likely all get scrapped for 0.26... def openBanner(self, mode='r', nooverwrite=False): return self.banner.open(mode) def openCoverart(self, mode='r', nooverwrite=False): return self.coverfile.open(mode) def openFanart(self, mode='r', nooverwrite=False): return self.fanart.open(mode) def openScreenshot(self, mode='r', nooverwrite=False): return self.screenshot.open(mode) def openTrailer(self, mode='r', nooverwrite=False): return self.trailer.open(mode) class VideoGrabber( Grabber ): """ VideoGrabber(mode, lang='en', db=None) -> VideoGrabber object 'mode' can be of either 'TV' or 'Movie' """ logmodule = 'Python MythVideo Grabber' cls = VideoMetadata def __init__(self, mode, lang='en', db=None): dbvalue = {'tv':'TelevisionGrabber', 'movie':'MovieGrabber'} path = {'tv':'metadata/Television/ttvdb.py', 'movie':'metadata/Movie/tmdb3.py'} self.mode = mode.lower() try: Grabber.__init__(self, setting=dbvalue[self.mode], db=db, path=path[self.mode], prefix=os.path.join(INSTALL_PREFIX, 'share/mythtv')) except KeyError: raise MythError('Invalid MythVideo grabber') self.append('-l',lang) #### MYTHNETVISION #### class InternetContent( DBData ): _key = ['name'] class InternetContentArticles( DBData ): _key = ['feedtitle','title','subtitle'] class InternetSource( DictData ): logmodule = 'Python Internet Video Source' _field_order = ['name','author','thumbnail','command','type','description','version','search','tree'] _field_type = 'Pass' xmlconn = None @classmethod def fromEtree(cls, etree, xmlconn): dat = {} for item in etree.getchildren(): dat[item.tag] = item.text raw = [] for field in cls._field_order: if field in dat: raw.append(dat[field]) else: raw.append('') source = cls(raw) source.xmlconn = xmlconn return source def searchContent(self, query, page=1): if (self.xmlconn is None) or (self.search=='false'): return xmldat = self.xmlconn._queryTree('GetInternetSearch', Grabber=self.command, Query=query, Page=page) xmldat = xmldat.find('channel') self.count = xmldat.find('numresults').text self.returned = xmldat.find('returned').text self.start = xmldat.find('startindex').text for item in xmldat.findall('item'): yield InternetMetadata(item) #### MYTHMUSIC #### class Song( MusicSchema, DBDataWrite ): _table = 'music_songs' @classmethod def fromAlbum(cls, album, db=None): """Returns iterable of songs from given album.""" try: db = album._db album = album.album_id except AttributeError: pass return cls._fromQuery("WHERE album_id=%s", [album], db) @classmethod def fromArtist(cls, artist, db=None): """Returns iterable of songs from given artist.""" try: db = artist._db artist = artist.artist_id except AttributeError: pass return cls._fromQuery("WHERE artist_id=%s", [artist], db) @classmethod def fromPlaylist(cls, playlist, db=None): """Returns iterable of songs from given playlist.""" try: songs = playlist._songstring() db = playlist._db except AttributeError: db = DBCache(db) songs = MusicPlaylist(playlist, db)._songstring() return cls._fromQuery("WHERE LOCATE(song_id, %s)", songs, db) class Album( MusicSchema, DBDataWrite ): _table = 'music_albums' @classmethod def fromArtist(cls, artist, db=None): """Returns iterable of albums from given artist.""" try: db = artist._db artist = artist.artist_id except AttributeError: pass return cls._fromQuery("WHERE artist_id=%s", [artist], db) @classmethod def fromSong(cls, song, db=None): """Returns the album for the given song.""" try: album = song.album_id db = song._db except AttributeError: db = DBCache(db) album = Song(song, db).album_id return cls(album, db) class Artist( MusicSchema, DBDataWrite ): _table = 'music_artists' @classmethod def fromName(cls, name, db=None): db = MythDB(db) c = db.cursor() count = c.execute("""SELECT * FROM %s WHERE artist_name=%s""", (cls._table, name)) if count > 1: raise MythDBError('Non-unique music_artist entry') elif count == 1: return cls.fromRaw(c.fetchone(), db) else: artist = cls(db=db) artist.artist_name = name return artist.create() @classmethod def fromSong(cls, song, db=None): """Returns the artist for the given song.""" try: artist = song.artist_id db = song._db except AttributeError: db = DBCache(db) artist = Song(song, db).artist_id return cls(artist, db) @classmethod def fromAlbum(cls, album, db=None): """Returns the artist for the given album.""" try: artist = album.artist_id db = album._db except AttributeError: db = DBCache(db) artist = Album(album, db).artist_id return cls(artist, db) class MusicPlaylist( MusicSchema, DBDataWrite ): _table = 'music_playlists' def _pl_tolist(self): try: self.playlist_songs = \ [int(id) for id in self.playlist_songs.split(',')] except: pass def _pl_tostr(self): try: self.playlist_songs = \ ','.join(['%d' % id for id in self.playlist_songs]) except: pass def _pull(self): DBDataWrite._pull(self) self._pl_tolist() def _push(self): self._pl_tostr() DBDataWrite._push(self) self._pl_tolist() def _evalwheredat(self, wheredat=None): DBDataWrite._evalwheredat(self, wheredat) self._pl_tolist() @classmethod def fromSong(cls, song, db=None): """Returns an iterable of playlists containing the given song.""" try: db = song._db song = song.song_id except AttributeError: db = DBCache(db) song = Song(song, db).song_id return cls._fromQuery("WHERE LOCATE(%s, playlist_songs)", song, db) class MusicDirectory( MusicSchema, DBDataWrite ): _table = 'music_directories' @classmethod def fromPath(cls, path, db=None): db = MythDB(db) c = db.cursor() count = c.execute("""SELECT * FROM %s WHERE path=%s""" \ % cls._table, path) if count > 1: raise MythDBError('Multiple matches for MusicDirectory.fromPath') elif count == 1: return cls.fromRaw(c.fetchone()) else: directory = cls() directory.path = path if path.find('/') != -1: directory.parent_id = \ cls.fromPath(path.rsplit('/',1)[0]).directory_id return directory.create()
Optional Paste Settings
Category:
None
Cryptocurrency
Cybersecurity
Fixit
Food
Gaming
Haiku
Help
History
Housing
Jokes
Legal
Money
Movies
Music
Pets
Photo
Science
Software
Source Code
Spirit
Sports
Travel
TV
Writing
Tags:
Syntax Highlighting:
None
Bash
C
C#
C++
CSS
HTML
JSON
Java
JavaScript
Lua
Markdown (PRO members only)
Objective C
PHP
Perl
Python
Ruby
Swift
4CS
6502 ACME Cross Assembler
6502 Kick Assembler
6502 TASM/64TASS
ABAP
AIMMS
ALGOL 68
APT Sources
ARM
ASM (NASM)
ASP
ActionScript
ActionScript 3
Ada
Apache Log
AppleScript
Arduino
Asymptote
AutoIt
Autohotkey
Avisynth
Awk
BASCOM AVR
BNF
BOO
Bash
Basic4GL
Batch
BibTeX
Blitz Basic
Blitz3D
BlitzMax
BrainFuck
C
C (WinAPI)
C Intermediate Language
C for Macs
C#
C++
C++ (WinAPI)
C++ (with Qt extensions)
C: Loadrunner
CAD DCL
CAD Lisp
CFDG
CMake
COBOL
CSS
Ceylon
ChaiScript
Chapel
Clojure
Clone C
Clone C++
CoffeeScript
ColdFusion
Cuesheet
D
DCL
DCPU-16
DCS
DIV
DOT
Dart
Delphi
Delphi Prism (Oxygene)
Diff
E
ECMAScript
EPC
Easytrieve
Eiffel
Email
Erlang
Euphoria
F#
FO Language
Falcon
Filemaker
Formula One
Fortran
FreeBasic
FreeSWITCH
GAMBAS
GDB
GDScript
Game Maker
Genero
Genie
GetText
Go
Godot GLSL
Groovy
GwBasic
HQ9 Plus
HTML
HTML 5
Haskell
Haxe
HicEst
IDL
INI file
INTERCAL
IO
ISPF Panel Definition
Icon
Inno Script
J
JCL
JSON
Java
Java 5
JavaScript
Julia
KSP (Kontakt Script)
KiXtart
Kotlin
LDIF
LLVM
LOL Code
LScript
Latex
Liberty BASIC
Linden Scripting
Lisp
Loco Basic
Logtalk
Lotus Formulas
Lotus Script
Lua
M68000 Assembler
MIX Assembler
MK-61/52
MPASM
MXML
MagikSF
Make
MapBasic
Markdown (PRO members only)
MatLab
Mercury
MetaPost
Modula 2
Modula 3
Motorola 68000 HiSoft Dev
MySQL
Nagios
NetRexx
Nginx
Nim
NullSoft Installer
OCaml
OCaml Brief
Oberon 2
Objeck Programming Langua
Objective C
Octave
Open Object Rexx
OpenBSD PACKET FILTER
OpenGL Shading
Openoffice BASIC
Oracle 11
Oracle 8
Oz
PARI/GP
PCRE
PHP
PHP Brief
PL/I
PL/SQL
POV-Ray
ParaSail
Pascal
Pawn
Per
Perl
Perl 6
Phix
Pic 16
Pike
Pixel Bender
PostScript
PostgreSQL
PowerBuilder
PowerShell
ProFTPd
Progress
Prolog
Properties
ProvideX
Puppet
PureBasic
PyCon
Python
Python for S60
QBasic
QML
R
RBScript
REBOL
REG
RPM Spec
Racket
Rails
Rexx
Robots
Roff Manpage
Ruby
Ruby Gnuplot
Rust
SAS
SCL
SPARK
SPARQL
SQF
SQL
SSH Config
Scala
Scheme
Scilab
SdlBasic
Smalltalk
Smarty
StandardML
StoneScript
SuperCollider
Swift
SystemVerilog
T-SQL
TCL
TeXgraph
Tera Term
TypeScript
TypoScript
UPC
Unicon
UnrealScript
Urbi
VB.NET
VBScript
VHDL
VIM
Vala
Vedit
VeriLog
Visual Pro Log
VisualBasic
VisualFoxPro
WHOIS
WhiteSpace
Winbatch
XBasic
XML
XPP
Xojo
Xorg Config
YAML
YARA
Z80 Assembler
ZXBasic
autoconf
jQuery
mIRC
newLISP
q/kdb+
thinBasic
Paste Expiration:
Never
Burn after read
10 Minutes
1 Hour
1 Day
1 Week
2 Weeks
1 Month
6 Months
1 Year
Paste Exposure:
Public
Unlisted
Private
Folder:
(members only)
Password
NEW
Enabled
Disabled
Burn after read
NEW
Paste Name / Title:
Create New Paste
Hello
Guest
Sign Up
or
Login
Sign in with Facebook
Sign in with Twitter
Sign in with Google
You are currently not logged in, this means you can not edit or delete anything you paste.
Sign Up
or
Login
Public Pastes
Untitled
6 min ago | 6.06 KB
Untitled
2 hours ago | 6.29 KB
Untitled
4 hours ago | 7.39 KB
Untitled
6 hours ago | 5.74 KB
Untitled
8 hours ago | 5.00 KB
Untitled
9 hours ago | 9.00 KB
Untitled
13 hours ago | 5.64 KB
PS: CI Sheet Review/Finalize CI Sheet - Respo...
13 hours ago | 0.27 KB
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the
Cookies Policy
.
OK, I Understand
Not a member of Pastebin yet?
Sign Up
, it unlocks many cool features!