def getShow(showTitle): html = getHtmlByTitle(showTitle) if html == None: logging.info("Cannot get show, not html returned from tvdb") return None soup = BeautifulSoup(html, 'lxml') showTitle = soup.find('div', {'class', 'crumbs'}).findAll('a')[2].text.strip() allEpisodes = soup.findAll("li", {"class", "list-group-item"}) episodeList = [] for e in allEpisodes: thisEp = Episode() try: thisEp.epNum = e.find("span", {"class", "episode-label"}).text thisEp.episodeName = e.find("a").text.strip() thisEp.airdate = e.find('ul').find('li').text thisEp.description = e.find('p').text episodeList.append(thisEp) except: #If there is any trouble getting date we just skip that episode (good enough is good enough) continue return Show(showTitle, episodeList)
def fuzzyMatch(title,subtitle,show,minRatio): found = list() seasons = sorted(show.keys(),reverse=True) bestRatio = 0 for season in seasons: for ep in show[season].keys(): ratio = fuzzyScore(show[season][ep]['episodeName'],subtitle.decode("utf-8")) if ratio > bestRatio: bestRatio = ratio #print(ratio) if ratio > minRatio: found.append((season,ep,ratio)) if len(found) == 0: logging.info("fuzzyMatch found a ratio of %d, but that's below the minimum of %d.",bestRatio,minRatio) return None else: topIdxRatio = max(enumerate(map(itemgetter(-1), found)),key=itemgetter(1)) sameScore = list() for result in found: if result[-1] == topIdxRatio[1]: sameScore.append(result) if len(sameScore) == 1: return sameScore[0] else: logging.info("Multiple matches with ratio of %d found!",topIdxRatio[1]) logging.info("Source information: %s :: %s", title, subtitle) for ep in sameScore: logging.info("Matches with S%dE%d :: %s", ep[0], ep[1], show[ep[0]][ep[1]]['episodeName']) logging.info("Fuzzy match failed, this needs to be sorted out manually") return None
def searchByDate(showTitle,datestring): """ Search theTvDb data structure for original air date Return show information object if found, None if not """ showInfo = getShow(str(showTitle)) foundShows = list() for season in showInfo.keys(): for episode in showInfo[season].keys(): if showInfo[season][episode]['firstAired'] == datestring: logging.info("Found: %s",showInfo[season][episode]['episodeName']) foundShows.append(showInfo[season][episode]) return foundShows
def airdateFuzzyMatch(filename, minRatio=85): """ Compares airdate and fuzzy-matched title for identifying episodes When exactMatch doesn't work, this should be run before falling back to pure fuzzyMatch Consider 'Wheel of Fortune' -- episcodeNames will be "Weekly Theme 1", "Weekly Theme 2", etc. Trying to fuzzy match these will be difficult as only 1 character is different A better way is checking the airdate, and fuzzy matching to the title. Hits on both are adequate. """ #Use MythTV Python Bindings to get show, episode, and airdate ''' targetProgram = getProgramObjectFromFilename(filename, getDbObject(), getBeObject()) if targetProgram == None: return None ''' systemCmd = "/usr/bin/python3 mythObjGetter.py " + filename showGetterCode = os.system(systemCmd) if (showGetterCode != 0): return None targetProgram = pickle.load(open("showObj.p","rb")) print(targetProgram) #Get thetvdb.com data structure for complete series showTitle = targetProgram['title'] airdate = targetProgram['airdate'].strftime("%Y-%m-%d") epTitle = targetProgram['subtitle'] #show = getShow(showTitle) #tvdb: was there an episode on our target airdate? tvdbShows = searchByDate(showTitle,airdate) if len(tvdbShows) == 0: return None #tvdb: do we have a high confience fuzzy match with the episodeName on that airdate? fuzzyMatch = [0,None] for show in tvdbShows: showRatio = fuzzyScore(show['episodeName'],epTitle) if showRatio > fuzzyMatch[0]: fuzzyMatch[0] = showRatio fuzzyMatch[1] = show #return season, ep, and ratio, otherwise return None as we only care about that airdate if fuzzyMatch[0] >= minRatio: epName = fuzzyMatch[1]['episodeName'] season = fuzzyMatch[1]['airedSeason'] episode = fuzzyMatch[1]['airedEpisodeNumber'] logging.info("Fuzzy matched on airdate: %s :: Season %s Episode %s :: %s",showTitle,season,episode,epName) return [season, episode, fuzzyMatch[0]] else: return None
def getEpNum_mythtv(targetProgram): if targetProgram['season'] != None and targetProgram['episode'] != None: seasonEpisode = 'S' + str(targetProgram['season']) + 'E' + str( targetProgram['episode']) logging.info("Episode Found: %s" % seasonEpisode) ep = Episode() ep.epNum = seasonEpisode ep.episodeName = targetProgram['subtitle'] ep.description = targetProgram['description'] filenameIsSet = ep.setSeriesAndFilename(targetProgram['title']) if filenameIsSet: return ep else: logging.info( "Error: failed to set filename from MythTV season/episode data" ) return None
def searchByDate(show, airdateObj): """ Search theTvDb data structure for original air date Return list of episode objects if found, empty if not """ foundEpisodes = list() for ep in show.episodes: try: tvdbAirdate = datetime.datetime.strptime(ep.airdate, '%B %d, %Y').date() except: #Skip this one. Good enough is good enough continue if tvdbAirdate == airdateObj: logging.info("Found: %s", ep.epNum) foundEpisodes.append(ep) return foundEpisodes
def deleteProgram(basename): """ Delete the recording with filename==basename This deletes the physical file as well as the MythTV database entries """ basename = os.path.split(basename)[-1] logging.info("Attempting to delete from mythtv: %s", basename) db1 = getDbObject() be1 = getBeObject(db1) showObject = getProgramObjectFromFilename(basename, db1, be1) if showObject == None: logging.info( "Cannot delete show: Unable to get show object from mythtv: %s", basename) return logging.info("Deleting program: %s :: %s (filename %s)", showObject['title'], showObject['subtitle'], basename) deletedShow = be1.deleteRecording(showObject, force=True) #Return -1 means success if deletedShow == -1: logging.info("Successfully deleted program")
def main(): recordingDir = sys.argv[1] recordingFile = sys.argv[2] logging.info("======================") logging.info("Attempting to move to showings: %s", recordingFile) episodeObj = getProgramObjectFromFilename(recordingFile, getDbObject(), getBeObject()) newFilename = "%s-%s-%s-%s___%s.ts" % ( episodeObj['title'], str(episodeObj['syndicatedepisode']), episodeObj['subtitle'], str(episodeObj['airdate']), str(episodeObj['starttime']).replace(' ', '_')) if os.path.isdir(storageDir) == False: logging.info("storageDir doesn't exist, aborting: %s", storageDir) sys.exit(1) fileSource = os.path.join(recordingDir, recordingFile) fileDestination = os.path.join(storageDir, newFilename) if os.path.exists(fileSource) == False: logging.error("ERROR: aborting, source file doesn't exist: %s", fileSource) sys.exit(1) if os.path.exists(fileDestination): logging.error("ERROR: aborting, file already exists at: %s", fileDestination) sys.exit(1) logging.info("Copying file: %s to: %s", fileSource, fileDestination) copyfile(fileSource, fileDestination) os.chmod(fileDestination, 0o777) logging.info("File copied successfully") logging.info("Deleting %s", recordingFile) deleteProgram(recordingFile) logging.info("Operation complete. Exiting.") sys.exit(0)
def findEpisodeFilename(showTitle,epTitle,fuzzyRatio=85,recordingFilename=None): #Get DB info show = getShow(showTitle) filenamePreamble = showTitle.replace(' ','_') logging.info("Trying exact match...") exactEpisode = exactMatch(testTitle,epTitle,show) if exactEpisode != None: logging.info("Exact match! Season: %d Episode: %d", exactEpisode[0], exactEpisode[1]) return filenamePreamble + "-S" + str(exactEpisode[0]) + "E" + str(exactEpisode[1]) logging.info("No exact match found.") if (recordingFilename != None): logging.info("Trying fuzzy match based on air date...") fuzzyDate = airdateFuzzyMatch(recordingFilename,min(70,fuzzyRatio)) if fuzzyDate != None: logging.info("Fuzzy matched with air date! Season: %d Episode: %d", fuzzyDate[0], fuzzyDate[1]) return filenamePreamble + "-S" + str(fuzzyDate[0]) + "E" + str(fuzzyDate[1]) logging.info("No fuzzy match based on air date.") logging.info("Trying generic fuzzy match of episode name.") fuzzyEpisode = fuzzyMatch(testTitle,epTitle,show,fuzzyRatio) if fuzzyEpisode != None: logging.info("Fuzzy match ratio: %d Season: %d Episode: %d", fuzzyEpisode[2], fuzzyEpisode[0], fuzzyEpisode[1]) return filenamePreamble + "-S" + str(fuzzyEpisode[0]) + "E" + str(fuzzyEpisode[1]) logging.info("No subtitle match could be found. Exiting.") return 0
def identifyMythtvEpisode(recordingFilename, fuzzyRatio=85): logging.info("Getting program info from mythtv using filename: %s", recordingFilename) targetProgram = getProgramObjectFromFilename(recordingFilename, getDbObject(), getBeObject()) #If MythTV metadata is enabled the season and episode may already be known episode = getEpNum_mythtv(targetProgram) logging.info("Checking MythTV for Season/Episode Numbers") if episode != None: return episode else: logging.info("Can't find season and episode numbers from MythTV") #Get DB info logging.info("Loading show info from tvdb: %s", targetProgram['title']) show = getShow(targetProgram['title']) #Try exact expisode title match logging.info("Trying exact match...") exactEpisode = exactMatch(show, targetProgram['subtitle']) if exactEpisode != None: logging.info("Exact match! %s", exactEpisode.epNum) filenameIsSet = exactEpisode.setSeriesAndFilename(show.title) if filenameIsSet: return exactEpisode else: logging.info("Error: failed to set filename from exact match data") logging.info("No exact match found.") logging.info("Trying fuzzy match of episode name.") fuzzyEpisode = fuzzyMatch(show, targetProgram['subtitle'], fuzzyRatio, targetProgram['airdate']) if fuzzyEpisode != None: logging.info("Fuzzy match ratio: %s", fuzzyEpisode.epNum) filenameIsSet = fuzzyEpisode.setSeriesAndFilename(show.title) if filenameIsSet: return fuzzyEpisode else: logging.info("Error: failed to set filename from fuzzy match data") logging.info("No episode match could be found. Exiting.") return 0
def fuzzyMatch(show, subtitle, minRatio, airdateObj=None): found = list() bestRatio = 0 for ep in show.episodes: ratio = fuzzyScore(ep.episodeName, subtitle) if ratio > bestRatio: bestRatio = ratio if ratio > minRatio: found.append((ep, ratio)) if len(found) == 0: logging.info( "fuzzyMatch found a ratio of %d, but that's below the minimum of %d.", bestRatio, minRatio) return None else: topIdxRatio = max(enumerate(map(itemgetter(-1), found)), key=itemgetter(1)) sameScore = list() for result in found: if result[-1] == topIdxRatio[1]: sameScore.append(result) if len(sameScore) == 1: return sameScore[0][0] else: logging.info("Multiple matches with ratio of %d found!", topIdxRatio[1]) logging.info("Source information: %s :: %s", show.title, subtitle) for scored in sameScore: logging.info("Matches with %s :: %s", scored[0].epNum, scored[0].episodeName) if isinstance(airdateObj, datetime.date): logging.info("Attempting to narrow by airdate: %s", airdateObj) airdateHits = searchByDate(show, airdateObj) dateFiltered = list() if len(airdateHits) != 0: for ep in airdateHits: for s in sameScore: if s[0].epNum == ep.epNum: dateFiltered.append(ep) if len(dateFiltered) == 1: logging.info( "Success, episode identified by fuzzy match and airdate: %s", dateFiltered[0].epNum) return dateFiltered[0] else: logging.info( "Couldn't narrow results based on airdate") logging.info( "Fuzzy match failed, this needs to be sorted out manually") return None
def main(): recordingFile = sys.argv[1] logging.info("======================") logging.info("Attempting tvdb match to: %s", recordingFile) matchedEpisode = identifyMythtvEpisode(recordingFilename=recordingFile) if matchedEpisode != None: if os.path.isdir(storageDir) == False: logging.info("storageDir doesn't exist, aborting.") sys.exit(1) if os.path.isdir(os.path.join(storageDir, matchedEpisode.seriestitle)) == False: logging.info("making directory for this show: %s", os.path.join(storageDir, matchedEpisode.seriestitle)) os.mkdir(os.path.join(storageDir, matchedEpisode.seriestitle), 0o0777) os.chmod(os.path.join(storageDir, matchedEpisode.seriestitle), 0o0777) fileDestination = os.path.join( storageDir, matchedEpisode.seriestitle, matchedEpisode.filename + os.path.splitext(recordingFile)[-1]) if os.path.exists(fileDestination): logging.error("ERROR: aborting, file already exists at: %s", fileDestination) sys.exit(1) logging.info("Copying file: %s to: %s", recordingFile, fileDestination) copyfile(os.path.join(recordingDir, recordingFile), fileDestination) os.chmod(fileDestination, 0o777) logging.info("File copied successfully") logging.info("Deleting %s", recordingFile) deleteMythRecording(recordingFile) logging.info("Operation complete. Exiting.") sys.exit(0) sys.exit(1)