Titanimport.py

From MythTV Official Wiki
Revision as of 04:13, 25 October 2011 by Wagnerrp (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Author Raymond Wagner
Description A python script that matches recording directives in tvpi files to existing EPG entries, and creates new recording rules to fit.
Supports Version24.png  Version25.png  


This Python script is intended to be used in combination with a web browser, as the registered handler for the application/x-tv-program-info MIME type. This searches within the existing EPG data for a show on a matching channel, in the correct timeslot, with the same title and subtitle. If the --fuzzy option is given, the requirements are relaxed to any show of that title in that timeslot. The timeslot allows for five minutes of variation either direction in start and end time, to allow for differences in guide data.


Important.png Note: Remote Scheduling is not currently supported.

Usage: titanimport.py [options] <tvpi file> [<tvpi file> [...]]

Options:
  -h, --help            show this help message and exit
  -f, --fuzzy           Record something with matching program title in that
                        timeslot if episode or station information does not
                        match
  -s, --simulation      Find matching recording and print out, but do not
                        create new rule
  -i, --intelligent     Use a 'record one' rule, rather than 'record
                        specific', to allow the scheduler to deal with
                        conflicting recordings intelligently
  -p PRIORITY, --priority=PRIORITY
                        Specify recording priority for purposes of conflict
                        resolution


PythonIcon.png titanimport.py

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#---------------------------
# Name: titanimport.py
# Python Script
# Author: Raymond Wagner
# Purpose
#   This python script is to be run by a browser to handle downloaded tvpi
#   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:
                return None
    elif tvmode in ('digital_cable', 'satellite'):
        # for digital cable and satellite, use station name only
        for c in Channel.getAllEntries(db=DB):
            if c.callsign == xmlprog.find('station').text:
                chan = c
                break
        else:
            if not fuzzy:
                return None
    elif tvmode == 'digital':
        # for broadcast digital, go with PSIP channel
        # fall back to physical channel and stream id
        for c in Channel.getAllEntries():
            if c.atsc_major_chan == int(xmlprog.find('psip-major').text) and \
               c.atsc_major_chan == int(xmlprog.find('psip-minor').text):
                chan = c
                break
        else:
            for c in Channel.getAllEntries(db=DB):
                if c.freqid == xmlprog.find('rf-channel').text and \
                   c.serviceid == xmlprog.find('stream-number').text:
                    chan = c
                    break
            else:
                if not fuzzy:
                    return None

    starttime = datetime.duck(xmlprog.find('start-date').text +\
                              xmlprog.find('start-time').text.replace(':','') +\
                              '00') + tzoff
    endtime = datetime.duck(xmlprog.find('end-date').text +\
                            xmlprog.find('end-time').text.replace(':','') +\
                            '00') + tzoff
    title = xmlprog.find('program-title').text
    try: subtitle = xmlprog.find('episode-title').text
    except AttributeError: subtitle = None

    if chan:
        # there should only be one response to the query
        try:
            prog = DB.searchGuide(chanid = chan.chanid,
                                  startafter = starttime-timedelta(0,300),
                                  startbefore = starttime+timedelta(0,300),
                                  endafter = endtime-timedelta(0,300),
                                  endbefore = endtime+timedelta(0,300)).next()
        except StopIteration:
            return None

        if prog.title == xmlprog.find('program-title').text:
            if not subtitle:
                # direct movie match
                return prog
            if prog.subtitle == subtitle:
                # direct television match
                return prog
            if fuzzy:
                # close enough
                return prog
        return None

    else:
        progs = list(DB.searchGuide(title = xmlprog.find('program-title').text,
                                    startafter = starttime-timedelta(0,300),
                                    startbefore = starttime+timedelta(0,300),
                                    endafter = endtime-timedelta(0,300),
                                    endbefore = endtime+timedelta(0,300)))
        if len(progs) == 0:
            return None

        if not subtitle:
            # nothing that can be used to better match, just pick the first
            return progs[0]

        for prog in progs:
            if prog.subtitle == subtitle:
                # best option
                return prog
        else:
            # no direct match, just pick the first
            return progs[0]

def main():
    parser = OptionParser(usage="usage: %prog [options] <tvpi file> [<tvpi file> [...]]")

    parser.add_option("-f", "--fuzzy", action="store_true", default=False, dest="fuzzy",
            help="Record something with matching program title in that timeslot if "+\
                 "episode or station information does not match")
    parser.add_option("-s", "--simulation", action="store_true", default=False, dest="sim",
            help="Find matching recording and print out, but do not create new rule")
    parser.add_option("-i", "--intelligent", action="store_true", default=False, dest="smart",
            help="Use a 'record one' rule, rather than 'record specific', to allow the "+\
                 "scheduler to deal with conflicting recordings intelligently")
    parser.add_option("-p", "--priority", action="store", type="int", default=0, dest="priority",
            help="Specify recording priority for purposes of conflict resolution")

    opts, args = parser.parse_args()

    for arg in args:
        print "Reading "+arg
        with open(arg) as f:
            xml = etree.parse(f)

        for xmlprog in xml.getroot().iterfind("program"):
            prog = FindProgram(xmlprog, opts.fuzzy)
            if not prog:
                print "No matching guide data for {0} found"\
                            .format(xmlprog.find('program-title').text)
                continue

            print "Adding rule for {0} on {1} at {2}"\
                            .format(prog.title, Channel(prog.chanid).callsign,
                                    prog.starttime.isoformat())
            if not opts.sim:
                if opts.smart:
                    rec = Record.fromPowerRule("{0.title} ({0.subtitle})".format(prog),
                            where = 'program.title = ? AND program.subtitle = ?',
                            args = (prog.title, prog.subtitle),
                            type = Record.kFindOneRecord,
                            db = DB)
                else:
                    rec = Record.fromGuide(prog, Record.kSingleRecord)
                if opts.priority:
                    rec.recpriority = opts.priority
                    rec.update()

if __name__ == '__main__':
    main()