Beispiel #1
0
 def __init__(self):
     self.db = MythDB()
     self.be = MythBE(db=db)
     self.vids = {}
     self._addCallback = doNothing
     self._events = [self.handleUpdate]
     self.be.registerevent(self.handleUpdate)
    def copy(self):
        stime = time.time()
        srcsize = self.rec.filesize
        htime = [stime,stime,stime,stime]

        self.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "Copying myth://%s@%s/%s"\
               % (self.rec.storagegroup, self.rec.hostname, self.rec.basename)\
                                                    +" to myth://Videos@%s/%s"\
                                          % (self.vid.host, self.vid.filename))
        srcfp = self.rec.open('r')
        dstfp = self.vid.open('w', nooverwrite=True)


        if self.job:
            self.job.setStatus(Job.RUNNING)
        tsize = 2**24
        while tsize == 2**24:
            tsize = min(tsize, srcsize - dstfp.tell())
            dstfp.write(srcfp.read(tsize))
            htime.append(time.time())
            rate = float(tsize*4)/(time.time()-htime.pop(0))
            remt = (srcsize-dstfp.tell())/rate
            if self.job:
                self.job.setComment("%02d%% complete - %d seconds remaining" %\
                            (dstfp.tell()*100/srcsize, remt))
        srcfp.close()
        dstfp.close()

        self.vid.hash = self.vid.getHash()

        self.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "Transfer Complete",
                            "%d seconds elapsed" % int(time.time()-stime))

        if self.opts.reallysafe:
            if self.job:
                self.job.setComment("Checking file hashes")
            self.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "Checking file hashes.")
            srchash = hashfile(self.rec.open('r'))
            dsthash = hashfile(self.vid.open('r'))
            if srchash != dsthash:
                raise MythError('Source hash (%s) does not match destination hash (%s)' \
                            % (srchash, dsthash))
        elif self.opts.safe:
            self.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "Checking file sizes.")
            be = MythBE(db=self.vid._db)
            try:
                srcsize = be.getSGFile(self.rec.hostname, self.rec.storagegroup, \
                                       self.rec.basename)[1]
                dstsize = be.getSGFile(self.vid.host, 'Videos', self.vid.filename)[1]
            except:
                raise MythError('Could not query file size from backend')
            if srcsize != dstsize:
                raise MythError('Source size (%d) does not match destination size (%d)' \
                            % (srcsize, dstsize))

        if self.job:
            self.job.setComment("Complete - %d seconds elapsed" % \
                            (int(time.time()-stime)))
            self.job.setStatus(Job.FINISHED)
    def __init__(self, host=None):
        self.db = MythDB()
        self.db.searchRecorded.handler = Recorded
        self.be = MythBE(db=self.db)
        self.log = MythLog(db=self.db)

        self.set_host(host)
        self.load_backends()
        self.load_storagegroups()
Beispiel #4
0
class Videos( Handler ):
    def __init__(self):
        self.db = MythDB()
        self.be = MythBE(db=db)
        self.vids = {}
        self._addCallback = doNothing
        self._events = [self.handleUpdate]
        self.be.registerevent(self.handleUpdate)

    def add(self, vid):
        if not vid.browse:
            return
        if vid.intid in self.vids:
            return

        vid.path = vid.filename
        vid.attr = Attr()
        try:
            ctime = vid.insertdate.timestamp()
        except:
            ctime = 0
        vid.attr.st_ctime = ctime
        vid.attr.st_atime = atime
        vid.attr.st_mtime = ctime
        vid.attr.st_mode = stat.S_IFREG | 0444
        t,s = self.be.getSGFile(vid.host, 'Videos', vid.filename)
        vid.attr.st_size = int(s)

        self._addCallback(vid)
        self.vids[vid.intid] = vid.attr.st_ino

    def getAll(self):
        for vid in Video.getAllEntries(db=self.db):
            self.add(vid)

    def handleUpdate(self, event=None):
        if event is None:
            self._reUp = re.compile(
                    re.escape(static.BACKEND_SEP).\
                        join(['BACKEND_MESSAGE',
                              'VIDEO_LIST_CHANGE',
                              'empty']))
            return self._reUp
        with self.db as cursor:
            cursor.execute("""SELECT intid FROM videometadata""")
            newids = [id[0] for id in cursor.fetchall()]

        oldids = self.vids.keys()
        for id in list(oldids):
            if id in newids:
                oldids.remove(id)
                newids.remove(id)

        for id in oldids:
            self._deleteCallback(self.vids[id])
        for id in newids:
            self.add(Video(id, db=self.db))
Beispiel #5
0
class Videos(Handler):
    def __init__(self):
        self.db = MythDB()
        self.be = MythBE(db=db)
        self.vids = {}
        self._addCallback = doNothing
        self._events = [self.handleUpdate]
        self.be.registerevent(self.handleUpdate)

    def add(self, vid):
        if not vid.browse:
            return
        if vid.intid in self.vids:
            return

        vid.path = vid.filename
        vid.attr = Attr()
        try:
            ctime = vid.insertdate.timestamp()
        except:
            ctime = 0
        vid.attr.st_ctime = ctime
        vid.attr.st_atime = atime
        vid.attr.st_mtime = ctime
        vid.attr.st_mode = stat.S_IFREG | 0444
        t, s = self.be.getSGFile(vid.host, 'Videos', vid.filename)
        vid.attr.st_size = int(s)

        self._addCallback(vid)
        self.vids[vid.intid] = vid.attr.st_ino

    def getAll(self):
        for vid in Video.getAllEntries(db=self.db):
            self.add(vid)

    def handleUpdate(self, event=None):
        if event is None:
            self._reUp = re.compile(
                    re.escape(static.BACKEND_SEP).\
                        join(['BACKEND_MESSAGE',
                              'VIDEO_LIST_CHANGE',
                              'empty']))
            return self._reUp
        with self.db as cursor:
            cursor.execute("""SELECT intid FROM videometadata""")
            newids = [id[0] for id in cursor.fetchall()]

        oldids = self.vids.keys()
        for id in list(oldids):
            if id in newids:
                oldids.remove(id)
                newids.remove(id)

        for id in oldids:
            self._deleteCallback(self.vids[id])
        for id in newids:
            self.add(Video(id, db=self.db))
Beispiel #6
0
 def checkRecordingStatus(self):
     """Checks whether the backend is currently recording."""
     try:
         recbe = MythBE()
         for recorder in recbe.getRecorderList():
             if recbe.isRecording(recorder):
                 self.isrecording = True
                 break
     except:
         # If we can't connect to it then it can't be recording.
         self.isrecording = False
 def checkRecordingStatus(self):
     """Checks whether the backend is currently recording."""
     try:
         recbe = MythBE()
         for recorder in recbe.getRecorderList():
             if recbe.isRecording(recorder):
                 self.isrecording = True
                 break
     except:
         # If we can't connect to it then it can't be recording.
         self.isrecording = False
def main():
    """Startup function."""
    parser = OptionParser(usage="usage: %prog [options]")
    parser.add_option(
        "--verbose",
        action="store_true",
        default=False,
        help="enable verbose output of MythTV API")
    parser.add_option(
        '-f',
        "--force",
        action="store_true",
        default=False,
        help="non-interactive mode, answer 'yes' to all questions")
    parser.add_option(
        '-t',
        "--title",
        action="store",
        type="string",
        help="limit recordings that match title")

    opts, _ = parser.parse_args()
    MythLog._setlevel('unknown' if opts.verbose else 'err')  # pylint:disable=protected-access

    try:
        backend = MythBE()
        recs = [
            r for r in list(backend.getRecordings()) if r.recgroup == 'Deleted'
        ]
        if opts.title:
            recs = [
                r for r in recs
                if re.findall(opts.title, r.title, re.IGNORECASE)
            ]
        if len(recs) == 0:
            print('no matching recordings found')
            sys.exit(0)
        if opts.force:
            undelete_all(backend, recs)
        else:
            interactive_undelete(backend, recs)
    except MythDBError as e:
        if e.name == 'DB_CREDENTIALS':
            print("ERROR: Could not find MythDB host:port OR correct login "
                  "credentials!")
            sys.exit(-1)
        else:
            raise
    sys.exit(0)
 def getRecordings(self):
     """Attempts to connect to MythTV backend and retrieve recordings."""
     try:
         # If we can connect then get recordings and save a local cache.
         self.be = MythBE()
         uprecs = self.be.getUpcomingRecordings()
         self.recs = self.recs_to_dict(uprecs)
         self.cacheRecs(self.recs)
         self.backendonline = True
     except:
         # Can't connect so we need to set variables accordinly and try
         # to load data from the cache.
         self.be = None
         self.recs = self.loadCache()
         self.backendonline = False
Beispiel #10
0
 def __init__(self):
     self.db = MythDB()
     self.be = MythBE(db=db)
     self.vids = {}
     self._addCallback = doNothing
     self._events = [self.handleUpdate]
     self.be.registerevent(self.handleUpdate)
    def __init__(self, opts, jobid=None):                           
        
        # Setup for the job to run
        if jobid:
            self.thisJob = Job(jobid)
            self.chanID = self.thisJob.chanid
            self.startTime = self.thisJob.starttime
            self.thisJob.update(status=Job.STARTING)
        
        # If no job ID given, must be a command line run
        else:
            self.thisJob = jobid
            self.chanID = opts.chanid
            self.startTime = opts.startdate + " " + opts.starttime + opts.offset
        self.opts = opts
        self.type = "none"
        self.db = MythDB()
        self.log = MythLog(module='Myth-Rec-to-Vid.py', db=self.db)
        

        # Capture the backend host name
        self.host = self.db.gethostname()

        # prep objects
        self.rec = Recorded((self.chanID,self.startTime), db=self.db)
        self.log(MythLog.GENERAL, MythLog.INFO, 'Using recording',
                        '%s - %s' % (self.rec.title.encode('utf-8'), 
                                     self.rec.subtitle.encode('utf-8')))

        self.vid = Video(db=self.db).create({'title':'', 'filename':'',
                                             'host':self.host})

        self.bend = MythBE(db=self.db)
Beispiel #12
0
def transcode(jobid=None, chanid=None, starttime=None):
        ' connect to mythtv database '
        db = MythDB()
        be = MythBE(db=db)

	logging.info("start transcode")

	if jobid:
		job = Job(jobid, db=db)
		chanid = job.chanid
		starttime = job.starttime
		logging.info("%s" % jobid)
		logging.info("%s" % chanid)
		logging.info("%s" % starttime)
	rec = Recorded((chanid, starttime), db=db)
	' TODO: get the starttime into the correct format (from 20150827014200 to 2015-08-27 00:42:00-06:00)'

	' loop all files in lib_dir that are symlinks and find the one that matches this recording '
        for ld in LIBDIR:
                for dp, dn, files in os.walk(ld):
                        for file in files:
                                filepath = os.path.join(dp,file)
                                if (os.path.islink(filepath)):
                                        logging.debug("%s -> %s" % (filepath, os.readlink(filepath)))

	' do the transode '

	' update the database for the new file name '

	' update the symlink for the new file name '
Beispiel #13
0
def main():
    """Startup function."""
    parser = OptionParser(usage="usage: %prog [options]")
    parser.add_option("--verbose",
                      action="store_true",
                      default=False,
                      help="enable verbose output of MythTV API")
    parser.add_option(
        '-f',
        "--force",
        action="store_true",
        default=False,
        help="non-interactive mode, answer 'yes' to all questions")
    parser.add_option('-t',
                      "--title",
                      action="store",
                      type="string",
                      help="limit recordings that match title")

    opts, _ = parser.parse_args()
    MythLog._setlevel('unknown' if opts.verbose else 'err')  # pylint:disable=protected-access

    try:
        backend = MythBE()
        recs = [
            r for r in list(backend.getRecordings()) if r.recgroup == 'Deleted'
        ]
        if opts.title:
            recs = [
                r for r in recs
                if re.findall(opts.title, r.title, re.IGNORECASE)
            ]
        if len(recs) == 0:
            print('no matching recordings found')
            sys.exit(0)
        if opts.force:
            undelete_all(backend, recs)
        else:
            interactive_undelete(backend, recs)
    except MythDBError as e:
        if e.name == 'DB_CREDENTIALS':
            print("ERROR: Could not find MythDB host:port OR correct login "
                  "credentials!")
            sys.exit(-1)
        else:
            raise
    sys.exit(0)
def main(opts):
    MythBE.getPendingRecordings.handler = MyProgram
    be = MythBE()
    now = datetime.now()

    if not opts.plaintext:
        print '<h3>Upcoming Recordings:</h3>'
        print '<div class="schedule">'

    count = 0
    for rec in be.getPendingRecordings():
        if not ((opts.filter & 2**0 and rec.is_scheduled) or
                (opts.filter & 2**1 and rec.is_duplicate) or
                (opts.filter & 2**2 and rec.is_deactivated) or
                (opts.filter & 2**3 and rec.is_conflict)):
            continue
        if opts.time and (opts.time < rec.recstartts):
            continue
        if now > rec.recendts:
            continue
        if opts.count and (opts.count <= count):
            break
        count += 1

        if opts.plaintext:
            print '{0} - {1}'.format(rec.starttime.strftime('%m/%d, %I:%M %p'),
                                     rec.callsign)
            if rec.subtitle:
                print '{0.title} - {0.subtitle}'.format(rec)
            else:
                print '{0.title}'.format(rec)
            print rec.description
            print ''
        else:
            print '<a href="#">{0} - {1} - {2}'.format(
                rec.starttime.strftime('%m/%d, %I:%M %p'), rec.callsign,
                rec.title),
            if rec.subtitle:
                print rec.subtitle,
            print '<br /><span><strong>{0.title}</strong>'.format(rec),
            print rec.starttime.strftime('%m/%d, %I:%M %p'),
            print '<br /><em>{0.description}<br/></span></a><hr />'

    if not opts.plaintext:
        print '</div>'
def main(opts):
    MythBE.getPendingRecordings.handler = MyProgram
    be = MythBE()
    now = datetime.now()

    if not opts.plaintext:
        print '<h3>Upcoming Recordings:</h3>'
        print '<div class="schedule">'

    count = 0
    for rec in be.getPendingRecordings():
        if not ((opts.filter&2**0 and rec.is_scheduled) or
                (opts.filter&2**1 and rec.is_duplicate) or
                (opts.filter&2**2 and rec.is_deactivated) or
                (opts.filter&2**3 and rec.is_conflict)):
            continue
        if opts.time and (opts.time < rec.recstartts):
            continue
        if now > rec.recendts:
            continue
        if opts.count and (opts.count <= count):
            break
        count += 1

        if opts.plaintext:
            print '{0} - {1}'.format(rec.starttime.strftime('%m/%d, %I:%M %p'),
                                     rec.callsign)
            if rec.subtitle:
                print '{0.title} - {0.subtitle}'.format(rec)
            else:
                print '{0.title}'.format(rec)
            print rec.description
            print ''
        else:
            print '<a href="#">{0} - {1} - {2}'.format(rec.starttime.strftime('%m/%d, %I:%M %p'),
                                     rec.callsign,
                                     rec.title),
            if rec.subtitle:
                print rec.subtitle,
            print '<br /><span><strong>{0.title}</strong>'.format(rec),
            print rec.starttime.strftime('%m/%d, %I:%M %p'),
            print '<br /><em>{0.description}<br/></span></a><hr />'

    if not opts.plaintext:
        print '</div>'
    def __init__(self, host=None):
        self.db = MythDB()
        self.db.searchRecorded.handler = Recorded
        self.be = MythBE(db=self.db)
        self.log = MythLog(db=self.db)

        self.set_host(host)
        self.load_backends()
        self.load_storagegroups()
    def test_repr_004_01(self):
        """
        Test '__repr__' and '__str__' methods of 'MythBE' class.
        Note: MythBE inherits from 'FileOps', which is inherited from 'BeCache'.
        """

        db = MythDB()
        be = MythBE(db=db)
        print()
        print(repr(be))
        print(str(be))
 def load_backends(self):
     with self.db as c:
         c.execute("""SELECT hostname FROM settings
                      WHERE value='BackendServerIP'""")
         hosts = [r[0] for r in c.fetchall()]
     self.hosts = []
     for host in hosts:
         # try to access all defined hosts, and
         # store the ones currently accessible
         try:
             MythBE(backend=host)
             self.hosts.append(host)
         except:
             pass
Beispiel #19
0
 def getRecordings(self):
     """Attempts to connect to MythTV backend and retrieve recordings."""
     try:
         # If we can connect then get recordings and save a local cache.
         self.be = MythBE()
         uprecs = self.be.getUpcomingRecordings()
         self.recs = self.recs_to_dict(uprecs)
         self.cacheRecs(self.recs)
         self.backendonline = True
     except:
         # Can't connect so we need to set variables accordinly and try
         # to load data from the cache.
         self.be = None
         self.recs = self.loadCache()
         self.backendonline = False
Beispiel #20
0
    def __init__(self, opts, jobid=None):

        # Setup for the job to run
        if jobid:
            self.thisJob = Job(jobid)
            self.chanID = self.thisJob.chanid
            self.startTime = self.thisJob.starttime
            self.thisJob.update(status=Job.STARTING)

        # If no job ID given, must be a command line run
        else:
            self.thisJob = jobid
            self.chanID = opts.chanid
            self.startTime = opts.startdate + " " + opts.starttime + opts.offset
        self.opts = opts
        self.type = "none"
        self.db = MythDB()
        self.log = MythLog(module='Myth-Rec-to-Vid.py', db=self.db)

        # Capture the backend host name
        self.host = self.db.gethostname()

        # prep objects
        self.rec = Recorded((self.chanID, self.startTime), db=self.db)
        self.log(
            MythLog.GENERAL, MythLog.INFO, 'Using recording',
            '%s - %s' % (self.rec.title.encode('utf-8'),
                         self.rec.subtitle.encode('utf-8')))

        self.vid = Video(db=self.db).create({
            'title': '',
            'filename': '',
            'host': self.host
        })

        self.bend = MythBE(db=self.db)
#!/usr/bin/python

# Check for recording conflicts on a mythtv-backend

from MythTV import MythBE

be = MythBE()

for program in be.getConflictedRecordings():
    title = "{} - {}".format(program['title'], program['subtitle'])
    starttime = program['starttime'].strftime('%Y-%m-%d %I:%M %p')
    print("{} at {} is in conflict and will not be recorded.\n".format(title, starttime))
Beispiel #22
0
 def delete(self):
     be = MythBE(self.host, db=DB)
     be.deleteFile(self, self.group)
Beispiel #23
0
#!/usr/bin/python

# get the files to delete
# ./files.py | cut -f 1 -d " " | grep -v lock | grep var | xargs ls -lastrh

from MythTV import MythBE
import os

myth = MythBE()
allShows = myth.getRecordings()
dirs = ["/var/lib/mythtv/recordings", "/var/lib/mythtv/recordings2", "/var/lib/mythtv/recordings3", "/var/lib/mythtv/movies-group"]

showMap = {}
badFiles = []

def handleFile(dir,file):
	parts = file.split(".")
	key = parts[0]
	bad = False
	if key not in showMap:
		badFiles.append(os.path.join(dir,file))
		bad = True

		if parts[len(parts)-1] == "mpg":
			print key, file

def handleDir(dir):
	for file in os.listdir(dir):
		path = os.path.join(dir,file)
		if os.path.isdir(path):
			handleDir(path)
 def delete(self):
     be = MythBE(self.hosts[0], db=self.db)
     be.deleteFile(self, self.group)
Beispiel #25
0
#!/usr/bin/python

from MythTV import MythBE, Program
from datetime import timedelta, datetime, time

def willRec(x):
	return x.recstatus == Program.rsWillRecord and x.airdate is not None

myth = MythBE()
pending = filter(willRec, myth.getPendingRecordings())

print "%-20.20s %-35.35s %-20s %-20s" % ("title", "subtitle", "original date", "record start time")
for p in sorted(pending, key=lambda a: a.title):
	diff = p.starttime - datetime.combine(p.airdate, time(tzinfo=p.starttime.tzinfo))
	if diff > timedelta(days=1,hours=6) and diff < timedelta(days=24):
		print "%-20.20s %-35.35s %-20s %-20s" % (p.title, p.subtitle, p.airdate, p.starttime)

    def copy(self):
        stime = time.time()
        srcsize = self.rec.filesize
        htime = [stime, stime, stime, stime]

        self.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "Copying myth://%s@%s/%s"\
               % (self.rec.storagegroup, self.rec.hostname, self.rec.basename)\
                                                    +" to myth://Videos@%s/%s"\
                                          % (self.vid.host, self.vid.filename))
        srcfp = self.rec.open('r')
        dstfp = self.vid.open('w', nooverwrite=True)

        if self.job:
            self.job.setStatus(Job.RUNNING)
        tsize = 2**24
        while tsize == 2**24:
            tsize = min(tsize, srcsize - dstfp.tell())
            dstfp.write(srcfp.read(tsize))
            htime.append(time.time())
            rate = float(tsize * 4) / (time.time() - htime.pop(0))
            remt = (srcsize - dstfp.tell()) / rate
            if self.job:
                self.job.setComment("%02d%% complete - %d seconds remaining" %\
                            (dstfp.tell()*100/srcsize, remt))
        srcfp.close()
        dstfp.close()

        self.vid.hash = self.vid.getHash()

        self.log(MythLog.GENERAL | MythLog.FILE, MythLog.INFO,
                 "Transfer Complete",
                 "%d seconds elapsed" % int(time.time() - stime))

        if self.opts.reallysafe:
            if self.job:
                self.job.setComment("Checking file hashes")
            self.log(MythLog.GENERAL | MythLog.FILE, MythLog.INFO,
                     "Checking file hashes.")
            srchash = hashfile(self.rec.open('r'))
            dsthash = hashfile(self.vid.open('r'))
            if srchash != dsthash:
                raise MythError('Source hash (%s) does not match destination hash (%s)' \
                            % (srchash, dsthash))
        elif self.opts.safe:
            self.log(MythLog.GENERAL | MythLog.FILE, MythLog.INFO,
                     "Checking file sizes.")
            be = MythBE(db=self.vid._db)
            try:
                srcsize = be.getSGFile(self.rec.hostname, self.rec.storagegroup, \
                                       self.rec.basename)[1]
                dstsize = be.getSGFile(self.vid.host, 'Videos',
                                       self.vid.filename)[1]
            except:
                raise MythError('Could not query file size from backend')
            if srcsize != dstsize:
                raise MythError('Source size (%d) does not match destination size (%d)' \
                            % (srcsize, dstsize))

        if self.job:
            self.job.setComment("Complete - %d seconds elapsed" % \
                            (int(time.time()-stime)))
            self.job.setStatus(Job.FINISHED)
Beispiel #27
0

def filepathToScreenshotFromRecording(recording):
    filepath = filepathFromRecording(recording)
    return filepath + ".png"


db = None
db = MythDB()

if db == None:
    print "Database could not be open or found"
    exit()

be = None
be = MythBE()

if be == None:
    print "Backend could not be found."
    #exit()

recordings = be.getRecordings()

for r in recordings:
    filepath = filepathFromRecording(r)
    size = -1
    try:
        size = os.path.getsize(filepath) / 1024 / 1024
    except:
        size = "NaN"
Beispiel #28
0
	for programTeam in programTeams:
		if isTeamMatch(team, programTeam):
			return True
	return False

try:
    opts, args = getopt.getopt(sys.argv[1:],"h:",["hours="])
except getopt.GetoptError:
    print 'soccer.py -h <hours a recording can be late - default 1>'
    sys.exit(2)

hours = 1
for o, a in opts:
    if o == "-h":
        hours = int(a)


myth = MythBE()
pending = filter(sportsRecs, myth.getPendingRecordings())

# get the output from the schedule
output = check_output(["/usr/bin/java", "-jar", "/opt/soccer/soccerschedule.jar", "--weeks=2"])

# split the output into lines and then filter only the real output
lines = filter(filterOutput,output.splitlines())
for line in lines:
	data = getData(line)
	if not hasMatches(data, pending):
		print "%s %s vs. %s (%s) on %s " % (data[0], " ".join(data[3]), " ".join(data[2]), data[1], data[4])

#!/usr/bin/python

# Check for recording conflicts on a mythtv-backend

from MythTV import MythBE

be = MythBE()

for program in be.getConflictedRecordings():
    title = "{} - {}".format(program['title'], program['subtitle'])
    starttime = program['starttime'].strftime('%Y-%m-%d %I:%M %p')
    print("{} at {} is in conflict and will not be recorded.\n".format(
        title, starttime))
Beispiel #30
0
    def showScreen(self):
        self.surface.fill([0, 0, 0])

        if self.be == None:
            try:
                recs = []
                self.be = MythBE()
                self.upcomingrecs = self.be.getUpcomingRecordings()
                for r in self.upcomingrecs:
                    rec = {}
                    rec["title"] = r.title
                    rec["subtitle"] = r.subtitle
                    rec["time"] = r.starttime.strftime("%a %d %b %H:%M")
                    rec["desc"] = r.description
                    recs.append(rec)
                recorders = MythBE().getRecorderList()
                for recorder in recorders:
                    if MythBE().isRecording(recorder):
                        self.isrecording = True
                        break
                self.backendfail = False
                self.cachedmode = False
            except:
                self.backendfail = True

        if self.backendfail:
            recs = self.loadCache()
            if recs:
                self.backendfail = False
                self.cachedmode = True

        if not self.backendfail:

            self.cacheRecs(recs)

            screentitle = self.mytitlefont.render("MythTV upcoming recordings",
                                                  1, (255, 255, 255))
            screenrect = screentitle.get_rect()
            screenrect.centerx = self.surface.get_rect().centerx
            screenrect.centery = 20
            self.surface.blit(screentitle, screenrect)

            n = min(len(recs), 5)
            if n > 0:
                for i in range(n):
                    mytitlerect = pygame.Rect((0, 0, self.rectwidth, 60))
                    mytimerect = pygame.Rect((0, 0, self.rectwidth, 30))
                    mydescrect = pygame.Rect((0, 0, self.rectwidth, 330))
                    fontcolour = (255, 255, 255)
                    rectcolour = (0, 50, 75)
                    titletext = self.render_textrect(recs[i]["title"],
                                                     self.myboldfont,
                                                     mytitlerect, fontcolour,
                                                     rectcolour, 1)
                    timetext = self.render_textrect(recs[i]["time"],
                                                    self.myitalicfont,
                                                    mytimerect, fontcolour,
                                                    rectcolour, 1)
                    desctext = self.render_textrect(recs[i]["desc"],
                                                    self.myregularfont,
                                                    mydescrect,
                                                    fontcolour,
                                                    rectcolour,
                                                    0,
                                                    margin=5)
                    self.surface.blit(titletext,
                                      ((self.rectwidth * i) +
                                       (self.rectgap *
                                        (i + 1) + self.rectadjust), 40))
                    self.surface.blit(timetext,
                                      ((self.rectwidth * i) +
                                       (self.rectgap *
                                        (i + 1) + self.rectadjust), 80))
                    self.surface.blit(desctext,
                                      ((self.rectwidth * i) +
                                       (self.rectgap *
                                        (i + 1) + self.rectadjust), 105))

                if self.cachedmode:
                    mystatus = self.myitalicfont.render(
                        "Backend is offline. Displaying cached recording list",
                        1, (255, 255, 255))
                else:
                    if self.isrecording:
                        recording = "currently"
                    else:
                        recording = "not"
                    mystatus = self.myitalicfont.render(
                        "Backend is online and is " + recording +
                        " recording.", 1, (255, 255, 255))

                self.surface.blit(mystatus, (5, 445))
            else:
                failtext = self.myboldfont.render(
                    "No upcoming recordings found.", 1, (255, 255, 255))
                failrect = failtext.get_rect()
                failrect.centerx = self.surface.get_rect().centerx
                failrect.centery = self.surface.get_rect().centery
                self.surface.blit(failtext, failrect)

        else:
            failtext = self.myboldfont.render("MythTV backend unavailable.", 1,
                                              (255, 255, 255))
            failrect = failtext.get_rect()
            failrect.centerx = self.surface.get_rect().centerx
            failrect.centery = self.surface.get_rect().centery
            self.surface.blit(failtext, failrect)

        self.be = None

        # Scale our surface to the required screensize before sending back
        scaled = pygame.transform.scale(self.surface, self.screensize)
        self.screen.blit(scaled, (0, 0))

        return self.screen
Beispiel #31
0
class myScreen(PiInfoScreen):
    refreshtime = 60
    displaytime = 10
    pluginname = "MythTV"
    plugininfo = "Displays details of upcoming recordings"
    supportedsizes = [(694, 466)]

    def setPluginVariables(self):
        self.be = None
        self.backendfail = False
        self.cachedmode = False
        self.isrecording = False
        self.regularfont = os.path.join(self.plugindir, "resources",
                                        "ArchivoNarrow-Regular.otf")
        self.italicfont = os.path.join(self.plugindir, "resources",
                                       "ArchivoNarrow-Italic.otf")
        self.boldfont = os.path.join(self.plugindir, "resources",
                                     "ArchivoNarrow-Bold.otf")
        self.cacheFile = os.path.join(self.plugindir, "resources",
                                      "cachedRecordings.json")
        self.mytitlefont = pygame.font.Font(self.boldfont, 24)
        self.myboldfont = pygame.font.Font(self.boldfont, 20)
        self.myregularfont = pygame.font.Font(self.regularfont, 16)
        self.myitalicfont = pygame.font.Font(self.italicfont, 16)
        self.rectwidth = 132
        self.rectgap = 5
        self.rectadjust = 2

    def cacheRecs(self, recs):
        with open(self.cacheFile, 'w') as outfile:
            json.dump(recs, outfile)

    def loadCache(self):
        try:
            raw = open(self.cacheFile, 'r')
            recs = json.load(raw)
        except:
            recs = []

        return recs

    def showScreen(self):
        self.surface.fill([0, 0, 0])

        if self.be == None:
            try:
                recs = []
                self.be = MythBE()
                self.upcomingrecs = self.be.getUpcomingRecordings()
                for r in self.upcomingrecs:
                    rec = {}
                    rec["title"] = r.title
                    rec["subtitle"] = r.subtitle
                    rec["time"] = r.starttime.strftime("%a %d %b %H:%M")
                    rec["desc"] = r.description
                    recs.append(rec)
                recorders = MythBE().getRecorderList()
                for recorder in recorders:
                    if MythBE().isRecording(recorder):
                        self.isrecording = True
                        break
                self.backendfail = False
                self.cachedmode = False
            except:
                self.backendfail = True

        if self.backendfail:
            recs = self.loadCache()
            if recs:
                self.backendfail = False
                self.cachedmode = True

        if not self.backendfail:

            self.cacheRecs(recs)

            screentitle = self.mytitlefont.render("MythTV upcoming recordings",
                                                  1, (255, 255, 255))
            screenrect = screentitle.get_rect()
            screenrect.centerx = self.surface.get_rect().centerx
            screenrect.centery = 20
            self.surface.blit(screentitle, screenrect)

            n = min(len(recs), 5)
            if n > 0:
                for i in range(n):
                    mytitlerect = pygame.Rect((0, 0, self.rectwidth, 60))
                    mytimerect = pygame.Rect((0, 0, self.rectwidth, 30))
                    mydescrect = pygame.Rect((0, 0, self.rectwidth, 330))
                    fontcolour = (255, 255, 255)
                    rectcolour = (0, 50, 75)
                    titletext = self.render_textrect(recs[i]["title"],
                                                     self.myboldfont,
                                                     mytitlerect, fontcolour,
                                                     rectcolour, 1)
                    timetext = self.render_textrect(recs[i]["time"],
                                                    self.myitalicfont,
                                                    mytimerect, fontcolour,
                                                    rectcolour, 1)
                    desctext = self.render_textrect(recs[i]["desc"],
                                                    self.myregularfont,
                                                    mydescrect,
                                                    fontcolour,
                                                    rectcolour,
                                                    0,
                                                    margin=5)
                    self.surface.blit(titletext,
                                      ((self.rectwidth * i) +
                                       (self.rectgap *
                                        (i + 1) + self.rectadjust), 40))
                    self.surface.blit(timetext,
                                      ((self.rectwidth * i) +
                                       (self.rectgap *
                                        (i + 1) + self.rectadjust), 80))
                    self.surface.blit(desctext,
                                      ((self.rectwidth * i) +
                                       (self.rectgap *
                                        (i + 1) + self.rectadjust), 105))

                if self.cachedmode:
                    mystatus = self.myitalicfont.render(
                        "Backend is offline. Displaying cached recording list",
                        1, (255, 255, 255))
                else:
                    if self.isrecording:
                        recording = "currently"
                    else:
                        recording = "not"
                    mystatus = self.myitalicfont.render(
                        "Backend is online and is " + recording +
                        " recording.", 1, (255, 255, 255))

                self.surface.blit(mystatus, (5, 445))
            else:
                failtext = self.myboldfont.render(
                    "No upcoming recordings found.", 1, (255, 255, 255))
                failrect = failtext.get_rect()
                failrect.centerx = self.surface.get_rect().centerx
                failrect.centery = self.surface.get_rect().centery
                self.surface.blit(failtext, failrect)

        else:
            failtext = self.myboldfont.render("MythTV backend unavailable.", 1,
                                              (255, 255, 255))
            failrect = failtext.get_rect()
            failrect.centerx = self.surface.get_rect().centerx
            failrect.centery = self.surface.get_rect().centery
            self.surface.blit(failtext, failrect)

        self.be = None

        # Scale our surface to the required screensize before sending back
        scaled = pygame.transform.scale(self.surface, self.screensize)
        self.screen.blit(scaled, (0, 0))

        return self.screen
Beispiel #32
0
from MythTV import MythDB, MythBE

db = None
db = MythDB()

if db == None:
    print "Database could not be open or found"
    exit()

be = None
be = MythBE()

if be == None:
    print "Backend could not be found."
    #exit()

for x in be.getRecorderList():
    print "Tuner " + str(x) + " is recording: " + str(
        be.getCurrentRecording(x).title)

fs = be.getFreeSpace()
print "----------------------------------"
for x in fs:
    print "Host: " + x.host
    print "\tPath: " + x.path
    print "\t\tTotal space: " + str(x.totalspace / 1024 / 1024) + "MB"
    print "\t\tFree  space: " + str(x.freespace / 1024 / 1024) + "MB"
    print "\t\tUsed  space: " + str(x.usedspace / 1024 / 1024) + "MB"
    print "----------------------------------"
    def copy(self):
        stime = time.time()
        srcsize = self.rec.filesize
        htime = [stime,stime,stime,stime]

        self.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "Copying myth://%s@%s/%s"\
               % (self.rec.storagegroup, self.rec.hostname, self.rec.basename)\
                                                    +" to myth://Videos@%s/%s"\
                                          % (self.vid.host, self.vid.filename))
        
        
        bend = MythBE(db=self.vid._db)
        self.log(MythLog.GENERAL, MythLog.INFO, 'Checking for duplication of ',
                    '%s - %s' % (self.rec.title.encode('utf-8'), 
                                 self.rec.subtitle.encode('utf-8')))
        if bend.fileExists(self.vid.filename, 'Videos'):
          self.log(MythLog.GENERAL, MythLog.INFO, 'Recording already exists in Myth Videos')
          self.job.setComment("Action would result in duplicate entry, job ended" )
          self.job.setStatus(Job.FINISHED)
          self.vid.delete()
          self.log(MythLog.GENERAL, MythLog.INFO, 'Exiting program')
          sys.exit(0)
    
        else:
          srcfp = self.rec.open('r')
          dstfp = self.vid.open('w')
	  
	  if self.job:
	      self.job.setStatus(Job.RUNNING)
	  tsize = 2**24
	  while tsize == 2**24:
	      tsize = min(tsize, srcsize - dstfp.tell())
	      dstfp.write(srcfp.read(tsize))
	      htime.append(time.time())
	      rate = float(tsize*4)/(time.time()-htime.pop(0))
	      remt = (srcsize-dstfp.tell())/rate
	      if self.job:
		  self.job.setComment("%02d%% complete - %d seconds remaining" %\
			      (dstfp.tell()*100/srcsize, remt))
	  srcfp.close()
	  dstfp.close()

	  self.vid.hash = self.vid.getHash()

	  self.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "Transfer Complete",
			      "%d seconds elapsed" % int(time.time()-stime))

	  if self.opts.reallysafe:
	      if self.job:
		  self.job.setComment("Checking file hashes")
	      self.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "Checking file hashes.")
	      srchash = hashfile(self.rec.open('r'))
	      dsthash = hashfile(self.rec.open('r'))
	      if srchash != dsthash:
		  raise MythError('Source hash (%s) does not match destination hash (%s)' \
			      % (srchash, dsthash))
	  elif self.opts.safe:
	      self.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "Checking file sizes.")
	      be = MythBE(db=self.vid._db)
	      try:
		  srcsize = be.getSGFile(self.rec.hostname, self.rec.storagegroup, \
					self.rec.basename)[1]
		  dstsize = be.getSGFile(self.vid.host, 'Videos', self.vid.filename)[1]
	      except:
		  raise MythError('Could not query file size from backend')
	      if srcsize != dstsize:
		  raise MythError('Source size (%d) does not match destination size (%d)' \
			      % (srcsize, dstsize))

	  if self.job:
	      self.job.setComment("Complete - %d seconds elapsed" % \
			      (int(time.time()-stime)))
	      self.job.setStatus(Job.FINISHED)
 def exists(self):
     be = MythBE(self.host, db=DB)
     return be.fileExists(self, self.group)
Beispiel #35
0
class Recordings( Handler ):
    def __init__(self):
        self.be = MythBE()
        self.recs = {}
        self._events = [self.handleAdd, self.handleDelete, self.handleUpdate]
        for e in self._events:
            self.be.registerevent(e)

    def add(self, rec):
        # check for duplicates
        match = (str(rec.chanid),rec.recstartts.isoformat())
        if match in self.recs:
            return

        # add attributes
        rec.attr = Attr()
        ctime = rec.lastmodified.timestamp()
        rec.attr.st_ctime = ctime
        rec.attr.st_mtime = ctime
        rec.attr.st_atime = ctime
        rec.attr.st_size = rec.filesize
        rec.attr.st_mode = stat.S_IFREG | 0444

        # process name
        rec.path = rec.formatPath(self.fmt, ' ')

        # add file
        self._addCallback(rec)
        self.recs[match] = rec.attr.st_ino

    def genAttr(self, rec):
        attr = Attr()
        ctime = rec.lastmodified.timestamp()
        attr.st_ctime = ctime
        attr.st_mtime = ctime
        attr.st_atime = ctime
        attr.st_size = rec.filesize
        attr.st_mode = stat.S_IFREG | 0444
        return attr

    def getAll(self):
        for rec in self.be.getRecordings():
            self.add(rec)

    def handleAdd(self, event=None):
        if event is None:
            self._reAdd = re.compile(
                    re.escape(static.BACKEND_SEP).\
                        join(['BACKEND_MESSAGE',
                              'RECORDING_LIST_CHANGE ADD '
                                  '(?P<chanid>[0-9]*) '
                                  '(?P<starttime>[0-9-]*T[0-9:]*)',
                              'empty']))
            return self._reAdd
        LOG(LOG.FILE, 'add event received', event)

        match = self._reAdd.match(event).groups()
        if match in self.recs:
            return

        rec = self.be.getRecording(match[0], match[1])
        self.add(rec)

    def handleDelete(self, event=None):
        if event is None:
            self._reDel = re.compile(
                    re.escape(static.BACKEND_SEP).\
                        join(['BACKEND_MESSAGE',
                              'RECORDING_LIST_CHANGE DELETE '
                                  '(?P<chanid>[0-9]*) '
                                  '(?P<starttime>[0-9-]*T[0-9:]*)',
                              'empty']))
            return self._reDel
        LOG(LOG.FILE, 'delete event received', event)

        match = self._reDel.match(event).groups()
        if match not in self.recs:
            return

        self._deleteCallback(self.recs[match])
        del self.recs[match]

    def handleUpdate(self, event=None):
        if event is None:
            self._reUp = re.compile(
                    re.escape(static.BACKEND_SEP).\
                        join(['BACKEND_MESSAGE',
                              'UPDATE_FILE_SIZE '
                                  '(?P<chanid>[0-9]*) '
                                  '(?P<starttime>[0-9-]*T[0-9:]*) '
                                  '(?P<size>[0-9]*)',
                              'empty']))
            return self._reUp
        LOG(LOG.FILE, 'update event received', event)

        match = self._reUp.match(event)
        size = match.group(3)
        match = match.group(1,2)
        if match not in self.recs:
            return

        inode = self.recs[match]
        rec = self._inodeCallback(inode)
        rec.filesize = int(size)
        rec.attr.st_size = int(size)

    def setFormat(self, fmt):
        if '%' not in fmt:
            LOG(LOG.FILE, 'pulling format from database', 'mythfs.format.%s' % fmt)
            fmt = self.be.db.settings.NULL['mythfs.format.%s' % fmt]
        LOG(LOG.FILE, 'using format', fmt)
        self.fmt = fmt
Beispiel #36
0
class MythTVScreen(Screen):
    """Main screen class for MythTV schedule.

       Screen attempts to connect to MythTV backend and retrieve list of
       upcoming recordings and display this.

       Data is cached so that information can still be viewed even if backend
       is offline (e.g. for power saving purposes).
    """
    backendonline = BooleanProperty(False)
    isrecording = BooleanProperty(False)

    def __init__(self, **kwargs):
        super(MythTVScreen, self).__init__(**kwargs)

        # Get the path for the folder
        scr = sys.modules[self.__class__.__module__].__file__

        # Create variable to retain path to our cache fie
        self.screendir = os.path.dirname(scr)
        self.cacheFile = os.path.join(self.screendir, "cache", "cache.json")

        # Some other useful variable
        self.running = False
        self.rec_timer = None
        self.status_timer = None
        self.be = None
        self.recs = None

    def on_enter(self):
        # We only update when we enter the screen. No need for regular updates.
        self.getRecordings()
        self.drawScreen()
        self.checkRecordingStatus()

    def on_leave(self):
        pass

    def cacheRecs(self, recs):
        """Method to save local copy of recordings. Backend may not be online
           all the time so a cache enables us to display recordings if if we
           can't poll the server for an update.
        """
        with open(self.cacheFile, 'w') as outfile:
            json.dump(recs, outfile)

    def loadCache(self):
        """Retrieves cached recorings and returns as a python list object."""
        try:
            raw = open(self.cacheFile, 'r')
            recs = json.load(raw)
        except:
            recs = []

        return recs

    def recs_to_dict(self, uprecs):
        """Converts the MythTV upcoming recording iterator into a list of
           dict objects.
        """
        raw_recs = []
        recs = []

        # Turn the response into a dict object and add to our list of recorings
        for r in uprecs:
            rec = {}
            st = r.starttime
            et = r.endtime
            rec["title"] = r.title
            rec["subtitle"] = r.subtitle if r.subtitle else ""
            day = dt.datetime(st.year, st.month, st.day)
            rec["day"] = (day - EPOCH).total_seconds()
            rec["time"] = "{} - {}".format(st.strftime("%H:%M"),
                                           et.strftime("%H:%M"))
            rec["timestamp"] = (st - EPOCH).total_seconds()
            rec["desc"] = r.description
            raw_recs.append(rec)

        # Group the recordings by day (so we can print a header)
        for k, g in groupby(raw_recs, lambda x: x["day"]):
            recs.append((k, list(g)))

        return recs

    def getRecordings(self):
        """Attempts to connect to MythTV backend and retrieve recordings."""
        try:
            # If we can connect then get recordings and save a local cache.
            self.be = MythBE()
            uprecs = self.be.getUpcomingRecordings()
            self.recs = self.recs_to_dict(uprecs)
            self.cacheRecs(self.recs)
            self.backendonline = True
        except:
            # Can't connect so we need to set variables accordinly and try
            # to load data from the cache.
            self.be = None
            self.recs = self.loadCache()
            self.backendonline = False

    def checkRecordingStatus(self):
        """Checks whether the backend is currently recording."""
        try:
            recbe = MythBE()
            for recorder in recbe.getRecorderList():
                if recbe.isRecording(recorder):
                    self.isrecording = True
                    break
        except:
            # If we can't connect to it then it can't be recording.
            self.isrecording = False

    def drawScreen(self):
        """Main method for rendering screen.

        If there is recording data (live or cached) then is laid out in a
        scroll view.

        If not, the user is notified that the backend is unreachable.
        """
        sv = self.ids.myth_scroll
        sv.clear_widgets()

        if self.recs:
            # Create a child widget to hold the recordings.
            self.sl = GridLayout(cols=1, size_hint=(1, None), spacing=2)
            self.sl.bind(minimum_height=self.sl.setter('height'))

            # Loop over the list of recordings.
            for rec in self.recs:

                # These are grouped by day so we need a header
                day = dt.timedelta(0, rec[0]) + EPOCH
                mrh = MythRecordingHeader(rec_date=day.strftime("%A %d %B"))
                self.sl.add_widget(mrh)

                # Then we loop over the recordings scheduled for that day
                for r in rec[1]:
                    # and add them to the display.
                    mr = MythRecording(rec=r)
                    self.sl.add_widget(mr)

            sv.add_widget(self.sl)

        else:
            lb = Label(text="Backend is unreachable and there is no cached"
                            " information")
            sv.add_widget(lb)
Beispiel #37
0
 def __init__(self):
     self.be = MythBE()
     self.recs = {}
     self._events = [self.handleAdd, self.handleDelete, self.handleUpdate]
     for e in self._events:
         self.be.registerevent(e)
Beispiel #38
0
 def __init__(self):
     self.be = MythBE()
     self.recs = {}
     self._events = [self.handleAdd, self.handleDelete, self.handleUpdate]
     for e in self._events:
         self.be.registerevent(e)
 def delete(self):
     be = MythBE(self.hosts[0], db=self.db)
     be.deleteFile(self, self.group)
class populate(object):
    __metaclass__ = Singleton

    def __init__(self, host=None):
        self.db = MythDB()
        self.db.searchRecorded.handler = Recorded
        self.be = MythBE(db=self.db)
        self.log = MythLog(db=self.db)

        self.set_host(host)
        self.load_backends()
        self.load_storagegroups()

    def set_host(self, host):
        self.host = host
        if host:
            # if the host was defined on the command line, check
            # to make sure such host is defined in the database
            with self.db as c:
                c.execute(
                    """SELECT count(1) FROM settings
                             WHERE hostname=? AND value=?""",
                    (host, 'BackendServerIP'))
                if c.fetchone()[0] == 0:
                    raise Exception('Invalid hostname specified for backend.')

    def load_backends(self):
        with self.db as c:
            c.execute("""SELECT hostname FROM settings
                         WHERE value='BackendServerIP'""")
            hosts = [r[0] for r in c.fetchall()]
        self.hosts = []
        for host in hosts:
            # try to access all defined hosts, and
            # store the ones currently accessible
            try:
                MythBE(backend=host)
                self.hosts.append(host)
            except:
                pass

    def load_storagegroups(self):
        self.storagegroups = \
            [sg for sg in self.db.getStorageGroup() \
                if sg.groupname not in ('Videos','Banners','Coverart',\
                                        'Fanart','Screenshots','Trailers')]

    def flush(self):
        self.misplaced = []
        self.zerorecs = []
        self.pendrecs = []
        self.orphrecs = []
        self.orphvids = []
        self.orphimgs = []
        self.dbbackup = []
        self.unfiltered = []

    def __call__(self):
        self.refresh_content()
        return self

    def refresh_content(self):
        # scan through all accessible backends to
        # generate a new listof orphaned content
        self.flush()

        unfiltered = {}
        for host in self.hosts:
            for sg in self.storagegroups:
                try:
                    dirs, files, sizes = self.be.getSGList(
                        host, sg.groupname, sg.dirname)
                    for f, s in zip(files, sizes):
                        newfile = File(host, sg.groupname, sg.dirname, f, s,
                                       self.db)
                        # each filename should be unique among all storage directories
                        # defined on all backends, but may exist in the same directory
                        # on multiple backends if they are shared
                        if newfile not in unfiltered:
                            # add a new file to the list
                            unfiltered[str(newfile)] = newfile
                        else:
                            # add a reference to the host on which it was found
                            unfiltered[str(newfile)].add_host(host)
                except:
                    self.log(
                        MythLog.GENERAL, MythLog.INFO,
                        'Could not access {0.groupname}@{1}{0.dirname}'.format(
                            sg, host))

        for rec in self.db.searchRecorded(livetv=True):
            if rec.hostname not in self.hosts:
                # recording is on an offline backend, ignore it
                name = rec.basename.rsplit('.', 1)[0]
                for n in unfiltered.keys():
                    if name in n:
                        # and anything related to it
                        del unfiltered[n]
            elif rec.basename in unfiltered:
                # run through list of recordings, matching basenames
                # with found files, and removing file from list
                f = unfiltered[rec.basename]
                del unfiltered[rec.basename]
                if f.size < 1024:
                    # file is too small to be of any worth
                    self.zerorecs.append(rec)
                elif rec.doubleorphan:
                    # file is marked for deletion, but has been forgotten by the backend
                    self.pendrecs.append(rec)
                elif rec.hostname not in f.hosts:
                    # recording is in the database, but not where it should be
                    self.misplaced.append(rec)

                name = rec.basename.rsplit('.', 1)[0]
                for f in unfiltered.keys():
                    if name in f:
                        # file is related to a valid recording, ignore it
                        del unfiltered[f]
            else:
                # recording has been orphaned
                self.orphrecs.append(rec)

        for n, f in unfiltered.iteritems():
            if n.endswith('.mpg') or n.endswith('.nuv'):
                # filter files with recording extensions
                self.orphvids.append(f)
            elif n.endswith('.png'):
                # filter files with image extensions
                self.orphimgs.append(f)
            elif 'sql' in n:
                # filter for database backups
                self.dbbackup.append(f)
            else:
                self.unfiltered.append(f)

    def print_results(self):
        printrecs("Recordings found on the wrong host", self.misplaced)
        printrecs("Recordings with missing files", self.orphrecs)
        printrecs("Zero byte recordings", self.zerorecs)
        printrecs("Forgotten pending deletions", self.pendrecs)
        printfiles("Orphaned video files", self.orphvids)
        printfiles("Orphaned snapshots", self.orphimgs)
        printfiles("Database backups", self.dbbackup)
        printfiles("Other files", self.unfiltered)
Beispiel #41
0
from MythTV import MythDB, MythBE

db = None
db = MythDB()

if db == None:
  print "Database could not be open or found"
  exit()


be = None
be = MythBE()

if be == None:
  print "Backend could not be found."
  #exit()


for x in be.getRecorderList():
  print "Tuner " + str(x) + " is recording: " + str(be.getCurrentRecording(x).title)

fs = be.getFreeSpace()
print "----------------------------------"
for x in fs:
  print "Host: " + x.host
  print "\tPath: " + x.path
  print "\t\tTotal space: " + str(x.totalspace / 1024 / 1024) + "MB"
  print "\t\tFree  space: " + str(x.freespace / 1024  / 1024) + "MB"
  print "\t\tUsed  space: " + str(x.usedspace / 1024  / 1024) + "MB"
  print "----------------------------------"
 def delete(self):
     be = MythBE(self.host, db=DB)
     be.deleteFile(self, self.group)
class populate( object ):
    __metaclass__ = Singleton
    def __init__(self, host=None):
        self.db = MythDB()
        self.db.searchRecorded.handler = Recorded
        self.be = MythBE(db=self.db)
        self.log = MythLog(db=self.db)

        self.set_host(host)
        self.load_backends()
        self.load_storagegroups()

    def set_host(self, host):
        self.host = host
        if host:
            # if the host was defined on the command line, check
            # to make sure such host is defined in the database
            with self.db as c:
                c.execute("""SELECT count(1) FROM settings
                             WHERE hostname=? AND value=?""",
                            (host, 'BackendServerIP'))
                if c.fetchone()[0] == 0:
                    raise Exception('Invalid hostname specified for backend.')

    def load_backends(self):
        with self.db as c:
            c.execute("""SELECT hostname FROM settings
                         WHERE value='BackendServerIP'""")
            hosts = [r[0] for r in c.fetchall()]
        self.hosts = []
        for host in hosts:
            # try to access all defined hosts, and
            # store the ones currently accessible
            try:
                MythBE(backend=host)
                self.hosts.append(host)
            except:
                pass

    def load_storagegroups(self):
        self.storagegroups = \
            [sg for sg in self.db.getStorageGroup() \
                if sg.groupname not in ('Videos','Banners','Coverart',\
                                        'Fanart','Screenshots','Trailers')]

    def flush(self):
        self.misplaced = []
        self.zerorecs = []
        self.pendrecs = []
        self.orphrecs = []
        self.orphvids = []
        self.orphimgs = []
        self.dbbackup = []
        self.unfiltered = []

    def __call__(self):
        self.refresh_content()
        return self

    def refresh_content(self):
        # scan through all accessible backends to
        # generate a new listof orphaned content
        self.flush()

        unfiltered = {}
        for host in self.hosts:
            for sg in self.storagegroups:
                try:
                    dirs,files,sizes = self.be.getSGList(host, sg.groupname, sg.dirname)
                    for f,s in zip(files, sizes):
                        newfile = File(host, sg.groupname, sg.dirname, f, s, self.db)
                        # each filename should be unique among all storage directories
                        # defined on all backends, but may exist in the same directory
                        # on multiple backends if they are shared
                        if newfile not in unfiltered:
                            # add a new file to the list
                            unfiltered[str(newfile)] = newfile
                        else:
                            # add a reference to the host on which it was found
                            unfiltered[str(newfile)].add_host(host)
                except:
                    self.log(MythLog.GENERAL, MythLog.INFO, 
                            'Could not access {0.groupname}@{1}{0.dirname}'.format(sg, host))

        for rec in self.db.searchRecorded(livetv=True):
            if rec.hostname not in self.hosts:
                # recording is on an offline backend, ignore it
                name = rec.basename.rsplit('.',1)[0]
                for n in unfiltered.keys():
                    if name in n:
                        # and anything related to it
                        del unfiltered[n]
            elif rec.basename in unfiltered:
                # run through list of recordings, matching basenames
                # with found files, and removing file from list
                f = unfiltered[rec.basename]
                del unfiltered[rec.basename]
                if f.size < 1024:
                    # file is too small to be of any worth
                    self.zerorecs.append(rec)
                elif rec.doubleorphan:
                    # file is marked for deletion, but has been forgotten by the backend
                    self.pendrecs.append(rec)
                elif rec.hostname not in f.hosts:
                    # recording is in the database, but not where it should be
                    self.misplaced.append(rec)

                name = rec.basename.rsplit('.',1)[0]
                for f in unfiltered.keys():
                    if name in f:
                        # file is related to a valid recording, ignore it
                        del unfiltered[f]
            else:
                # recording has been orphaned
                self.orphrecs.append(rec)

        for n,f in unfiltered.iteritems():
            if n.endswith('.mpg') or n.endswith('.nuv'):
                # filter files with recording extensions
                self.orphvids.append(f)
            elif n.endswith('.png'):
                # filter files with image extensions
                self.orphimgs.append(f)
            elif 'sql' in n:
                # filter for database backups
                self.dbbackup.append(f)
            else:
                self.unfiltered.append(f)

    def print_results(self):
        printrecs("Recordings found on the wrong host", self.misplaced)
        printrecs("Recordings with missing files", self.orphrecs)
        printrecs("Zero byte recordings", self.zerorecs)
        printrecs("Forgotten pending deletions", self.pendrecs)
        printfiles("Orphaned video files", self.orphvids)
        printfiles("Orphaned snapshots", self.orphimgs)
        printfiles("Database backups", self.dbbackup)
        printfiles("Other files", self.unfiltered)
class MythTVScreen(Screen):
    """Main screen class for MythTV schedule.

       Screen attempts to connect to MythTV backend and retrieve list of
       upcoming recordings and display this.

       Data is cached so that information can still be viewed even if backend
       is offline (e.g. for power saving purposes).
    """
    backendonline = BooleanProperty(False)
    isrecording = BooleanProperty(False)

    def __init__(self, **kwargs):
        super(MythTVScreen, self).__init__(**kwargs)

        # Get the path for the folder
        scr = sys.modules[self.__class__.__module__].__file__

        # Create variable to retain path to our cache fie
        self.screendir = os.path.dirname(scr)
        self.cacheFile = os.path.join(self.screendir, "cache", "cache.json")

        # Some other useful variable
        self.running = False
        self.rec_timer = None
        self.status_timer = None
        self.be = None
        self.recs = None

    def on_enter(self):
        # We only update when we enter the screen. No need for regular updates.
        self.getRecordings()
        self.drawScreen()
        self.checkRecordingStatus()

    def on_leave(self):
        pass

    def cacheRecs(self, recs):
        """Method to save local copy of recordings. Backend may not be online
           all the time so a cache enables us to display recordings if if we
           can't poll the server for an update.
        """
        with open(self.cacheFile, 'w') as outfile:
            json.dump(recs, outfile)

    def loadCache(self):
        """Retrieves cached recorings and returns as a python list object."""
        try:
            raw = open(self.cacheFile, 'r')
            recs = json.load(raw)
        except:
            recs = []

        return recs

    def recs_to_dict(self, uprecs):
        """Converts the MythTV upcoming recording iterator into a list of
           dict objects.
        """
        raw_recs = []
        recs = []

        # Turn the response into a dict object and add to our list of recorings
        for r in uprecs:
            rec = {}
            st = r.starttime
            et = r.endtime
            rec["title"] = r.title
            rec["subtitle"] = r.subtitle if r.subtitle else ""
            day = dt.datetime(st.year, st.month, st.day)
            rec["day"] = (day - EPOCH).total_seconds()
            rec["time"] = "{} - {}".format(st.strftime("%H:%M"),
                                           et.strftime("%H:%M"))
            rec["timestamp"] = (st - EPOCH).total_seconds()
            rec["desc"] = r.description
            raw_recs.append(rec)

        # Group the recordings by day (so we can print a header)
        for k, g in groupby(raw_recs, lambda x: x["day"]):
            recs.append((k, list(g)))

        return recs

    def getRecordings(self):
        """Attempts to connect to MythTV backend and retrieve recordings."""
        try:
            # If we can connect then get recordings and save a local cache.
            self.be = MythBE()
            uprecs = self.be.getUpcomingRecordings()
            self.recs = self.recs_to_dict(uprecs)
            self.cacheRecs(self.recs)
            self.backendonline = True
        except:
            # Can't connect so we need to set variables accordinly and try
            # to load data from the cache.
            self.be = None
            self.recs = self.loadCache()
            self.backendonline = False

    def checkRecordingStatus(self):
        """Checks whether the backend is currently recording."""
        try:
            recbe = MythBE()
            for recorder in recbe.getRecorderList():
                if recbe.isRecording(recorder):
                    self.isrecording = True
                    break
        except:
            # If we can't connect to it then it can't be recording.
            self.isrecording = False

    def drawScreen(self):
        """Main method for rendering screen.

        If there is recording data (live or cached) then is laid out in a
        scroll view.

        If not, the user is notified that the backend is unreachable.
        """
        sv = self.ids.myth_scroll
        sv.clear_widgets()

        if self.recs:
            # Create a child widget to hold the recordings.
            self.sl = GridLayout(cols=1, size_hint=(1, None), spacing=2)
            self.sl.bind(minimum_height=self.sl.setter('height'))

            # Loop over the list of recordings.
            for rec in self.recs:

                # These are grouped by day so we need a header
                day = dt.timedelta(0, rec[0]) + EPOCH
                mrh = MythRecordingHeader(rec_date=day.strftime("%A %d %B"))
                self.sl.add_widget(mrh)

                # Then we loop over the recordings scheduled for that day
                for r in rec[1]:
                    # and add them to the display.
                    mr = MythRecording(rec=r)
                    self.sl.add_widget(mr)

            sv.add_widget(self.sl)

        else:
            lb = Label(text="Backend is unreachable and there is no cached"
                       " information")
            sv.add_widget(lb)
Beispiel #45
0
                    res = int(res)
                except:
                    res = input('input number. ctrl-c to exit > ')
                    continue
                if (res <= 0) or (res > len(opts)):
                    res = input('input number within range > ')
                    continue
                break
            opt = opts[res - 1]
            if opt[1] is None:
                continue
            else:
                opt[1](opt[2])

        except KeyboardInterrupt:
            break
        except EOFError:
            sys.exit(0)


DB = MythDB()
BE = MythBE(db=DB)
DB.searchRecorded.handler = MyRecorded
DB.searchRecorded.dbclass = MyRecorded

if __name__ == '__main__':
    if len(sys.argv) == 2:
        main(sys.argv[1])
    else:
        main()
Beispiel #46
0
    def showScreen(self):
        self.surface.fill([0,0,0])

        if self.be == None:
            try:
                recs = []
                self.be = MythBE()
                self.upcomingrecs = self.be.getUpcomingRecordings()
                for r in self.upcomingrecs:
                    rec = {}
                    rec["title"] = r.title
                    rec["subtitle"] = r.subtitle
                    rec["time"] = r.starttime.strftime("%a %d %b %H:%M")
                    rec["desc"] = r.description
                    recs.append(rec)
                recorders = MythBE().getRecorderList()
                for recorder in recorders:
                    if MythBE().isRecording(recorder):
                        self.isrecording = True
                        break
                self.backendfail = False
                self.cachedmode = False
            except:
                self.backendfail = True
        
        if self.backendfail:
            recs = self.loadCache()
            if recs: 
                self.backendfail = False
                self.cachedmode = True
        
        if not self.backendfail:
            
            self.cacheRecs(recs)
            
            screentitle = self.mytitlefont.render("MythTV upcoming recordings",1,(255,255,255))
            screenrect = screentitle.get_rect()
            screenrect.centerx = self.surface.get_rect().centerx
            screenrect.centery = 20
            self.surface.blit(screentitle, screenrect)
            
            n = min(len(recs),5)
            if n > 0:
                for i in range(n):
                    mytitlerect = pygame.Rect((0,0,self.rectwidth,60))
                    mytimerect = pygame.Rect((0,0,self.rectwidth,30))
                    mydescrect = pygame.Rect((0,0,self.rectwidth,330))
                    fontcolour = (255,255,255)
                    rectcolour = (0,50,75)
                    titletext = self.render_textrect(recs[i]["title"], self.myboldfont, mytitlerect, fontcolour, rectcolour,1)
                    timetext = self.render_textrect(recs[i]["time"], self.myitalicfont, mytimerect, fontcolour, rectcolour,1)
                    desctext = self.render_textrect(recs[i]["desc"], self.myregularfont, mydescrect, fontcolour, rectcolour,0,margin=5)
                    self.surface.blit(titletext,((self.rectwidth*i)+(self.rectgap*(i+1)+self.rectadjust), 40))
                    self.surface.blit(timetext,((self.rectwidth*i)+(self.rectgap*(i+1)+self.rectadjust), 80))
                    self.surface.blit(desctext,((self.rectwidth*i)+(self.rectgap*(i+1)+self.rectadjust), 105))
                    
                if self.cachedmode:
                    mystatus = self.myitalicfont.render("Backend is offline. Displaying cached recording list",1,(255,255,255))
                else:
                    if self.isrecording:
                        recording = "currently"
                    else:
                        recording = "not"
                    mystatus = self.myitalicfont.render("Backend is online and is " + recording + " recording.",1,(255,255,255))
                    
                self.surface.blit(mystatus,(5,445))
            else:
                failtext = self.myboldfont.render("No upcoming recordings found.",1, (255,255,255))
                failrect = failtext.get_rect()
                failrect.centerx = self.surface.get_rect().centerx
                failrect.centery = self.surface.get_rect().centery
                self.surface.blit(failtext, failrect)                
            
        else:
            failtext = self.myboldfont.render("MythTV backend unavailable.",1, (255,255,255))
            failrect = failtext.get_rect()
            failrect.centerx = self.surface.get_rect().centerx
            failrect.centery = self.surface.get_rect().centery
            self.surface.blit(failtext, failrect)
        
        self.be = None

        # Scale our surface to the required screensize before sending back
        scaled = pygame.transform.scale(self.surface,self.screensize)
        self.screen.blit(scaled,(0,0))

        return self.screen
def main():
    parser = OptionParser(usage="usage: option [option] [option]")

    maintenancegroup = OptionGroup(
        parser, "Maintenance",
        "These options can be used to perform DB cleanup.")
    maintenancegroup.add_option(
        "--dedup",
        action="store_true",
        default=False,
        dest="dedup",
        help="checks for duplicate entries in the Video database")
    maintenancegroup.add_option("--check_orphans",
                                action="store_true",
                                default=False,
                                dest="check_orphans",
                                help="checks for orphaned DB entries in Video")
    parser.add_option_group(maintenancegroup)

    actiongroup = OptionGroup(parser, "Meta Updates",
                              "This option updates Video Meta Data")
    actiongroup.add_option('--update',
                           action='store_true',
                           default=False,
                           dest='update',
                           help='Updates the video meta data on each entry')
    parser.add_option_group(actiongroup)

    othergroup = OptionGroup(parser, 'Other Options',
                             "These options allow for additional controls")
    othergroup.add_option(
        '--folder',
        action='store',
        type='string',
        dest='folder',
        help='Limits actions to Videos in a specified folder,\
                                  example "Movies" would limit to any filename \
                                  starting with Movies/some_video_file.mpg')
    othergroup.add_option(
        '--step',
        action="store_true",
        default=False,
        dest='step',
        help='Steps through each action to allow evaluation of \
                                  process')
    parser.add_option_group(othergroup)

    opts, args = parser.parse_args()

    # if a manual channel and time entry then setup the export with opts

    # sys.exit(1)

    # setup the connection to the DB
    db = MythDB()
    be = MythBE()

    #Setup a scanner for all videos in the DB
    videos = db.searchVideos()

    # setup a Video object to work with

    def get_Meta(item):

        metadata = item.exportMetadata()

        if not item.filename.startswith('Television'):
            grab = VideoGrabber('Movie')
            if item.get('inetref') != '00000000':
                try:
                    match = grab.grabInetref(item.inetref)
                    item.importMetadata(match)
                    item.plot = match.get('description')
                    item.title = match.get('title')
                    copy_Art(match, item)
                    item.update()
                    return

                except Exception:
                    print 'grabber failed for: ' + str(item.get('inetref'))
                    print 'trying by name instead'

            try:
                results = grab.sortedSearch(item.title)
            except Exception, e:
                print 'grabber failed for: ' + str(item.get('inetref'))
                print e
                return

            if len(results) > 0:
                if len(results) > 1:
                    menu = {}
                    list = 1
                    for each in results:
                        menu[list]= each.title + ', year: ' + str(each.get('year')) \
                                                + ', inetref: ' + str(each.get('inetref'))
                        list = list + 1
                    menu[list] = 'Skip to next video\n\n'
                    print '\n'
                    while True:
                        options = menu.keys()
                        options.sort()
                        for entry in options:
                            print entry, menu[entry]
                        try:
                            selection = input("Please Select: ")
                            if selection in range(1, len(results) + 1):
                                listing = results[selection - 1]
                                break
                            elif selection == len(results) + 1:
                                return
                            else:
                                print "Invalid Selection, try again!\n\n"
                        except Exception:
                            print "Invalid Selection, try again!\n\n"
                else:
                    listing = results[0]

                try:
                    match = grab.grabInetref(listing.get('inetref'))
                    item.importMetadata(match)
                    item.plot = match.get('description')
                    item.title = match.get('title')
                    copy_Art(match, item)
                    item.update()
                    print 'Full MetaData Import complete for: ' + item.title + '\n'

                except Exception, e:
                    print 'grabber failed for: ' + str(item.get('inetref'))
                    print e

            elif len(results) == 0:
                print 'No MetaData to import for: ' + item.title + '\n'
Beispiel #48
0
#   files from TitanTV, importing them as scheduling rules for MythTV to
#   record
#---------------------------

__title__ = "TitanImport"
__author__ = "Raymond Wagner"
__version__ = "v0.1.0"

from MythTV import MythDB, MythBE, Channel, Record, datetime
from datetime import timedelta
from optparse import OptionParser

import lxml.etree as etree

DB = MythDB()
BE = MythBE(db=DB)
tzoff = timedelta(0,
                  int(BE.backendCommand('QUERY_TIME_ZONE').split('[]:[]')[1]))


def FindProgram(xmlprog, fuzzy):
    tvmode = xmlprog.find('tv-mode').text
    chan = None
    if tvmode == 'cable':
        # for cable, require a match of channel and station name
        for c in Channel.getAllEntries(db=DB):
            if c.freqid == xmlprog.find('rf-channel').text and \
               c.mplexid == 32767 and \
               c.callsign == xmlprog.find('station').text:
                chan = c
                break
Beispiel #49
0
 def setUp(self):
     with add_log_flags():
         self.mydb = MythDB()
         self.mybe = MythBE(db=self.mydb)
Beispiel #50
0
class VIDEO:
    def __init__(self, opts, jobid=None):

        # Setup for the job to run
        if jobid:
            self.thisJob = Job(jobid)
            self.chanID = self.thisJob.chanid
            self.startTime = self.thisJob.starttime
            self.thisJob.update(status=Job.STARTING)

        # If no job ID given, must be a command line run
        else:
            self.thisJob = jobid
            self.chanID = opts.chanid
            self.startTime = opts.startdate + " " + opts.starttime + opts.offset
        self.opts = opts
        self.type = "none"
        self.db = MythDB()
        self.log = MythLog(module='Myth-Rec-to-Vid.py', db=self.db)

        # Capture the backend host name
        self.host = self.db.gethostname()

        # prep objects
        self.rec = Recorded((self.chanID, self.startTime), db=self.db)
        self.log(
            MythLog.GENERAL, MythLog.INFO, 'Using recording',
            '%s - %s' % (self.rec.title.encode('utf-8'),
                         self.rec.subtitle.encode('utf-8')))

        self.vid = Video(db=self.db).create({
            'title': '',
            'filename': '',
            'host': self.host
        })

        self.bend = MythBE(db=self.db)

    def check_hash(self):
        self.log(self.log.GENERAL, self.log.INFO,
                 'Performing copy validation.')
        srchash = self.bend.getHash(self.rec.basename, self.rec.storagegroup)
        dsthash = self.bend.getHash(self.vid.filename, 'Videos')
        if srchash != dsthash:
            return False
        else:
            return True

    def copy(self):
        stime = time.time()
        srcsize = self.rec.filesize
        htime = [stime, stime, stime, stime]

        self.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "Copying myth://%s@%s/%s"\
               % (self.rec.storagegroup, self.rec.hostname, self.rec.basename)\
                                                    +" to myth://Videos@%s/%s"\
                                          % (self.host, self.vid.filename))

        srcfp = self.rec.open('r')
        dstfp = self.vid.open('w')

        if self.thisJob:
            self.set_job_status(Job.RUNNING)
        tsize = 2**24
        while tsize == 2**24:
            tsize = min(tsize, srcsize - dstfp.tell())
            dstfp.write(srcfp.read(tsize))
            htime.append(time.time())
            rate = float(tsize * 4) / (time.time() - htime.pop(0))
            remt = (srcsize - dstfp.tell()) / rate
            if self.thisJob:
                self.thisJob.setComment("%02d%% complete - %d seconds remaining" %\
                                      (dstfp.tell()*100/srcsize, remt))
        srcfp.close()
        dstfp.close()

        self.vid.hash = self.vid.getHash()

        self.log(MythLog.GENERAL | MythLog.FILE, MythLog.INFO,
                 "Transfer Complete",
                 "%d seconds elapsed" % int(time.time() - stime))

        if self.thisJob:
            self.thisJob.setComment("Complete - %d seconds elapsed" % \
               (int(time.time()-stime)))

    def copy_markup(self, start, stop):
        for mark in self.rec.markup:
            if mark.type in (start, stop):
                self.vid.markup.add(mark.mark, 0, mark.type)

    def copy_seek(self):
        for seek in self.rec.seek:
            self.vid.markup.add(seek.mark, seek.offset, seek.type)

    def delete_vid(self):
        self.vid.delete()

    def delete_rec(self):
        self.rec.delete()

    def dup_check(self):
        self.log(MythLog.GENERAL, MythLog.INFO, 'Processing new file name ',
                 '%s' % (self.vid.filename))
        self.log(
            MythLog.GENERAL, MythLog.INFO, 'Checking for duplication of ',
            '%s - %s' % (self.rec.title.encode('utf-8'),
                         self.rec.subtitle.encode('utf-8')))
        if self.bend.fileExists(self.vid.filename, 'Videos'):
            self.log(MythLog.GENERAL, MythLog.INFO,
                     'Recording already exists in Myth Videos')
            if self.thisJob:
                self.thisJob.setComment(
                    "Action would result in duplicate entry")
            return True

        else:
            self.log(
                MythLog.GENERAL, MythLog.INFO, 'No duplication found for ',
                '%s - %s' % (self.rec.title.encode('utf-8'),
                             self.rec.subtitle.encode('utf-8')))
            return False

    def get_dest(self):
        if self.type == 'TV':
            self.vid.filename = self.process_fmt(TVFMT)
        elif self.type == 'MOVIE':
            self.vid.filename = self.process_fmt(MVFMT)

        self.vid.markup._refdat = (self.vid.filename, )

    def get_meta(self):
        import_info = 'Listing only MetaData import complete'
        metadata = self.rec.exportMetadata()
        yrInfo = self.rec.getProgram()
        metadata['year'] = yrInfo.get('year')
        self.vid.importMetadata(metadata)
        if self.type == 'MOVIE':
            grab = VideoGrabber('Movie')
            results = grab.sortedSearch(self.rec.title)
            if len(results) > 0:
                for i in results:
                    if i.year == yrInfo.get(
                            'year') and i.title == self.rec.get('title'):
                        self.vid.importMetadata(i)
                        match = grab.grabInetref(i.get('inetref'))
                        length = len(match.people)
                        for p in range(length - 2):
                            self.vid.cast.add(match.people[p].get('name'))
                        self.vid.director = match.people[length -
                                                         1].get('name')
                        import_info = 'Full MetaData Import complete'
        else:
            grab = VideoGrabber('TV')
            results = grab.sortedSearch(self.rec.title, self.rec.subtitle)
            if len(results) > 0:
                for i in results:
                    if i.title == self.rec.get(
                            'title') and i.subtitle == self.rec.get(
                                'subtitle'):
                        self.vid.importMetadata(i)
                        match = grab.grabInetref(grab.grabInetref(i.get('inetref'), \
                                season=i.get('season'),episode=i.get('episode')))
                        length = len(match.people)
                        for p in range(length - 2):
                            self.vid.cast.add(match.people[p].get('name'))
                        self.vid.director = match.people[length -
                                                         1].get('name')
                        import_info = 'Full MetaData Import complete'

        self.vid.category = self.rec.get('category')

        self.log(self.log.GENERAL, self.log.INFO, import_info)

    def get_type(self):
        if self.rec.seriesid != None and self.rec.programid[:2] != 'MV':
            self.type = 'TV'
            self.log(self.log.GENERAL, self.log.INFO,
                     'Performing TV type migration.')
        else:
            self.type = 'MOVIE'
            self.log(self.log.GENERAL, self.log.INFO,
                     'Performing Movie type migration.')

    def process_fmt(self, fmt):
        # replace fields from viddata

        ext = '.' + self.rec.basename.rsplit('.', 1)[1]
        rep = (('%TITLE%', 'title', '%s'), ('%SUBTITLE%', 'subtitle', '%s'),
               ('%SEASON%', 'season', '%d'), ('%SEASONPAD%', 'season', '%02d'),
               ('%EPISODE%', 'episode', '%d'), ('%EPISODEPAD%', 'episode',
                                                '%02d'),
               ('%YEAR%', 'year', '%s'), ('%DIRECTOR%', 'director', '%s'))
        for tag, data, format in rep:
            if self.vid[data]:
                fmt = fmt.replace(tag, format % self.vid[data])
            else:
                fmt = fmt.replace(tag, '')

        # replace fields from program data
        rep = (('%HOSTNAME%', 'hostname', '%s'), ('%STORAGEGROUP%',
                                                  'storagegroup', '%s'))
        for tag, data, format in rep:
            data = getattr(self.rec, data)
            fmt = fmt.replace(tag, format % data)

        if len(self.vid.genre):
            fmt = fmt.replace('%GENRE%', self.vid.genre[0].genre)
        else:
            fmt = fmt.replace('%GENRE%', '')
        return fmt + ext

    def set_job_status(self, status):
        self.thisJob.setStatus(status)

    def set_vid_hash(self):
        self.vid.hash = self.vid.getHash()

    def update_vid(self):
        self.vid.update()
Beispiel #51
0
        # but in new ones: 
        if marker in seen: continue 
        seen[marker] = 1 
        result.append(item) 
    return result

def pendFun(x):
	out = x.title + "-"
	if x.subtitle is not None:
		out += x.subtitle
	return out

def goodRec(x):
	return x.recgroup <> 'Sports' and x.subtitle is not None and x.title not in titlesIgnore and (x.recstatus == Program.rsWillRecord or x.recstatus == Program.rsConflict) 

myth = MythBE()
mythDB = MythDB()
pending = uniq(filter(goodRec, myth.getPendingRecordings()), pendFun)

for p in sorted(pending, key=pendFun):
#	print p.title
	olds = list(mythDB.searchOldRecorded(title=p.title, subtitle=p.subtitle, recstatus=-3, duplicate=1))
#	olds = filter(lambda x: x.programid == "", olds)
	if len(olds) > 0:
		print "%s %s (%s) %s" % (p.title, p.subtitle, p.starttime, p.recstatus)
		for o in olds:
			if o.programid == "":
				print "\tNo Old Program Id"

			if o.programid <> "" and o.programid <> p.programid:
#			if o.programid <> p.programid:
Beispiel #52
0
	src = get_dir(filename)

	if not src:
		print "Can't find src for %s" % movie.title
		return

	dst = d + filename
	if not simulate:
		print "before: %s" % datetime.now()

	print "%-20.20s: moving from %s to %s" % (movie.title,src, dst)

	if not simulate:
		shutil.move(src,dst)
		print "after: %s" % datetime.now()


myth = MythBE()
input = ""
progs = filter(filterMovies, myth.getRecordings())
progs = sorted(progs,cmpFileSize)

totalSize = 0
for i,p in enumerate(progs):
	totalSize += p.filesize
	print "%2d: %-35.35s (%-5s) %s" % (i,p.title,pretty_filesize(p.filesize),get_dir(p.filename.split("/")[3]))
print "Total size: %s" % pretty_filesize(totalSize)
input = raw_input("Enter some numbers or 'q' : ")
moveMovies(getIds(input),progs)
Beispiel #53
0
def getBeObject(databaseObj=getDbObject()):
    return MythBE(db=databaseObj)
Beispiel #54
0
class Recordings(Handler):
    def __init__(self):
        self.be = MythBE()
        self.recs = {}
        self._events = [self.handleAdd, self.handleDelete, self.handleUpdate]
        for e in self._events:
            self.be.registerevent(e)

    def add(self, rec):
        # check for duplicates
        match = (str(rec.chanid), rec.recstartts.isoformat())
        if match in self.recs:
            return

        # add attributes
        rec.attr = Attr()
        ctime = rec.lastmodified.timestamp()
        rec.attr.st_ctime = ctime
        rec.attr.st_mtime = ctime
        rec.attr.st_atime = ctime
        rec.attr.st_size = rec.filesize
        rec.attr.st_mode = stat.S_IFREG | 0444

        # process name
        rec.path = rec.formatPath(self.fmt, ' ')

        # add file
        self._addCallback(rec)
        self.recs[match] = rec.attr.st_ino

    def genAttr(self, rec):
        attr = Attr()
        ctime = rec.lastmodified.timestamp()
        attr.st_ctime = ctime
        attr.st_mtime = ctime
        attr.st_atime = ctime
        attr.st_size = rec.filesize
        attr.st_mode = stat.S_IFREG | 0444
        return attr

    def getAll(self):
        for rec in self.be.getRecordings():
            self.add(rec)

    def handleAdd(self, event=None):
        if event is None:
            self._reAdd = re.compile(
                    re.escape(static.BACKEND_SEP).\
                        join(['BACKEND_MESSAGE',
                              'RECORDING_LIST_CHANGE ADD '
                                  '(?P<chanid>[0-9]*) '
                                  '(?P<starttime>[0-9-]*T[0-9:]*)',
                              'empty']))
            return self._reAdd
        LOG(LOG.FILE, 'add event received', event)

        match = self._reAdd.match(event).groups()
        if match in self.recs:
            return

        rec = self.be.getRecording(match[0], match[1])
        self.add(rec)

    def handleDelete(self, event=None):
        if event is None:
            self._reDel = re.compile(
                    re.escape(static.BACKEND_SEP).\
                        join(['BACKEND_MESSAGE',
                              'RECORDING_LIST_CHANGE DELETE '
                                  '(?P<chanid>[0-9]*) '
                                  '(?P<starttime>[0-9-]*T[0-9:]*)',
                              'empty']))
            return self._reDel
        LOG(LOG.FILE, 'delete event received', event)

        match = self._reDel.match(event).groups()
        if match not in self.recs:
            return

        self._deleteCallback(self.recs[match])
        del self.recs[match]

    def handleUpdate(self, event=None):
        if event is None:
            self._reUp = re.compile(
                    re.escape(static.BACKEND_SEP).\
                        join(['BACKEND_MESSAGE',
                              'UPDATE_FILE_SIZE '
                                  '(?P<chanid>[0-9]*) '
                                  '(?P<starttime>[0-9-]*T[0-9:]*) '
                                  '(?P<size>[0-9]*)',
                              'empty']))
            return self._reUp
        LOG(LOG.FILE, 'update event received', event)

        match = self._reUp.match(event)
        size = match.group(3)
        match = match.group(1, 2)
        if match not in self.recs:
            return

        inode = self.recs[match]
        rec = self._inodeCallback(inode)
        rec.filesize = int(size)
        rec.attr.st_size = int(size)

    def setFormat(self, fmt):
        if '%' not in fmt:
            LOG(LOG.FILE, 'pulling format from database',
                'mythfs.format.%s' % fmt)
            fmt = self.be.db.settings.NULL['mythfs.format.%s' % fmt]
        LOG(LOG.FILE, 'using format', fmt)
        self.fmt = fmt
Beispiel #55
0
class myScreen(PiInfoScreen):
    refreshtime = 60
    displaytime = 10
    pluginname = "MythTV"
    plugininfo = "Displays details of upcoming recordings"
    supportedsizes = [ (694, 466) ]
    
    def setPluginVariables(self):
        self.be = None
        self.backendfail = False
        self.cachedmode = False
        self.isrecording = False
        self.regularfont = os.path.join(self.plugindir, "resources", "ArchivoNarrow-Regular.otf") 
        self.italicfont = os.path.join(self.plugindir, "resources", "ArchivoNarrow-Italic.otf") 
        self.boldfont = os.path.join(self.plugindir, "resources", "ArchivoNarrow-Bold.otf") 
        self.cacheFile = os.path.join(self.plugindir, "resources", "cachedRecordings.json")
        self.mytitlefont = pygame.font.Font(self.boldfont, 24)
        self.myboldfont = pygame.font.Font(self.boldfont, 20)
        self.myregularfont = pygame.font.Font(self.regularfont, 16)
        self.myitalicfont = pygame.font.Font(self.italicfont, 16)
        self.rectwidth = 132
        self.rectgap = 5
        self.rectadjust = 2
    
    def cacheRecs(self, recs):
        with open(self.cacheFile, 'w') as outfile:
            json.dump(recs, outfile)   
    
    def loadCache(self):
        try:
            raw = open(self.cacheFile, 'r')
            recs = json.load(raw)
        except:
            recs = []
        
        return recs

    def showScreen(self):
        self.surface.fill([0,0,0])

        if self.be == None:
            try:
                recs = []
                self.be = MythBE()
                self.upcomingrecs = self.be.getUpcomingRecordings()
                for r in self.upcomingrecs:
                    rec = {}
                    rec["title"] = r.title
                    rec["subtitle"] = r.subtitle
                    rec["time"] = r.starttime.strftime("%a %d %b %H:%M")
                    rec["desc"] = r.description
                    recs.append(rec)
                recorders = MythBE().getRecorderList()
                for recorder in recorders:
                    if MythBE().isRecording(recorder):
                        self.isrecording = True
                        break
                self.backendfail = False
                self.cachedmode = False
            except:
                self.backendfail = True
        
        if self.backendfail:
            recs = self.loadCache()
            if recs: 
                self.backendfail = False
                self.cachedmode = True
        
        if not self.backendfail:
            
            self.cacheRecs(recs)
            
            screentitle = self.mytitlefont.render("MythTV upcoming recordings",1,(255,255,255))
            screenrect = screentitle.get_rect()
            screenrect.centerx = self.surface.get_rect().centerx
            screenrect.centery = 20
            self.surface.blit(screentitle, screenrect)
            
            n = min(len(recs),5)
            if n > 0:
                for i in range(n):
                    mytitlerect = pygame.Rect((0,0,self.rectwidth,60))
                    mytimerect = pygame.Rect((0,0,self.rectwidth,30))
                    mydescrect = pygame.Rect((0,0,self.rectwidth,330))
                    fontcolour = (255,255,255)
                    rectcolour = (0,50,75)
                    titletext = self.render_textrect(recs[i]["title"], self.myboldfont, mytitlerect, fontcolour, rectcolour,1)
                    timetext = self.render_textrect(recs[i]["time"], self.myitalicfont, mytimerect, fontcolour, rectcolour,1)
                    desctext = self.render_textrect(recs[i]["desc"], self.myregularfont, mydescrect, fontcolour, rectcolour,0,margin=5)
                    self.surface.blit(titletext,((self.rectwidth*i)+(self.rectgap*(i+1)+self.rectadjust), 40))
                    self.surface.blit(timetext,((self.rectwidth*i)+(self.rectgap*(i+1)+self.rectadjust), 80))
                    self.surface.blit(desctext,((self.rectwidth*i)+(self.rectgap*(i+1)+self.rectadjust), 105))
                    
                if self.cachedmode:
                    mystatus = self.myitalicfont.render("Backend is offline. Displaying cached recording list",1,(255,255,255))
                else:
                    if self.isrecording:
                        recording = "currently"
                    else:
                        recording = "not"
                    mystatus = self.myitalicfont.render("Backend is online and is " + recording + " recording.",1,(255,255,255))
                    
                self.surface.blit(mystatus,(5,445))
            else:
                failtext = self.myboldfont.render("No upcoming recordings found.",1, (255,255,255))
                failrect = failtext.get_rect()
                failrect.centerx = self.surface.get_rect().centerx
                failrect.centery = self.surface.get_rect().centery
                self.surface.blit(failtext, failrect)                
            
        else:
            failtext = self.myboldfont.render("MythTV backend unavailable.",1, (255,255,255))
            failrect = failtext.get_rect()
            failrect.centerx = self.surface.get_rect().centerx
            failrect.centery = self.surface.get_rect().centery
            self.surface.blit(failtext, failrect)
        
        self.be = None

        # Scale our surface to the required screensize before sending back
        scaled = pygame.transform.scale(self.surface,self.screensize)
        self.screen.blit(scaled,(0,0))

        return self.screen
#   files from TitanTV, importing them as scheduling rules for MythTV to
#   record
#---------------------------

__title__  = "TitanImport"
__author__ = "Raymond Wagner"
__version__= "v0.1.0"

from MythTV import MythDB, MythBE, Channel, Record, datetime
from datetime import timedelta
from optparse import OptionParser

import lxml.etree as etree

DB = MythDB()
BE = MythBE(db=DB)
tzoff = timedelta(0, int(BE.backendCommand('QUERY_TIME_ZONE').split('[]:[]')[1]))

def FindProgram(xmlprog, fuzzy):
    tvmode = xmlprog.find('tv-mode').text
    chan = None
    if tvmode == 'cable':
        # for cable, require a match of channel and station name
        for c in Channel.getAllEntries(db=DB):
            if c.freqid == xmlprog.find('rf-channel').text and \
               c.mplexid == 32767 and \
               c.callsign == xmlprog.find('station').text:
                chan = c
                break
        else:
            if not fuzzy:
Beispiel #57
0
    sendMessage("Could not find mythbackend process!")
    exit(-1)

db = None
try:
    db = MythDB()
except:
    db = None

if db == None:
    sendMessage("Database could not be open or found")
    exit(-1)

be = None
try:
    be = MythBE()
except:
    be = None

if be == None:
    sendMessage("Backend could not be found")
    exit(-1)

if len(be.getRecorderList()) != 2:
    sendMessage("Tuner list is not what we expected")
    exit(-1)

#if strftime("%S") == str('00') || strftime("%S") == str('30'):
#  exit(0)

for x in be.getRecorderList():
class VIDEO:
    def __init__(self, opts, jobid=None):                           
        
        # Setup for the job to run
        if jobid:
            self.thisJob = Job(jobid)
            self.chanID = self.thisJob.chanid
            self.startTime = self.thisJob.starttime
            self.thisJob.update(status=Job.STARTING)
        
        # If no job ID given, must be a command line run
        else:
            self.thisJob = jobid
            self.chanID = opts.chanid
            self.startTime = opts.startdate + " " + opts.starttime + opts.offset
        self.opts = opts
        self.type = "none"
        self.db = MythDB()
        self.log = MythLog(module='Myth-Rec-to-Vid.py', db=self.db)
        

        # Capture the backend host name
        self.host = self.db.gethostname()

        # prep objects
        self.rec = Recorded((self.chanID,self.startTime), db=self.db)
        self.log(MythLog.GENERAL, MythLog.INFO, 'Using recording',
                        '%s - %s' % (self.rec.title.encode('utf-8'), 
                                     self.rec.subtitle.encode('utf-8')))

        self.vid = Video(db=self.db).create({'title':'', 'filename':'',
                                             'host':self.host})

        self.bend = MythBE(db=self.db)
        
        
    def check_hash(self):
        self.log(self.log.GENERAL, self.log.INFO,
                 'Performing copy validation.')
        srchash = self.bend.getHash(self.rec.basename, self.rec.storagegroup)
        dsthash = self.bend.getHash(self.vid.filename, 'Videos')
        if srchash != dsthash:
            return False
        else:
            return True
               
    def copy(self):
        stime = time.time()
        srcsize = self.rec.filesize
        htime = [stime,stime,stime,stime]

        self.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "Copying myth://%s@%s/%s"\
               % (self.rec.storagegroup, self.rec.hostname, self.rec.basename)\
                                                    +" to myth://Videos@%s/%s"\
                                          % (self.host, self.vid.filename))
        
 
        srcfp = self.rec.open('r')
        dstfp = self.vid.open('w')

        if self.thisJob:
            self.set_job_status(Job.RUNNING)
        tsize = 2**24
        while tsize == 2**24:
            tsize = min(tsize, srcsize - dstfp.tell())
            dstfp.write(srcfp.read(tsize))
            htime.append(time.time())
            rate = float(tsize*4)/(time.time()-htime.pop(0))
            remt = (srcsize-dstfp.tell())/rate
            if self.thisJob:
                self.thisJob.setComment("%02d%% complete - %d seconds remaining" %\
                                      (dstfp.tell()*100/srcsize, remt))
        srcfp.close()
        dstfp.close()
        
        self.vid.hash = self.vid.getHash()
        
        self.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "Transfer Complete",
        			      "%d seconds elapsed" % int(time.time()-stime))

        if self.thisJob:
            self.thisJob.setComment("Complete - %d seconds elapsed" % \
        	      (int(time.time()-stime)))

    def copy_markup(self, start, stop):
        for mark in self.rec.markup:
            if mark.type in (start, stop):
                self.vid.markup.add(mark.mark, 0, mark.type)

    def copy_seek(self):
        for seek in self.rec.seek:
            self.vid.markup.add(seek.mark, seek.offset, seek.type)
                
    def delete_vid(self):
        self.vid.delete()
        
    def delete_rec(self):
        self.rec.delete()
        
    def dup_check(self):
        self.log(MythLog.GENERAL, MythLog.INFO, 'Processing new file name ',
                    '%s' % (self.vid.filename))
        self.log(MythLog.GENERAL, MythLog.INFO, 'Checking for duplication of ',
                    '%s - %s' % (self.rec.title.encode('utf-8'), 
                                 self.rec.subtitle.encode('utf-8')))
        if self.bend.fileExists(self.vid.filename, 'Videos'):
            self.log(MythLog.GENERAL, MythLog.INFO, 'Recording already exists in Myth Videos')
            if self.thisJob:
                self.thisJob.setComment("Action would result in duplicate entry" )
            return True
          
        else:
            self.log(MythLog.GENERAL, MythLog.INFO, 'No duplication found for ',
                    '%s - %s' % (self.rec.title.encode('utf-8'), 
                                 self.rec.subtitle.encode('utf-8')))
            return False 

    def get_dest(self):
        if self.type == 'TV':
            self.vid.filename = self.process_fmt(TVFMT)
        elif self.type == 'MOVIE':
            self.vid.filename = self.process_fmt(MVFMT)
        
        self.vid.markup._refdat = (self.vid.filename,)

    def get_meta(self):
        import_info = 'Listing only MetaData import complete'
        metadata = self.rec.exportMetadata()
        yrInfo = self.rec.getProgram()
        metadata['year'] = yrInfo.get('year')
        self.vid.importMetadata(metadata)
        if self.type == 'MOVIE':
            grab = VideoGrabber('Movie')
            results = grab.sortedSearch(self.rec.title)
            if len(results) > 0:
                for i in results:
                    if i.year == yrInfo.get('year') and i.title == self.rec.get('title'):
                        self.vid.importMetadata(i)
                        match = grab.grabInetref(i.get('inetref'))
                        length = len(match.people)
                        for p in range(length-2):
                            self.vid.cast.add(match.people[p].get('name'))
                        self.vid.director = match.people[length - 1].get('name')
                        import_info = 'Full MetaData Import complete'
        else:
            grab = VideoGrabber('TV')
            results = grab.sortedSearch(self.rec.title, self.rec.subtitle)
            if len(results) > 0:
                for i in results:
                    if  i.title == self.rec.get('title') and i.subtitle == self.rec.get('subtitle'):
                        self.vid.importMetadata(i)
                        match = grab.grabInetref(grab.grabInetref(i.get('inetref'), \
                                season=i.get('season'),episode=i.get('episode')))
                        length = len(match.people)
                        for p in range(length-2):
                            self.vid.cast.add(match.people[p].get('name'))
                        self.vid.director = match.people[length - 1].get('name')
                        import_info = 'Full MetaData Import complete'
            
        
        self.vid.category = self.rec.get('category')

        self.log(self.log.GENERAL, self.log.INFO, import_info)

    def get_type(self):
        if self.rec.seriesid != None and self.rec.programid[:2] != 'MV':
            self.type = 'TV'
            self.log(self.log.GENERAL, self.log.INFO,
                    'Performing TV type migration.')
        else:
            self.type = 'MOVIE'
            self.log(self.log.GENERAL, self.log.INFO,
                    'Performing Movie type migration.')

    def process_fmt(self, fmt):
        # replace fields from viddata

        ext = '.'+self.rec.basename.rsplit('.',1)[1]
        rep = ( ('%TITLE%','title','%s'),   ('%SUBTITLE%','subtitle','%s'),
            ('%SEASON%','season','%d'),     ('%SEASONPAD%','season','%02d'),
            ('%EPISODE%','episode','%d'),   ('%EPISODEPAD%','episode','%02d'),
            ('%YEAR%','year','%s'),         ('%DIRECTOR%','director','%s'))
        for tag, data, format in rep:
            if self.vid[data]:
                fmt = fmt.replace(tag,format % self.vid[data])
            else:
                fmt = fmt.replace(tag,'')

        # replace fields from program data
        rep = ( ('%HOSTNAME%',    'hostname',    '%s'),
                ('%STORAGEGROUP%','storagegroup','%s'))
        for tag, data, format in rep:
            data = getattr(self.rec, data)
            fmt = fmt.replace(tag,format % data)


        if len(self.vid.genre):
            fmt = fmt.replace('%GENRE%',self.vid.genre[0].genre)
        else:
            fmt = fmt.replace('%GENRE%','')
        return fmt+ext

    def set_job_status(self, status):
        self.thisJob.setStatus(status)
        
    def set_vid_hash(self):
        self.vid.hash = self.vid.getHash()
    
    def update_vid(self):
        self.vid.update()
Beispiel #59
0
	magic = datetime(2009,9,1,0,0,0,0,a.starttime.tzinfo)
#	return a.recgroup == "Movies" and (float(a.channum) < 500 or getCommMethod(a.chanid) <> -2 or a.starttime < magic)
	return a.recgroup == "Movies" and (getCommMethod(a.chanid) <> -2 or a.starttime < magic)

def getChannel(chanid):
	for c in channels:
		if c.chanid == chanid:
			return c

def getCommMethod(chanid):
	c = getChannel(chanid);
	return c.commmethod


myth = MythBE()
channels = list(Channel.getAllEntries())
pending = filter(filterPMovies, myth.getPendingRecordings())
all_movies = filter(filterPMovies, myth.getRecordings())
recorded = filter(filterRMovies, all_movies)

for r in sorted(recorded, key=lambda a: a.title):

	def r_filter(a):
		return a.title == r.title

	my_rec = filter(r_filter, all_movies)

	def p_filter(a):
		for b in my_rec:
			if a.title <> b.title:
Beispiel #60
0
	"The Big Sleep" : 2,
	"Shaft" : 2,
	"The Wild One" : 2,
	"The Postman Always Rings Twice" : 2,
	"Witness for the Prosecution" : 2
	}


def findDupes(orig):  
    seen = {} 
    result = [] 
    for item in orig:
	seen[item.title] = seen.get(item.title,0) + 1 
    return dict((title,count) for title,count in seen.iteritems() if count > allowedDupes.get(title,1))

myth = MythBE()
mythDB = MythDB()

# get all recordings
recs = myth.getRecordings()

# limit to only movies that are currently recorded
recs = filter(lambda x: x.recgroup == "Movies", recs)

# limit to only ones that are recorded multiple times
recs = findDupes(recs)

# sort by title
recs = sorted(recs.iteritems(), key=operator.itemgetter(0))

for title,count in recs: