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()
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.kFindDailyRecord, RECTYPE.kFindWeeklyRecord, RECTYPE.kFindOneRecord): 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)
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)
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)
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)
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)
class Video(VideoSchema, DBDataWrite, CMPVideo): """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'}) class _open(object): def __init__(self, func): self.__name__ = func.__name__ self.__module__ = func.__module__ self.__doc__ = """Video.%s(mode='r', nooverwrite=False) -> file or FileTransfer object""" % self.__name__ self.type, self.sgroup = \ {'':('filename','Videos'), 'Banner':('banner','Banners'), 'Coverart':('coverfile','Coverart'), 'Fanart':('fanart','Fanart'), 'Screenshot':('screenshot','Screenshots'), 'Trailer':('trailer','Trailers')}[self.__name__[4:]] def __get__(self, inst, own): self.inst = inst return self def __call__(self, mode='r', nooverwrite=False): if self.inst.host == '': raise MythFileError('File access only works ' 'with Storage Group content') return ftopen( 'myth://%s@%s/%s' % (self.sgroup, self.inst.host, self.inst[self.type]), mode, False, nooverwrite, self.inst._db) @classmethod def _setClassDefs(cls, db=None): db = DBCache(db) super(Video, cls)._setClassDefs(db) cls._fill_cm(db) @classmethod def _getGroup(cls, host, groupname=None, db=None): db = DBCache(db) metadata = ['coverart', 'fanart', 'banner', 'screenshot'] fields = ['coverfile', 'fanart', 'banner', 'screenshot'] groups = ['Coverart', 'Fanart', 'Banners', 'Screenshots'] if (groupname is None) or (groupname == 'Videos'): if len(list(db.getStorageGroup('Videos', host))) == 0: raise MythError('MythVideo not set up for this host.') else: return 'Videos' elif groupname in groups: if len(list(db.getStorageGroup(groupname, host))) == 0: return cls._getGroup(host, 'Videos', db) else: return groupname elif groupname in fields: return cls._getGroup(host, groups[fields.index(groupname)]) elif groupname in metadata: return cls._getGroup(host, groups[metadata.index(groupname)]) else: raise MythError('Invalid Video StorageGroup name.') @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() def __repr__(self): return str(self).encode('utf-8') def __str__(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() 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) @_open def open(self, mode='r', nooverwrite=False): pass @_open def openBanner(self, mode='r', nooverwrite=False): pass @_open def openCoverart(self, mode='r', nooverwrite=False): pass @_open def openFanart(self, mode='r', nooverwrite=False): pass @_open def openScreenshot(self, mode='r', nooverwrite=False): pass @_open def openTrailer(self, mode='r', nooverwrite=False): pass def getHash(self): """Video.getHash() -> file hash""" if self.host is None: return None be = FileOps(self.host) hash = be.getHash(self.filename, 'Videos') 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'): 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): # only perform image grabs if 'host' is set, denoting SG use t1 = ['coverart', 'fanart', 'banner', 'screenshot'] t2 = ['coverfile', 'fanart', 'banner', 'screenshot'] mdtype = dict(zip(t1, t2)) exists = dict(zip(t1, [False, False, False, False])) be = FileOps(self.host, db=self._db) for image in metadata.images: if exists[image.type]: continue if not _allow_change(self, mdtype[image.type], overwrite): continue self[mdtype[image.type]] = image.filename group = self._getGroup(self.host, image.type, self._db) if not be.fileExists(image.filename, group): be.downloadTo(image.url, group, image.filename) exists[image.type] = True self.processed = True self.update() 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))
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.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.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()