def test_datetime_000_13(self): """Test method 'MythTV.datetime.strptime()' with given timezone""" slt = self.lt.rsplit(" ", 1)[0] dt9 = slt.split(" ", 1)[1] #print(slt) tzlocal = datetime.localTZ() ts3 = datetime.strptime(slt, '%a %Y-%m-%d %H:%M:%S', tzlocal) #print(ts3, type(ts3)) # 2020-05-31 11:03:51+02:00 <class 'MythTV.utility.dt.datetime'> ts3_str = str(ts3).split("+")[0] #print(ts3_str) self.assertTrue( isinstance(ts3, MythTV.utility.dt.datetime), "Instance ''MythTV.datetime.strptime()' is not available") self.assertEqual( ts3_str, dt9, "Instance ''MythTV.datetime.strptime()' for local-time is wrong") sut = self.ut.rsplit(" ", 1)[0] dt10 = sut.split(" ", 1)[1] #print(sut) utctz = datetime.UTCTZ() ts4 = datetime.strptime(sut, '%a %Y-%m-%d %H:%M:%S', utctz) #print(ts4, type(ts4)) # 2020-05-31 11:03:51+02:00 <class 'MythTV.utility.dt.datetime'> ts4_str = str(ts4).split("+")[0] #print(ts4_str) self.assertTrue( isinstance(ts4, MythTV.utility.dt.datetime), "Instance ''MythTV.datetime.strptime()' is not available") self.assertEqual( ts4_str, dt10, "Instance ''MythTV.datetime.strptime()' for local-time is wrong")
def test_datetime_000_12(self): """Test method 'MythTV.datetime.strptime()' without timezone""" ts1 = datetime.strptime( self.lt, '%a %Y-%m-%d %H:%M:%S %Z') # Sat 2020-05-30 22:06:46 CEST #print(ts1, type(ts1)) # 2020-05-30 22:06:46+02:00 <class 'MythTV.utility.dt.datetime'> ts1_str = str(ts1).split("+")[0] #print(ts1_str) # 2020-05-30 22:06:46 self.assertTrue( isinstance(ts1, MythTV.utility.dt.datetime), "Instance ''MythTV.datetime.strptime()' is not available") dt8 = self.lt.split(" ", 1)[1] dt8 = dt8.rsplit(" ", 1)[0] #print(dt8) self.assertEqual( ts1_str, dt8, "Instance ''MythTV.datetime.strptime()' for local-time is wrong") ts2 = datetime.strptime( self.ut, '%a %Y-%m-%d %H:%M:%S %Z') # Sat 2020-05-30 20:06:46 UTC #print(ts2, type(ts2)) # 2020-05-30 20:06:46+02:00 <class 'MythTV.utility.dt.datetime'> ts2_str = str(ts2).split("+")[0] dt9 = self.ut.split(" ", 1)[1] dt9 = dt9.rsplit(" ", 1)[0] #print(dt9) self.assertEqual( ts2_str, dt9, "Instance ''MythTV.datetime.strptime()' for utc-time is wrong")
def runjob(jobid=None, chanid=None, starttime=None): db = MythDB() if jobid: job = Job(jobid, db=db) chanid = job.chanid starttime = job.starttime else: starttime = datetime.strptime(str(starttime), "%Y%m%d%H%M%S")+timedelta(hours=-5) rec = Recorded((chanid, starttime), db=db) timestr = time.strftime("%m-%d-%y %H:%M:%S") title_san = re.sub("\s", ".", rec.title) print title_san try: os.mkdir(log_dir) except OSError, e: pass
def runjob(jobid=None, chanid=None, starttime=None): db = MythDB() if jobid: job = Job(jobid, db=db) chanid = job.chanid starttime = job.starttime else: starttime = datetime.strptime(str(starttime), "%Y%m%d%H%M%S") + timedelta(hours=-5) rec = Recorded((chanid, starttime), db=db) timestr = time.strftime("%m-%d-%y %H:%M:%S") title_san = re.sub("\s", ".", rec.title) print title_san try: os.mkdir(log_dir) except OSError, e: pass
def buildNumbers(args, opts): # either option -N <inetref> <subtitle> e.g. -N 69 "Elizabeth Keen" # or option -N <inetref> <date time> e.g. -N 69 "2021-01-29 19:00:00" # or option -N <title> <subtitle> e.g. -N "The Blacklist" "Elizabeth Keen" # or option -N <title> <date time> e.g. -N "The Blacklist" "2021-01-29 19:00:00" from MythTV.utility import levenshtein from MythTV.utility.dt import posixtzinfo from MythTV.tvmaze import tvmaze_api as tvmaze from MythTV import datetime from lxml import etree from datetime import timedelta if opts.debug: print("Function 'buildNumbers' called with arguments: " + (" ".join(["'%s'" % i for i in args]))) # set the session if opts.session: tvmaze.set_session(opts.session) dtInLocalZone = None # ToDo: # below check shows a deficiency of the MythTV grabber API itself: # TV-Shows or Movies with an integer as title are not recognized correctly. # see https://www.mythtv.org/wiki/MythTV_Universal_Metadata_Format # and https://code.mythtv.org/trac/ticket/11850 try: inetref = int(args[0]) tvsubtitle = args[1] inetrefList = [inetref] except ValueError: tvtitle = args[0] tvsubtitle = args[1] inetrefList = [] # inetrefs for shows with title matches best_show_quality = 0.5 # require at least this quality on string match showlist = tvmaze.search_show(tvtitle) # It's problematic to make decisions solely upon the Levenshtein distance. # If the strings are really long or really short, a simple rule, such as # "accept any distance < 6" can provide misleading results. # To establish a more useful measurement, we'll use the Levenshtein # distance to figure out the ratio (0 - 1) of matching characters in the # longer string, and call this 'match_quality'. # "Risk", "Call" -> distance = 4 # match_quality = (4 - 4) / 4 = 0 # "In Sickness and in Health", "Sickness and Health" -> distance = 6 # match_quality = (25 - 6)/25 = .76 for show_info in showlist: try: inetref = int(show_info.id) distance = levenshtein(show_info.name.lower(), tvtitle.lower()) if len(tvtitle) > len(show_info.name): match_quality = float(len(tvtitle) - distance) / len(tvtitle) else: match_quality = float(len(show_info.name) - distance) / len(show_info.name) if match_quality >= best_show_quality: #if opts.debug: #print ('show_info =', show_info, ', match_quality =', match_quality) if match_quality == best_show_quality: inetrefList.append(inetref) else: # Any items previously appended for a lesser match need to be eliminated inetrefList = [inetref] best_show_quality = match_quality except (TypeError, ValueError): pass # check whether the 'subtitle' is really a timestamp try: dtInLocalZone = datetime.strptime( tvsubtitle, "%Y-%m-%d %H:%M:%S") # defaults to local timezone except ValueError: dtInLocalZone = None matchesFound = 0 best_ep_quality = 0.5 # require at least this quality on string match tree = etree.XML(u'<metadata></metadata>') for inetref in inetrefList: dtInTgtZone = None if dtInLocalZone: try: show_info = tvmaze.get_show(inetref) # Some cases have 'network' = None, but webChannel != None. If we # find such a case, we'll set show_network to the webChannel. show_network = show_info.network if show_network is None: show_network = show_info.streaming_service show_country = show_network.get('country') show_tz = show_country.get('timezone') dtInTgtZone = dtInLocalZone.astimezone(posixtzinfo(show_tz)) except (ValueError, AttributeError) as e: dtInTgtZone = None if dtInTgtZone: # get episode info based on inetref and datetime in target zone try: #print('get_show_episodes_by_date(', inetref, ',', dtInTgtZone, ')') episodes = tvmaze.get_show_episodes_by_date( inetref, dtInTgtZone) except SystemExit: episodes = [] time_match_list = [] early_match_list = [] minTimeDelta = timedelta(minutes=60) for i, ep in enumerate(episodes): epInTgtZone = datetime.fromIso(ep.timestamp, tz=posixtzinfo(show_tz)) durationDelta = timedelta(minutes=ep.duration) # Consider it a match if the recording starts late, but within the duration of the show. if epInTgtZone <= dtInTgtZone < epInTgtZone + durationDelta: # Recording start time is within the range of this episode if opts.debug: print('Recording in range of inetref %d, season %d, episode %d (%s ... %s)' \ % (inetref, ep.season, ep.number, epInTgtZone, epInTgtZone+durationDelta)) time_match_list.append(i) minTimeDelta = timedelta(minutes=0) # Consider it a match if the recording is a little bit early. This helps cases # where you set up a rule to record, at say 9:00, and the broadcaster uses a # slightly odd start time, like 9:05. elif epInTgtZone - minTimeDelta <= dtInTgtZone < epInTgtZone: # Recording started earlier than this episode, so see if it's the closest match if epInTgtZone - dtInTgtZone == minTimeDelta: if opts.debug: print('adding episode to closest list', epInTgtZone - dtInTgtZone, '\n') early_match_list.append(i) elif epInTgtZone - dtInTgtZone < minTimeDelta: if opts.debug: print('this episode is new closest', epInTgtZone - dtInTgtZone, '\n') minTimeDelta = epInTgtZone - dtInTgtZone early_match_list = [i] if not time_match_list: # No exact matches found, so use the list of the closest episode(s) time_match_list = early_match_list if time_match_list: for ep_index in time_match_list: season_nr = str(episodes[ep_index].season) episode_id = episodes[ep_index].id item = buildSingleItem(inetref, season_nr, episode_id) if item is not None: tree.append(item.toXML()) matchesFound += 1 else: # get episode based on subtitle episodes = tvmaze.get_show_episode_list(inetref) min_dist_list = [] for i, ep in enumerate(episodes): if 0 and opts.debug: print("tvmaze.get_show_episode_list(%s) returned :" % inetref) for k, v in ep.__dict__.items(): print(k, " : ", v) distance = levenshtein(ep.name, tvsubtitle) if len(tvsubtitle) >= len(ep.name): match_quality = float(len(tvsubtitle) - distance) / len(tvsubtitle) else: match_quality = float(len(ep.name) - distance) / len( ep.name) #if opts.debug: #print('inetref', inetref, 'episode =', ep.name, ', distance =', distance, ', match_quality =', match_quality) if match_quality >= best_ep_quality: if match_quality == best_ep_quality: min_dist_list.append(i) if opts.debug: print( '"%s" added to best list, match_quality = %g' % (ep.name, match_quality)) else: # Any items previously appended for a lesser match need to be eliminated tree = etree.XML(u'<metadata></metadata>') min_dist_list = [i] best_ep_quality = match_quality if opts.debug: print('"%s" is new best match_quality = %g' % (ep.name, match_quality)) # The list is constructed in order of oldest season to newest. # If episodes with equivalent match quality show up in multiple # seasons, we want to list the most recent first. To accomplish # this, we'll process items starting at the end of the list, and # proceed to the beginning. while min_dist_list: ep_index = min_dist_list.pop() season_nr = str(episodes[ep_index].season) episode_id = episodes[ep_index].id if opts.debug: episode_nr = str(episodes[ep_index].number) print("tvmaze.get_show_episode_list(%s) returned :" % inetref) print("with season : %s and episode %s" % (season_nr, episode_nr)) print( "Chosen episode index '%d' based on match quality %g" % (ep_index, best_ep_quality)) # we have now inetref, season, episode_id item = buildSingleItem(inetref, season_nr, episode_id) if item is not None: tree.append(item.toXML()) matchesFound += 1 if matchesFound > 0: print_etree( etree.tostring(tree, encoding='UTF-8', pretty_print=True, xml_declaration=True)) else: if dtInLocalZone: raise Exception( "Cannot find any episode with timestamp matching '%s'." % tvsubtitle) else: # tvmaze.py -N 4711 "Episode 42" raise Exception("Cannot find any episode with subtitle '%s'." % tvsubtitle)
def runjob(jobid=None, chanid=None, starttime=None, tzoffset=None): global estimateBitrate db = MythDB() if jobid: job = Job(jobid, db=db) chanid = job.chanid utcstarttime = job.starttime else: job=None; utcstarttime = datetime.strptime(starttime, "%Y%m%d%H%M%S") utcstarttime = utcstarttime + timedelta(hours=tzoffset) if debug: print 'chanid "%s"' % chanid print 'utcstarttime "%s"' % utcstarttime rec = Recorded((chanid, utcstarttime), db=db); utcstarttime = rec.starttime; starttime_datetime = utcstarttime # reformat 'starttime' for use with mythtranscode/ffmpeg/mythcommflag starttime = str(utcstarttime.utcisoformat().replace(u':', '').replace(u' ', '').replace(u'T', '').replace('-', '')) if debug: print 'mythtv format starttime "%s"' % starttime input_filesize = rec.filesize if rec.commflagged: if debug: print 'Recording has been scanned to detect commerical breaks.' waititer=1 keepWaiting = True while keepWaiting == True: keepWaiting=False; for index,jobitem in reversed(list(enumerate(db.searchJobs(chanid=chanid, starttime=starttime_datetime)))): if jobitem.type == jobitem.COMMFLAG: # Commercial flagging job if debug: print 'Commercial flagging job detected with status %s' % jobitem.status if jobitem.status == jobitem.RUNNING: # status = RUNNING? job.update({'status':job.PAUSED, 'comment':'Waited %d secs for the commercial flagging job' % (waititer*POLL_INTERVAL) \ + ' currently running on this recording to complete.'}) if debug: print 'Waited %d secs for the commercial flagging job' % (waititer*POLL_INTERVAL) \ + ' currently running on this recording to complete.' time.sleep(POLL_INTERVAL); keepWaiting=True waititer = waititer + 1 break else: if debug: print 'Recording has not been scanned to detect/remove commercial breaks.' if require_commflagged: if jobid: job.update({'status':job.RUNNING, 'comment':'Required commercial flagging for this file is not found.' + 'Flagging commercials and cancelling any queued commercial flagging.'}) # cancel any queued job to flag commercials for this recording and run commercial flagging in this script for index,jobitem in reversed(list(enumerate(db.searchJobs(chanid=chanid,starttime=starttime_datetime)))): if debug: if index==0: print jobitem.keys() print index,jobitem.id,jobitem.chanid if jobitem.type == jobitem.COMMFLAG: # Commercial flagging job if jobitem.status == jobitem.RUNNING: # status = RUNNING? jobitem.cmds = jobitem.STOP # stop command from the frontend to stop the commercial flagging job #jobitem.setStatus(jobitem.CANCELLED) #jobitem.setComment('Cancelled: Transcode command ran commercial flagging for this recording.') jobitem.update({'status':jobitem.CANCELLED, 'comment':'A user transcode job ran commercial flagging for' + ' this recording and cancelled this job.'}) if debug: print 'Flagging Commercials...' # Call "mythcommflag --chanid $CHANID --starttime $STARTTIME" task = System(path='mythcommflag', db=db) try: output = task('--chanid "%s"' % chanid, '--starttime "%s"' % starttime, '2> /dev/null') except MythError, e: # it seems mythcommflag always exits with an decoding error "eno: Unknown error 541478725 (541478725)" pass
def runjob(jobid=None, chanid=None, starttime=None, tzoffset=None, maxWidth=maxWidth, maxHeight=maxHeight, sdonly=0, burncc=0, usemkv=0, overwrite=1): global estimateBitrate db = MythDB() try: if jobid: job = Job(jobid, db=db) chanid = job.chanid utcstarttime = job.starttime else: job = None utcstarttime = datetime.strptime(starttime, "%Y%m%d%H%M%S") utcstarttime = utcstarttime + timedelta(hours=tzoffset) if debug: print 'chanid "%s"' % chanid print 'utcstarttime "%s"' % utcstarttime rec = Recorded((chanid, utcstarttime), db=db) utcstarttime = rec.starttime starttime_datetime = utcstarttime # reformat 'starttime' for use with mythtranscode/HandBrakeCLI/mythcommflag starttime = str(utcstarttime.utcisoformat().replace(u':', '').replace( u' ', '').replace(u'T', '').replace('-', '')) if debug: print 'mythtv format starttime "%s"' % starttime input_filesize = rec.filesize if rec.commflagged: if debug: print 'Recording has been scanned to detect commerical breaks.' waititer = 1 keepWaiting = True while keepWaiting == True: keepWaiting = False for index, jobitem in reversed( list( enumerate( db.searchJobs(chanid=chanid, starttime=starttime_datetime)))): if jobitem.type == jobitem.COMMFLAG: # Commercial flagging job if debug: print 'Commercial flagging job detected with status %s' % jobitem.status if jobitem.status == jobitem.RUNNING: # status = RUNNING? job.update({'status':job.PAUSED, 'comment':'Waited %d secs for the commercial flagging job' % (waititer*POLL_INTERVAL) \ + ' currently running on this recording to complete.'}) if debug: print 'Waited %d secs for the commercial flagging job' % (waititer*POLL_INTERVAL) \ + ' currently running on this recording to complete.' time.sleep(POLL_INTERVAL) keepWaiting = True waititer = waititer + 1 break else: if debug: print 'Recording has not been scanned to detect/remove commercial breaks.' if require_commflagged: if jobid: job.update({ 'status': job.RUNNING, 'comment': 'Required commercial flagging for this file is not found.' + 'Flagging commercials and cancelling any queued commercial flagging.' }) # cancel any queued job to flag commercials for this recording and run commercial flagging in this script for index, jobitem in reversed( list( enumerate( db.searchJobs(chanid=chanid, starttime=starttime_datetime)))): if debug: if index == 0: print jobitem.keys() print index, jobitem.id, jobitem.chanid if jobitem.type == jobitem.COMMFLAG: # Commercial flagging job if jobitem.status == jobitem.RUNNING: # status = RUNNING? jobitem.cmds = jobitem.STOP # stop command from the frontend to stop the commercial flagging job #jobitem.setStatus(jobitem.CANCELLED) #jobitem.setComment('Cancelled: Transcode command ran commercial flagging for this recording.') jobitem.update({ 'status': jobitem.CANCELLED, 'comment': 'A user transcode job ran commercial flagging for' + ' this recording and cancelled this job.' }) if debug: print 'Flagging Commercials...' # Call "mythcommflag --chanid $CHANID --starttime $STARTTIME" task = System(path='mythcommflag', db=db) try: output = task('--chanid "%s"' % chanid, '--starttime "%s"' % starttime, '2> /dev/null') except MythError, e: # it seems mythcommflag always exits with an decoding error "eno: Unknown error 541478725 (541478725)" pass #print 'Command failed with output:\n%s' % e.stderr #if jobid: # job.update({'status':304, 'comment':'Flagging commercials failed'}) #sys.exit(e.retcode) sg = findfile('/' + rec.basename, rec.storagegroup, db=db) if sg is None: print 'Local access to recording not found.' sys.exit(1) infile = os.path.join(sg.dirname, rec.basename) #TODO: set overWrite to 0 if infile is m4v or mkv (already converted) #tmpfile = '%s.tmp' % infile.rsplit('.',1)[0] outtitle = rec.title.replace("&", "and") outtitle = re.sub('[^A-Za-z0-9 ]+', '', outtitle) filetype = 'm4v' #DEBUG CODE TO FIND OBJECT STRUCT: #print '{}'.format(dir(rec.getProgram())) #print '{}'.format(rec.getProgram().year) if usemkv == 1: filetype = 'mkv' #print '!{}!'.format(rec.programid[0:2]) if rec.season > 0 and rec.episode > 0: #if there are seasons and episode numbers in the recording data outtitle = '{0:s} S{1:d} E{2:02d}'.format(outtitle, rec.season, rec.episode) elif rec.programid[0:2] == 'MV' and str(rec.getProgram().year).isdigit( ): #if it's a movie and has an original air date for when it came out outtitle = '{} ({})'.format(outtitle, rec.getProgram().year) elif rec.programid[ 0: 2] == 'MV' and rec.originalairdate != None and rec.originalairdate > datetime.date( datetime(1, 1, 1, 0, 0) ): #if it's a movie and has an original air date for when it came out outtitle = '{} ({})'.format(outtitle, rec.originalairdate.year) elif 'Sports' in rec.category: #if it's sports outtitle = '{}-{}-{}'.format( outtitle, re.sub('[^A-Za-z0-9 ]+', '', rec.subtitle), str(rec.starttime.strftime("%Y%m%d"))) elif rec.programid[0:2] == 'SH' and (' News ' in rec.title or rec.category == 'News'): #if it's a news show outtitle = '{}-{}'.format(outtitle, str(rec.starttime.strftime("%Y%m%d"))) elif rec.originalairdate != None and rec.originalairdate > datetime.date( datetime(1, 1, 1, 0, 0)): #if it has an original air date outtitle = '{} {}'.format( outtitle, str(rec.originalairdate.strftime("%Y%m%d"))) else: outtitle = '{} {}'.format(outtitle, str(rec.starttime.strftime("%Y%m%d"))) outtitle = '{}.{}'.format(outtitle, filetype) outfile = os.path.join(sg.dirname, outtitle) tmpfile = '{}.{}'.format( outfile.rsplit('.', 1)[0], infile.rsplit('.', 1)[1]) if tmpfile == infile: tmpfile = '{}.tmp'.format(infile.rsplit('.', 1)[0]) if (overwrite == 0): # If not overwritting the file, use the export folder outfile = os.path.join(exportFolder, outtitle) if debug: print 'overwrite is 0. outfile "{}"'.format(outfile) if os.path.isfile(outfile) or infile == outfile: # If outfile exists already, create a new name for the file. outfile = '{}-{}.{}'.format( outfile.rsplit('.', 1)[0], str(rec.starttime.strftime("%Y%m%d")), filetype) if os.path.isfile(tmpfile): # If the infile and tmpfile are the same, create a new name for the tmpfile tmpfile = '{}-{}.tmp'.format( outfile.rsplit('.', 1)[0], str(rec.starttime.strftime("%Y%m%d"))) if os.path.isfile(tmpfile): # If tmp exists already, create a new name for the file. outfile = '{}-{}.tmp'.format( tmpfile.rsplit('.', 1)[0], str(rec.starttime.strftime("%Y%m%d"))) if debug: print 'tmp exists. outfile "{}"'.format(outfile) if debug: print 'infile "{}"'.format(infile) print 'tmpfile "{}"'.format(tmpfile) print 'outfile "{}"'.format(outfile) #add_metadata(db, jobid, debug, job, rec, filetype, tmpfile) clipped_bytes = 0 # If selected, create a cutlist to remove commercials via mythtranscode by running: # mythutil --gencutlist --chanid $CHANID --starttime $STARTTIME if generate_commcutlist: if jobid: job.update({ 'status': job.RUNNING, 'comment': 'Generating Cutlist for commercial removal' }) task = System(path='mythutil', db=db) try: output = task('--gencutlist', '--chanid "%s"' % chanid, '--starttime "%s"' % starttime) except MythError, e: print 'Command "mythutil --gencutlist" failed with output:\n%s' % e.stderr if jobid: job.update({ 'status': job.ERRORED, 'comment': 'Generation of commercial Cutlist failed' }) sys.exit(e.retcode)