Ticket #6680: mythvidexport.6.py

File mythvidexport.6.py, 13.6 KB (added by Raymond Wagner <raymond@…>, 11 years ago)
Line 
1#!/usr/local/bin/python
2from MythTV import MythDB, MythLog, MythTV, MythVideo, Program, Job, FileTransfer
3from socket import gethostname
4from urllib import urlretrieve
5import sys, getopt, re, os, time, logging#, pprint
6
7#Usage: mythvidexport.py <--chanid <channel id>> <--starttime <start time>> [options]
8#               --fformat <see directory/file name options>
9#               --tformat <see directory/file name options>
10#       moves a single file over to MythVideo with optional directory format, format is discarded after use
11
12#Usage: mythvidexport.py <job id>
13#               WARNING: for internal use by job queue only!
14#       moves a single file, grabbing necessary information from job queue table
15
16#Usage: mythvidexport.py <options>
17#               --setformat <see directory/file name options>
18#                       stored directory format in database for future use
19#               --help
20#               --helpformat
21#                       lengthy discription of directory formatting
22#               --delete
23
24
25class VIDEO:
26        mythdb = None
27        dbconn = None
28        job = None
29        jobhost = None
30        chanid = None
31        rtime = None
32
33        prog = None
34        source = None
35        srchost = None
36        dest = None
37        desthost = None
38
39        ttvdb = None
40        ffmt = None
41        dfmt = None
42        log = None
43
44        viddata = {}
45        cast = []
46        genre = []
47        country = []
48
49        def __init__(self, *inp):
50                if len(inp) == 1:
51                        self.job = Job(inp[0]);
52                        self.chanid = self.job.chanid
53                        self.rtime = "%04d%02d%02d%02d%02d%02d" % self.job.starttime.timetuple()[0:6]
54                        self.jobhost = self.job.host
55                        self.job.setStatus(3)
56                elif len(inp) == 2:
57                        self.chanid = inp[0]
58                        self.rtime = inp[1]
59                else:
60                        sys.exit(2)
61                self.get_source()
62                self.log = MythLog(logging.CRITICAL, '#%(levelname)s - %(message)s', 'MythTV')
63
64        def dbconnect(self):
65                self.mythdb = MythDB()
66                self.dbconn = self.mythdb.db
67
68        def get_source(self):
69                ## connects to myth data socket and finds recording
70                if not self.dbconn:
71                        self.dbconnect()
72                if not self.jobhost:
73                        self.jobhost = gethostname()
74
75                mythinst = MythTV()
76                self.prog = mythinst.getRecording(self.chanid,int(self.rtime))
77
78                self.srchost = self.prog.hostname
79                self.source = mythinst.getCheckfile(self.prog)
80
81        def find_ttvdb(self):
82                ## searches for ttvdb.py script, and optional config file
83                if os.access('/usr/local/share/mythtv/mythvideo/scripts/ttvdb.py',os.F_OK):
84                        self.ttvdb = '/usr/local/share/mythtv/mythvideo/scripts/ttvdb.py'
85                elif os.access('/usr/share/mythtv/mythvideo/scripts/ttvdb.py',os.F_OK):
86                        self.ttvdb = '/usr/local/share/mythtv/mythvideo/scripts/ttvdb.py'
87                else:
88                        self.log.Msg(logging.CRITICAL, "Could not find ttvdb.py")
89                        self.exit(2)
90                if os.access(os.path.expanduser("~/.mythtv/ttvdb.conf"),os.F_OK):
91                        self.ttvdb += ' -c ' + os.path.expanduser("~/.mythtv/ttvdb.conf")
92
93        def get_meta(self):
94                ## grab season and episode number from thetvdb.com, exit if failed
95                regex = re.compile('S(?P<season>[0-9]*)E(?P<episode>[0-9]*)')
96                match = None
97                if self.ttvdb == None:
98                        self.find_ttvdb()
99                fp = os.popen("%s -N \"%s\" \"%s\"" % (self.ttvdb,self.prog.title,self.prog.subtitle),"r")
100                match = regex.search(fp.read())
101                fp.close()
102                if match is None:
103                        if self.job:
104                                self.job.setComment("TTVDB.py failed to get season/episode numbers")
105                                self.job.setStatus(304)
106                                self.log.Msg(logging.CRITICAL, "TTVDB.py failed to get season/episode numbers")
107                        else:
108                                print("TTVDB.py failed to get season/episode numbers")
109                        sys.exit(2)
110
111                self.viddata['season'] = int(match.group('season'))
112                self.viddata['episode'] = int(match.group('episode'))
113
114                ## gather remaining data
115                fp = os.popen("%s -mD '%s' %d %d" % (self.ttvdb,self.prog.title,self.viddata['season'],self.viddata['episode']))
116                time.sleep(2)
117                res = fp.read()
118                fp.close()
119                if len(res) == 0:
120                        ## ttvdb failed to grab data, use listings information
121                        self.viddata['director'] = self.mythdb.getCast(self.chanid, int(self.rtime), roles='director')
122                        if len(self.viddata['director']) == 0:
123                                self.viddata['director'] = 'NULL'
124                        else:
125                                self.viddata['director'] = self.viddata['director'][0]
126                        self.viddata['title'] = self.prog.title
127                        self.viddata['subtitle'] = self.prog.subtitle
128                        self.viddata['plot'] = self.prog.description
129                        self.viddata['year'] = self.prog.year
130                        self.viddata['length'] = str(int((self.prog.recendts-self.prog.recstartts).seconds/60))
131                        self.cast = self.mythdb.getCast(self.chanid, int(self.rtime), roles=('actor','guest_star','host','commentator','guest'))
132                        self.viddata['inetref'] = '00000000'
133                        self.genre = ()
134                        self.country = ()
135                else:
136                        ttvdbdat = {}
137                        for point in res.split('\n')[:-1]:
138                                key,dat = point.split(':',1)
139                                ttvdbdat[key] = dat
140                        self.viddata['title'] = ttvdbdat['Title']
141                        self.viddata['subtitle'] = ttvdbdat['Subtitle']
142                        self.viddata['director'] = ttvdbdat['Director']
143                        self.viddata['plot'] = ttvdbdat['Plot']
144                        self.viddata['year'] = ttvdbdat['Year']
145                        self.viddata['length'] = ttvdbdat['Runtime']
146                        self.viddata['userrating'] = ttvdbdat['UserRating']
147                        self.cast = tuple(ttvdbdat['Cast'].split(', '))
148                        self.genre = tuple(ttvdbdat['Genres'].split(', '))
149                        self.viddata['inetref'] = ttvdbdat['Seriesid']
150                        self.country = ()
151
152                self.viddata['showlevel'] = 1
153                self.viddata['rating'] = 'NR'
154                self.viddata['coverfile'] = 'No Cover'
155
156        def get_dest(self):
157                if self.ffmt is None:
158                        self.get_fmt()
159                ext = self.source[self.source.rfind('.'):]
160                subpath = self.process_fmt(self.dfmt) + "/" + self.process_fmt(self.ffmt) + ext
161
162                self.desthost = self.jobhost
163                cursor = self.dbconn.cursor()
164                reslen = cursor.execute("SELECT dirname FROM storagegroup WHERE hostname='%s' and groupname='Videos'" % self.desthost)
165                if reslen:
166                        destdir = cursor.fetchone()[0]
167                        self.destdb = subpath
168                else:
169                        destdir = self.mythdb.getSetting("VideoStartupDir",hostname=self.jobhost)+'/'
170                        self.destdb = self.dest
171                self.dest = destdir+subpath
172
173                tmppath = self.dest[0:self.dest.rfind('/')]
174                if not os.access(tmppath,os.F_OK):
175                        os.makedirs(tmppath)
176
177        def set_dfmt(self, fmt):
178                self.dfmt = fmt
179        def set_ffmt(self, fmt):
180                self.ffmt = fmt
181
182        def get_fmt(self):
183                self.ffmt = self.mythdb.getSetting('TV Export File Format')
184                if not self.ffmt:
185                        self.ffmt = '%TITLE% - %SUBTITLE%'
186                self.tfmt = self.mythdb.getSetting('TV Export Directory Format')
187                if not self.dfmt:
188                        self.dfmt = 'Television/%TITLE%'
189
190        def process_fmt(self, fmt):
191                fmt = fmt.replace('%TITLE%',self.viddata['title'])
192                fmt = fmt.replace('%SUBTITLE%',self.viddata['subtitle'])
193                fmt = fmt.replace('%SEASON%',"%d" % self.viddata['season'])
194                fmt = fmt.replace('%EPISODE%',"%02d" % self.viddata['episode'])
195                fmt = fmt.replace('%YEAR%',self.viddata['year'])
196                fmt = fmt.replace('%DIRECTOR%',self.viddata['director'])
197#               fmt = fmt.replace('%CARDID%',self.prog.cardid)
198#               fmt = fmt.replace('%CARDNAME%',self.prog.cardid)
199#               fmt = fmt.replace('%SOURCEID%',self.prog.cardid)
200#               fmt = fmt.replace('%SOURCENAME%',self.prog.cardid)
201                fmt = fmt.replace('%HOSTNAME%',self.prog.hostname)
202                fmt = fmt.replace('%STORAGEGROUP%',self.prog.storagegroup)
203#               fmt = fmt.replace('%CHANNUM%',self.prog.channum)
204#               fmt = fmt.replace('%CHANNAME%',self.prog.cardid)
205                if len(self.genre):
206                        fmt = fmt.replace('%GENRE%',self.genre[0])
207                else:
208                        fmt = fmt.replace('%GENRE%','')
209#               if len(self.country):
210#                       fmt = fmt.replace('%COUNTRY%',self.country[0])
211#               else:
212#                       fmt = fmt.replace('%COUNTRY%','')
213                return fmt
214
215        def copy(self):
216                cursor = None
217                srcp = None
218                srcsize = None
219                dtime = None
220                self.get_meta()
221                self.get_dest()
222                if self.desthost != self.jobhost:
223                        if self.job:
224                                self.job.setComment("Job must be run on destination host")
225                                self.log.Msg(logging.CRITICAL, 'MythVidExport failed - must be run on destination host')
226                                self.job.setStatus(304)
227                        else:
228                                print("ERROR: job must be run on destionation host")
229                        sys.exit(2)
230
231                stime = time.time()
232                ctime = time.time()
233                if os.access(self.source,os.F_OK):
234                        srcp = open(self.source,'r')
235                else:
236                        srcp = FileTransfer(self.prop)
237                destp = open(self.dest,'w')
238                srcsize = self.prog.filesize
239                destsize = [0,0,0,0,0,0,0,0,0,0]
240                if self.job:
241                        self.job.setStatus(4)
242                tsize = 2**18
243                while tsize == 2**18:
244                        if (srcsize - destp.tell()) < tsize:
245                                tsize = srcsize - destp.tell()
246                        if time.time() - ctime > 4:
247                                ctime = time.time()
248                                destsize.append(destp.tell())
249                                rem = srcsize - destp.tell()
250                                if destsize[0]:
251                                        dtime = 40
252                                else:
253                                        dtime = int(ctime - stime)
254                                rate = (destp.tell() - destsize.pop(0))/dtime
255                                remt = rem/rate
256                                if self.job:
257                                        self.job.setComment("%02d%% complete - %s seconds remaining" % (destsize[9]*100/srcsize, remt))
258                        destp.write(srcp.read(tsize))
259
260                srcp.close()
261                destp.close()
262                if self.job:
263                        self.job.setComment("Complete - %d seconds elapsed" % (int(time.time()-stime)))
264                        self.job.setStatus(256)
265
266
267        def write_meta(self):
268                if self.tfmt is None:
269                        self.get_fmt()
270                mythvid = MythVideo()
271                mythtv = MythTV()
272#               pp = pprint.PrettyPrinter(indent=4)
273                self.viddata['title'] = self.process_fmt(self.tfmt)
274                self.viddata['filename'] = self.destdb
275                self.viddata['host'] = self.desthost
276
277
278#               pp.pprint(self.viddata)
279                intid = mythvid.getMetadataId(self.destdb)
280                if intid:
281                        mythvid.setMetadata(self.viddata,intid)
282                else:
283                        intid = mythvid.setMetadata(self.viddata)
284#               print("Metadata written to intid %d" % intid)
285#               pp.pprint(self.viddata)
286
287#               print("With cast:")
288#               pp.pprint(self.cast)
289                for name in self.cast:
290                        mythvid.setCast(name, intid)
291#               print("With genre:")
292#               pp.pprint(self.genre)
293#               for genre in self.genre:
294#                       mythvid.setGenre(genre, intid)
295                self.write_image('coverfile',intid)
296                self.write_image('banner',intid)
297                self.write_image('fanart',intid)
298                if self.job:
299                        self.job.setStatus(272)
300
301        def write_image(self,mode,intid):
302                destdir = None
303                SG = {'coverfile':'Coverart','banner':'Banners','fanart':'Fanart'}
304                ttvdb = {'coverfile':'-mP','banner':'-mB','fanart':'-mtF'}
305                mythvid = MythVideo()
306                cursor = self.dbconn.cursor()
307                reslen = cursor.execute("SELECT dirname FROM storagegroup WHERE hostname='%s' and groupname='%s'" % (self.desthost,SG[mode]))
308                if reslen:
309                        destdir = cursor.fetchone()[0]
310                else:
311                        return
312
313                fp = os.popen("%s %s '%s' %d %d" % (self.ttvdb,ttvdb[mode],self.prog.title,self.viddata['season'],self.viddata['episode']))
314                time.sleep(2)
315                res = fp.read()
316                fp.close()
317
318                match = re.match('http://images.thetvdb.com.nyud.net:8080/[a-zA-Z/]*/(?P<file>[a-zA-Z0-9-.]*)',res)
319                if not match:
320                        return
321                path = destdir+match.group('file')
322                if not os.access(path,os.F_OK):
323                        urlretrieve(match.group(),path)
324
325                mythvid.setMetadata({mode:match.group('file')},intid)
326
327        def delete():
328                mythtv = MythTV()
329                mythtv.deleteRecording(self.prog)
330
331def usage():
332        print("mythvidexport.py [options] [--chanid=<chanid> --starttime=<starttime> | <jobid>]")
333        print("        This script can be run by specifing the channel and start time directly")
334        print("            or by specifing the ID of a job in jobqueue")
335        print("")
336        print("        Run from the command line through the former:")
337        print("            mythvidexport.py --chanid=1002 --starttime=200907010000")
338        print("        Or from a user script through the latter:")
339        print("            mythvidexport.py %JOBID%")
340        print("")
341        print("    Options:")
342        print("        -h/--help and -f/--helpformat:")
343        print("             return this help, or a listing of available formatting strings")
344        print("        --fformat='<string>' and --dformat='<string>':")
345        print("             override the stored formatting string in the database")
346        print("             if no recording is specified, store format string to the database")
347
348def usage_format():
349        print("The default strings are:")
350        print("    fformat: '%TITLE% - %SUBTITLE%'")
351        print("    dformat: 'Television/%TITLE%'")
352        print("")
353        print("Available strings:")
354        print("    %TITLE%:         series title")
355        print("    %SUBTITLE%:      episode title")
356        print("    %SEASON%:        season number")
357        print("    %EPISODE%:       episode number, padded to 2 digits")
358        print("    %YEAR%:          year")
359        print("    %DIRECTOR%:      director")
360#       print("    %CARDID%:        ID of tuner card used to record show")
361#       print("    %CARDNAME%:      name of tuner card used to record show")
362#       print("    %SOURCEID%:      ID of video source used to record show")
363#       print("    %SOURCENAME%:    name of video source used to record show")
364        print("    %HOSTNAME%:      backend used to record show")
365        print("    %STORAGEGROUP%:  storage group containing recorded show")
366#       print("    %CHANNUM%:       ID of channel used to record show")
367#       print("    %CHANNAME%:      name of channel used to record show")
368        print("    %GENRE%:         first genre listed for recording")
369#       print("    %COUNTRY%:       first country listed for recording")
370
371def main():
372        fformat = None
373        tformat = None
374        jobid = None
375        chanid = None
376        rtime = None
377        skip = False
378        delete = False
379        try:
380                opts, args = getopt.getopt(sys.argv[1:], "hf", ["help","helpformat","fformat=","dformat=","chanid=","starttime=","skip","delete"])
381        except getopt.GetoptError, err:
382                print(str(err))
383                usage()
384                sys.exit(2)
385        for opt, arg in opts:
386                if opt in ("-h", "--help"):
387                        usage()
388                        sys.exit()
389                elif opt in ('-f', '--helpformat'):
390                        usage_format()
391                        sys.exit()
392                elif opt == '--dformat':
393                        dformat = arg
394                elif opt == '--fformat':
395                        fformat = arg
396                elif opt == '--chanid':
397                        chanid = int(arg)
398                elif opt == '--starttime':
399                        rtime = int(arg)
400                elif opt == '--skip':
401                        skip = True
402                elif opt == '--delete':
403                        delete = True
404               
405        if len(args):
406                jobid = int(args[0])
407
408        if chanid and rtime:
409                export = VIDEO(chanid,rtime)
410        elif jobid:
411                export = VIDEO(jobid)
412        elif fformat or dformat:
413                if fformat:
414                        mythdb = MythDB()
415                        mythdb.setSetting('TV Export File Format', fmt)
416                if dformat:
417                        mythdb = MythDB()
418                        mythdb.setSetting('TV Export Directory Format', fmt)
419                sys.exit()
420        else:
421                usage()
422                sys.exit(2)
423
424        if skip:
425                export.get_meta()
426                export.get_dest()
427        else:
428                export.copy()
429        export.write_meta()
430#       if delete:
431#               export.delete()
432
433if __name__ == "__main__":
434        main()