Ticket #6680: mythvidexport.14.py

File mythvidexport.14.py, 16.4 KB (added by Raymond Wagner <raymond@…>, 10 years ago)

update for #7264

Line 
1#!/usr/local/bin/python
2# -*- coding: UTF-8 -*-
3#---------------------------
4# Name: mythvidexport.py
5# Python Script
6# Author: Raymond Wagner
7# Purpose
8#   This python script is intended to function as a user job, run through
9#   mythjobqueue, capable of exporting recordings into MythVideo.
10#---------------------------
11__title__  = "MythVidExport"
12__author__ = "Raymond Wagner"
13__version__= "v0.6.0"
14
15usage_txt = """
16This script can be run from the command line, or called through the mythtv
17jobqueue.  The input format will be:
18  mythvidexport.py [options] <--chanid <channel id>> <--starttime <start time>>
19                 --- or ---
20  mythvidexport.py [options] %JOBID%
21
22Options are:
23        --mformat <format string>
24        --tformat <format string>
25        --gformat <format string>
26            overrides the stored format string for a single run
27        --listingonly
28            use EPG data rather than grabbers for metadata
29            will still try to grab episode and season information from ttvdb.py
30
31Additional functions are available beyond exporting video
32  mythvidexport.py <options>
33        -h, --help             show this help message
34        -p, --printformat      print existing format strings
35        -f, --helpformat       lengthy description for formatting strings
36        --mformat <string>     replace existing Movie format
37        --tformat <string>     replace existing TV format
38        --gformat <string>     replace existing Generic format
39"""
40
41from MythTV import MythDB, Job, Video, VideoGrabber
42from socket import gethostname
43from urllib import urlopen
44from optparse import OptionParser
45import sys, re, os, time
46
47
48#log = MythLog(NOTICE, 'MythVidExport.py')
49
50class VIDEO:
51    def __init__(self, opts, jobid=None):
52        if jobid:
53            self.job = Job(jobid)
54            self.chanid = self.job.chanid
55            self.starttime = int("%04d%02d%02d%02d%02d%02d" \
56                        % self.job.starttime.timetuple()[0:6])
57            self.job.update(status=3)
58        else:
59            self.job = None
60            self.chanid = opts.chanid
61            self.rtime = opts.starttime
62
63        self.opts = opts
64        self.db = MythDB()
65
66        # load setting strings
67        self.get_grabbers()
68        self.get_format()
69
70        # process file
71        self.cast = ()
72        self.genre = ()
73        self.country = ()
74        self.rec = self.db.getRecorded(chanid=self.chanid,\
75                                    starttime=self.starttime)
76        self.vid = Video()
77        self.vid.host = gethostname()
78
79        self.get_meta()
80        self.get_dest()
81
82        # save file
83        self.copy()
84        self.write_images()
85        self.vid.create()
86        self.write_cref()
87
88    def get_grabbers(self):
89        # TV Grabber
90        self.TVgrab = VideoGrabber('TV')
91        # if ttvdb.py, optionally add config file
92        if self.TVgrab.path.split('/')[-1] == 'ttvdb.py':
93            path = os.path.expanduser('~/.mythtv/ttvdb.conf')
94            if os.access(path, os.F_OK):
95                self.TVgrab.append(' -c '+path)
96
97        # Movie Grabber
98        self.Mgrab = VideoGrabber('Movie')
99
100    def get_format(self):
101        host = gethostname()
102        # TV Format
103        if self.opts.tformat:
104            self.tfmt = self.opts.tformat
105        elif self.db.settings[host]['mythvideo.TVexportfmt']:
106            self.tfmt = self.db.settings[host]['mythvideo.TVexportfmt']
107        else:
108            self.tfmt = 'Television/%TITLE%/Season %SEASON%/'+\
109                            '%TITLE% - S%SEASON%E%EPISODEPAD% - %SUBTITLE%'
110
111        # Movie Format
112        if self.opts.mformat:
113            self.mfmt = self.opts.mformat
114        elif self.db.settings[host]['mythvideo.MOVIEexportfmt']:
115            self.mfmt = self.db.settings[host]['mythvideo.MOVIEexportfmt']
116        else:
117            self.mfmt = 'Movies/%TITLE%'
118
119        # Generic Format
120        if self.opts.gformat:
121            self.gfmt = self.opts.gformat
122        elif self.db.settings[host]['mythvideo.GENERICexportfmt']:
123            self.gfmt = self.db.settings[host]['mythvideo.GENERICexportfmt']
124        else:
125            self.gfmt = 'Videos/%TITLE%'
126
127    def get_meta(self):
128        self.vid.hostname = gethostname()
129        if self.rec.subtitle:  # subtitle exists, assume tv show
130            self.get_tv()
131        else:                   # assume movie
132            self.get_movie()
133
134    def get_tv(self):
135        # grab season and episode number, run generic export if failed
136        #inetref = self.TVgrab.searchTitle(self.rec.title)
137        season, episode = self.TVgrab.searchEpisode(self.rec.title, \
138                                                    self.rec.subtitle)
139        if (season is None):# or (len(inetref) > 1):
140            self.get_generic()
141            return
142        #self.vid.inetref = inetref[0][0]
143        self.vid.season, self.vid.episode = (season, episode)
144
145        if self.opts.listingonly:
146            self.get_generic()
147        else:
148            dat, self.cast, self.genre, self.country = \
149                    self.TVgrab.getData(self.rec.title,\
150                                        self.vid.season,\
151                                        self.vid.episode)
152            self.vid.data.update(dat)
153        self.type = 'TV'
154
155    def get_movie(self):
156        inetref = self.Mgrab.searchTitle(self.rec.title,\
157                            self.rec.originalairdate.year)
158        if len(inetref) == 1:
159            inetref = inetref[0][0]
160        else:
161            self.get_generic()
162            return
163
164        if self.opts.listingonly:
165            self.get_generic()
166        else:
167            dat, self.cast, self.genre, self.country = \
168                    self.Mgrab.getData(inetref)
169            self.vid.data.update(dat)
170        self.type = 'Movie'
171
172    def get_generic(self):
173        self.vid.title = self.rec.title
174        if self.rec.subtitle:
175            self.vid.subtitle = self.rec.subtitle
176        if self.rec.description:
177            self.vid.plot = self.rec.description
178        if self.rec.originalairdate:
179            self.vid.year = self.rec.originalairdate.year
180            self.vid.releasedate = self.rec.originalairdate
181        lsec = (self.rec.endtime-self.rec.starttime).seconds
182        self.vid.length = str(lsec/60)
183        for member in self.rec.cast:
184            if member.role == 'director':
185                self.vid.director = member.name
186            elif member.role == 'actor':
187                self.cast.append(member.name)
188        self.type = 'GENERIC'
189
190    def get_dest(self):
191        if self.type == 'TV':
192            self.vid.filename = self.process_fmt(self.tfmt)
193        elif self.type == 'MOVIE':
194            self.vid.filename = self.process_fmt(self.mfmt)
195        elif self.type == 'GENERIC':
196            self.vid.filename = self.process_fmt(self.gfmt)
197
198    def process_fmt(self, fmt):
199        # replace fields from viddata
200        #print self.vid.data
201        ext = '.'+self.rec.basename.rsplit('.',1)[1]
202        rep = ( ('%TITLE%','title','%s'),   ('%SUBTITLE%','subtitle','%s'),
203            ('%SEASON%','season','%d'),     ('%SEASONPAD%','season','%02d'),
204            ('%EPISODE%','episode','%d'),   ('%EPISODEPAD%','episode','%02d'),
205            ('%YEAR%','year','%s'),         ('%DIRECTOR%','director','%s'))
206        for (tag, data, format) in rep:
207            if self.vid[data]:
208                fmt = fmt.replace(tag,format % self.vid[data])
209            else:
210                fmt = fmt.replace(tag,'')
211
212        # replace fields from program data
213        rep = ( ('%HOSTNAME','hostname','%s'),('%STORAGEGROUP%','storagegroup','%s'))
214        for (tag, data, format) in rep:
215            data = eval('self.rec.%s' % data)
216            fmt = fmt.replace(tag,format % data)
217
218#       fmt = fmt.replace('%CARDID%',self.rec.cardid)
219#       fmt = fmt.replace('%CARDNAME%',self.rec.cardid)
220#       fmt = fmt.replace('%SOURCEID%',self.rec.cardid)
221#       fmt = fmt.replace('%SOURCENAME%',self.rec.cardid)
222#       fmt = fmt.replace('%CHANNUM%',self.rec.channum)
223#       fmt = fmt.replace('%CHANNAME%',self.rec.cardid)
224
225        if len(self.genre):
226            fmt = fmt.replace('%GENRE%',self.genre[0])
227        else:
228            fmt = fmt.replace('%GENRE%','')
229#       if len(self.country):
230#           fmt = fmt.replace('%COUNTRY%',self.country[0])
231#       else:
232#           fmt = fmt.replace('%COUNTRY%','')
233        return fmt+ext
234
235    def copy(self):
236        if self.opts.skip:
237            self.vid.hash = self.vid.getHash()
238            return
239        if self.opts.sim:
240            return
241
242        #print self.vid.filename
243
244        stime = time.time()
245        srcsize = self.rec.filesize
246        htime = [stime,stime,stime,stime]
247
248        srcfp = self.rec.open('r')
249        dstfp = self.vid.open('w')
250
251        if self.job:
252            self.job.setStatus(4)
253        tsize = 2**24
254        while tsize == 2**24:
255            if (srcsize - dstfp.tell()) < tsize:
256                tsize = srcsize - dstfp.tell()
257            dstfp.write(srcfp.read(tsize))
258            htime.append(time.time())
259            rate = float(tsize*4)/(time.time()-htime.pop())
260            remt = (srcsize-dstfp.tell())/rate
261            if self.job:
262                self.job.setComment("%02d%% complete - %s seconds remaining" %\
263                            (dstfp.tell()*100/srcsize, remt))
264        srcfp.close()
265        dstfp.close()
266
267        self.vid.hash = self.vid.getHash()
268
269#       log.notice('Transfer complete','%d seconds elapsed' % int(time.time()-stime))
270        if self.job:
271            self.job.setComment("Complete - %d seconds elapsed" % \
272                            (int(time.time()-stime)))
273            self.job.setStatus(256)
274
275    def write_images(self):
276        for type in ('coverfile', 'screenshot', 'banner', 'fanart'):
277            if self.vid[type] in ('No Cover','',None):
278                continue
279            if type == 'coverfile': name = 'coverart'
280            else: name = type
281            url = self.vid[type]
282            if len(url.split(',')) > 1:
283                url = url.split(',')[0]
284
285            if self.type == 'TV':
286                if type == 'screenshot':
287                    self.vid[type] = '%s Season %dx%d_%s.%s' % \
288                                (self.vid.title, self.vid.season,
289                                self.vid.episode, name, url.rsplit('.',1)[1])
290                else:
291                    self.vid[type] = '%s Season %d_%s.%s' % \
292                                (self.vid.title, self.vid.season,
293                                 name, url.rsplit('.',1)[1])
294            else:
295                self.vid[type] = '%s_%s.%s' % \
296                            (self.vid.title, name, url.rsplit('.',1)[1])
297
298            try:
299                dstfp = self.vid._open(type, 'w', True)
300                srcfp = urlopen(url)
301                dstfp.write(srcfp.read())
302                srcfp.close()
303                dstfp.close()
304            except:
305                #print 'existing images: ' + self.vid[type]
306                pass
307
308    def write_cref(self):
309        for member in self.cast:
310            self.vid.cast.add(member)
311        for member in self.genre:
312            self.vid.genre.add(member)
313        for member in self.country:
314            self.vid.country.add(member)
315
316def usage():
317    print("mythvidexport.py [options] [--chanid=<chanid> --starttime=<starttime> | <jobid>]")
318    print("        This script can be run by specifing the channel and start time directly")
319    print("            or by specifing the ID of a job in jobqueue")
320    print("")
321    print("        Run from the command line through the former:")
322    print("            mythvidexport.py --chanid=1002 --starttime=200907010000")
323    print("        Or from a user script through the latter:")
324    print("            mythvidexport.py %JOBID%")
325    print("")
326    print("    Options:")
327    print("        -h/--help and -f/--helpformat:")
328    print("             return this help, or a listing of available formatting strings")
329    print("        --fformat='<string>' and --dformat='<string>':")
330    print("             override the stored formatting string in the database")
331    print("             if no recording is specified, store format string to the database")
332
333def usage_format():
334    print("The default strings are:")
335    print("    Television: 'Television/%TITLE%/Season %SEASON%/%TITLE% - S%SEASON%E%EPISODEPAD% - %SUBTITLE%'")
336    print("    Movie:      'Movies/%TITLE%'")
337    print("    Generic:    'Videos/%TITLE%'")
338    print("")
339    print("Available strings:")
340    print("    %TITLE%:         series title")
341    print("    %SUBTITLE%:      episode title")
342    print("    %SEASON%:        season number")
343    print("    %SEASONPAD%:     season number, padded to 2 digits")
344    print("    %EPISODE%:       episode number")
345    print("    %EPISODEPAD%:    episode number, padded to 2 digits")
346    print("    %YEAR%:          year")
347    print("    %DIRECTOR%:      director")
348#   print("    %CARDID%:        ID of tuner card used to record show")
349#   print("    %CARDNAME%:      name of tuner card used to record show")
350#   print("    %SOURCEID%:      ID of video source used to record show")
351#   print("    %SOURCENAME%:    name of video source used to record show")
352    print("    %HOSTNAME%:      backend used to record show")
353    print("    %STORAGEGROUP%:  storage group containing recorded show")
354#   print("    %CHANNUM%:       ID of channel used to record show")
355#   print("    %CHANNAME%:      name of channel used to record show")
356    print("    %GENRE%:         first genre listed for recording")
357#   print("    %COUNTRY%:       first country listed for recording")
358
359def print_format():
360    db = MythDB()
361    tfmt = db.getSetting('mythvideo.TVexportfmt')
362    if not tfmt:
363        tfmt = 'Television/%TITLE%/%TITLE% - S%SEASON%E%EPISODEPAD% - %SUBTITLE%'
364    mfmt = db.getSetting('mythvideo.MOVIEexportfmt')
365    if not mfmt:
366        mfmt = 'Movies/%TITLE%'
367    gfmt = db.getSetting('mythvideo.GENERICexportfmt')
368    if not gfmt:
369        gfmt = 'Videos/%TITLE%'
370    print "Current output formats:"
371    print "    TV:      "+tfmt
372    print "    Movies:  "+mfmt
373    print "    Generic: "+gfmt
374
375def main():
376    parser = OptionParser(usage="usage: %prog [options] [jobid]")
377
378    parser.add_option("-f", "--helpformat", action="store_true", default=False, dest="fmthelp",
379            help="Print explination of file format string.")
380    parser.add_option("-p", "--printformat", action="store_true", default=False, dest="fmtprint",
381            help="Print current file format string.")
382    parser.add_option("--tformat", action="store", type="string", dest="tformat",
383            help="Use TV format for current task. If no task, store in database.")
384    parser.add_option("--mformat", action="store", type="string", dest="mformat",
385            help="Use Movie format for current task. If no task, store in database.")
386    parser.add_option("--gformat", action="store", type="string", dest="gformat",
387            help="Use Generic format for current task. If no task, store in database.")
388    parser.add_option("--chanid", action="store", type="int", dest="chanid",
389            help="Use chanid for manual operation")
390    parser.add_option("--starttime", action="store", type="int", dest="starttime",
391            help="Use starttime for manual operation")
392    parser.add_option("--listingonly", action="store_true", default=False, dest="listingonly",
393            help="Use data from listing provider, rather than grabber")
394    parser.add_option("-s", "--simulation", action="store_true", default=False, dest="sim",
395            help="Simulation (dry run), no files are copied or new entries made")
396    parser.add_option("--skip", action="store_true", default=False, dest="skip") # debugging use only
397
398    opts, args = parser.parse_args()
399
400    if opts.fmthelp:
401        usage_format()
402        sys.exit(0)
403
404    if opts.fmtprint:
405        usage_current()
406        sys.exit(0)
407
408    if opts.chanid and opts.starttime:
409        export = VIDEO(opts)
410    elif len(args) == 1:
411        export = VIDEO(opts,int(args[0]))
412    else:
413        if opts.tformat or opts.mformat or opts.gformat:
414            db = MythDBConn()
415            if opts.tformat:
416                print "Changing TV format to: "+opts.tformat
417                db.setting['NULL']['mythvideo.TVexportfmt'] = opts.tformat
418            if opts.mformat:
419                print "Changing Movie format to: "+opts.mformat
420                db.setting['NULL']['mythvideo.MOVIEexportfmt'] = opts.mformat
421            if opts.gformat:
422                print "Changing Generic format to: "+opts.gformat
423                db.setting['NULL']['mythvideo.GENERICexportfmt'] = opts.gformat
424            sys.exit(0)
425        else:
426            parser.print_help()
427            sys.exit(2)
428
429if __name__ == "__main__":
430    main()