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
Example #7
0
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")
Example #8
0
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)