Ticket #6680: mythvidexport.7.py

File mythvidexport.7.py, 13.9 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% - %SEASON%x%EPISODEPAD% - %SUBTITLE%'
186                self.dfmt = 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('%SEASONPAD%',"%02d" % self.viddata['season'])
195                fmt = fmt.replace('%EPISODE%',"%d" % self.viddata['episode'])
196                fmt = fmt.replace('%EPISODEPAD%',"%02d" % self.viddata['episode'])
197                fmt = fmt.replace('%YEAR%',self.viddata['year'])
198                fmt = fmt.replace('%DIRECTOR%',self.viddata['director'])
199#               fmt = fmt.replace('%CARDID%',self.prog.cardid)
200#               fmt = fmt.replace('%CARDNAME%',self.prog.cardid)
201#               fmt = fmt.replace('%SOURCEID%',self.prog.cardid)
202#               fmt = fmt.replace('%SOURCENAME%',self.prog.cardid)
203                fmt = fmt.replace('%HOSTNAME%',self.prog.hostname)
204                fmt = fmt.replace('%STORAGEGROUP%',self.prog.storagegroup)
205#               fmt = fmt.replace('%CHANNUM%',self.prog.channum)
206#               fmt = fmt.replace('%CHANNAME%',self.prog.cardid)
207                if len(self.genre):
208                        fmt = fmt.replace('%GENRE%',self.genre[0])
209                else:
210                        fmt = fmt.replace('%GENRE%','')
211#               if len(self.country):
212#                       fmt = fmt.replace('%COUNTRY%',self.country[0])
213#               else:
214#                       fmt = fmt.replace('%COUNTRY%','')
215                return fmt
216
217        def copy(self):
218                cursor = None
219                srcp = None
220                srcsize = None
221                dtime = None
222                self.get_meta()
223                self.get_dest()
224                if self.desthost != self.jobhost:
225                        if self.job:
226                                self.job.setComment("Job must be run on destination host")
227                                self.log.Msg(logging.CRITICAL, 'MythVidExport failed - must be run on destination host')
228                                self.job.setStatus(304)
229                        else:
230                                print("ERROR: job must be run on destionation host")
231                        sys.exit(2)
232
233                stime = time.time()
234                ctime = time.time()
235                if os.access(self.source,os.F_OK):
236                        srcp = open(self.source,'r')
237                else:
238                        srcp = FileTransfer(self.prop)
239                destp = open(self.dest,'w')
240                srcsize = self.prog.filesize
241                destsize = [0,0,0,0,0,0,0,0,0,0]
242                if self.job:
243                        self.job.setStatus(4)
244                tsize = 2**18
245                while tsize == 2**18:
246                        if (srcsize - destp.tell()) < tsize:
247                                tsize = srcsize - destp.tell()
248                        if time.time() - ctime > 4:
249                                ctime = time.time()
250                                destsize.append(destp.tell())
251                                rem = srcsize - destp.tell()
252                                if destsize[0]:
253                                        dtime = 40
254                                else:
255                                        dtime = int(ctime - stime)
256                                rate = (destp.tell() - destsize.pop(0))/dtime
257                                remt = rem/rate
258                                if self.job:
259                                        self.job.setComment("%02d%% complete - %s seconds remaining" % (destsize[9]*100/srcsize, remt))
260                        destp.write(srcp.read(tsize))
261
262                srcp.close()
263                destp.close()
264                if self.job:
265                        self.job.setComment("Complete - %d seconds elapsed" % (int(time.time()-stime)))
266                        self.job.setStatus(256)
267
268
269        def write_meta(self):
270                if self.tfmt is None:
271                        self.get_fmt()
272                mythvid = MythVideo()
273                mythtv = MythTV()
274#               pp = pprint.PrettyPrinter(indent=4)
275                self.viddata['title'] = self.process_fmt(self.tfmt)
276                self.viddata['filename'] = self.destdb
277                self.viddata['host'] = self.desthost
278
279
280#               pp.pprint(self.viddata)
281                intid = mythvid.getMetadataId(self.destdb)
282                if intid:
283                        mythvid.setMetadata(self.viddata,intid)
284                else:
285                        intid = mythvid.setMetadata(self.viddata)
286#               print("Metadata written to intid %d" % intid)
287#               pp.pprint(self.viddata)
288
289#               print("With cast:")
290#               pp.pprint(self.cast)
291                for name in self.cast:
292                        mythvid.setCast(name, intid)
293#               print("With genre:")
294#               pp.pprint(self.genre)
295#               for genre in self.genre:
296#                       mythvid.setGenre(genre, intid)
297                self.write_image('coverfile',intid)
298                self.write_image('banner',intid)
299                self.write_image('fanart',intid)
300                if self.job:
301                        self.job.setStatus(272)
302
303        def write_image(self,mode,intid):
304                destdir = None
305                SG = {'coverfile':'Coverart','banner':'Banners','fanart':'Fanart'}
306                ttvdb = {'coverfile':'-mP','banner':'-mB','fanart':'-mtF'}
307                mythvid = MythVideo()
308                cursor = self.dbconn.cursor()
309                reslen = cursor.execute("SELECT dirname FROM storagegroup WHERE hostname='%s' and groupname='%s'" % (self.desthost,SG[mode]))
310                if reslen:
311                        destdir = cursor.fetchone()[0]
312                else:
313                        return
314
315                fp = os.popen("%s %s '%s' %d %d" % (self.ttvdb,ttvdb[mode],self.prog.title,self.viddata['season'],self.viddata['episode']))
316                time.sleep(2)
317                res = fp.read()
318                fp.close()
319
320                match = re.match('http://images.thetvdb.com.nyud.net:8080/[a-zA-Z/]*/(?P<file>[a-zA-Z0-9-.]*)',res)
321                if not match:
322                        return
323                path = destdir+match.group('file')
324                if not os.access(path,os.F_OK):
325                        urlretrieve(match.group(),path)
326                if mode == 'coverfile':
327                        if not os.access(destdir+'folder.jpg'):
328                                urlretrieve(match.group(),destdir+'folder.jpg')
329
330                mythvid.setMetadata({mode:match.group('file')},intid)
331
332        def delete():
333                mythtv = MythTV()
334                mythtv.deleteRecording(self.prog)
335
336def usage():
337        print("mythvidexport.py [options] [--chanid=<chanid> --starttime=<starttime> | <jobid>]")
338        print("        This script can be run by specifing the channel and start time directly")
339        print("            or by specifing the ID of a job in jobqueue")
340        print("")
341        print("        Run from the command line through the former:")
342        print("            mythvidexport.py --chanid=1002 --starttime=200907010000")
343        print("        Or from a user script through the latter:")
344        print("            mythvidexport.py %JOBID%")
345        print("")
346        print("    Options:")
347        print("        -h/--help and -f/--helpformat:")
348        print("             return this help, or a listing of available formatting strings")
349        print("        --fformat='<string>' and --dformat='<string>':")
350        print("             override the stored formatting string in the database")
351        print("             if no recording is specified, store format string to the database")
352
353def usage_format():
354        print("The default strings are:")
355        print("    fformat: '%TITLE% - %SUBTITLE%'")
356        print("    dformat: 'Television/%TITLE%'")
357        print("")
358        print("Available strings:")
359        print("    %TITLE%:         series title")
360        print("    %SUBTITLE%:      episode title")
361        print("    %SEASON%:        season number")
362        print("    %EPISODE%:       episode number, padded to 2 digits")
363        print("    %YEAR%:          year")
364        print("    %DIRECTOR%:      director")
365#       print("    %CARDID%:        ID of tuner card used to record show")
366#       print("    %CARDNAME%:      name of tuner card used to record show")
367#       print("    %SOURCEID%:      ID of video source used to record show")
368#       print("    %SOURCENAME%:    name of video source used to record show")
369        print("    %HOSTNAME%:      backend used to record show")
370        print("    %STORAGEGROUP%:  storage group containing recorded show")
371#       print("    %CHANNUM%:       ID of channel used to record show")
372#       print("    %CHANNAME%:      name of channel used to record show")
373        print("    %GENRE%:         first genre listed for recording")
374#       print("    %COUNTRY%:       first country listed for recording")
375
376def main():
377        fformat = None
378        tformat = None
379        jobid = None
380        chanid = None
381        rtime = None
382        skip = False
383        delete = False
384        try:
385                opts, args = getopt.getopt(sys.argv[1:], "hf", ["help","helpformat","fformat=","dformat=","chanid=","starttime=","skip","delete"])
386        except getopt.GetoptError, err:
387                print(str(err))
388                usage()
389                sys.exit(2)
390        for opt, arg in opts:
391                if opt in ("-h", "--help"):
392                        usage()
393                        sys.exit()
394                elif opt in ('-f', '--helpformat'):
395                        usage_format()
396                        sys.exit()
397                elif opt == '--dformat':
398                        dformat = arg
399                elif opt == '--fformat':
400                        fformat = arg
401                elif opt == '--chanid':
402                        chanid = int(arg)
403                elif opt == '--starttime':
404                        rtime = int(arg)
405                elif opt == '--skip':
406                        skip = True
407                elif opt == '--delete':
408                        delete = True
409               
410        if len(args):
411                jobid = int(args[0])
412
413        if chanid and rtime:
414                export = VIDEO(chanid,rtime)
415        elif jobid:
416                export = VIDEO(jobid)
417        elif fformat or dformat:
418                if fformat:
419                        mythdb = MythDB()
420                        mythdb.setSetting('TV Export File Format', fmt)
421                if dformat:
422                        mythdb = MythDB()
423                        mythdb.setSetting('TV Export Directory Format', fmt)
424                sys.exit()
425        else:
426                usage()
427                sys.exit(2)
428
429        if skip:
430                export.get_meta()
431                export.get_dest()
432        else:
433                export.copy()
434        export.write_meta()
435#       if delete:
436#               export.delete()
437
438if __name__ == "__main__":
439        main()