2022-02-19 04:58:40 -06:00
#!/usr/bin/env python3
2021-04-09 09:13:06 -05:00
import csv
import json
2022-02-19 05:34:54 -06:00
import logging
2022-02-19 07:15:27 -06:00
import os
2021-04-09 09:13:06 -05:00
import re
import sys
2022-02-19 04:58:40 -06:00
import time
from datetime import datetime
2022-02-19 07:15:27 -06:00
from pathlib import Path
2021-04-09 09:13:06 -05:00
2022-02-19 04:58:40 -06:00
import trakt . core
from tinydb import Query , TinyDB
2022-02-19 07:15:27 -06:00
from trakt import init
2021-11-22 14:40:26 -06:00
from trakt . tv import TVShow
2022-02-19 05:34:54 -06:00
# Setup logger
logging . basicConfig (
2022-02-19 07:15:27 -06:00
format = " %(asctime)s [ %(levelname)7s ] :: %(message)s " ,
2022-02-19 05:34:54 -06:00
level = logging . INFO ,
2022-02-19 07:15:27 -06:00
datefmt = " % Y- % m- %d % H: % M: % S " ,
2022-02-19 05:34:54 -06:00
)
2021-04-09 11:13:11 -05:00
# Adjust this value to increase/decrease your requests between episodes.
2022-02-19 05:34:54 -06:00
# Make to remain within the rate limit: https://trakt.docs.apiary.io/#introduction/rate-limiting
DELAY_BETWEEN_EPISODES_IN_SECONDS = 0.75
2021-04-09 11:13:11 -05:00
2021-04-09 09:13:06 -05:00
# Create a database to keep track of completed processes
2022-02-19 05:07:17 -06:00
database = TinyDB ( " localStorage.json " )
syncedEpisodesTable = database . table ( " SyncedEpisodes " )
userMatchedShowsTable = database . table ( " TvTimeTraktUserMatched " )
2021-04-09 09:13:06 -05:00
class Expando ( object ) :
pass
2022-02-19 07:15:27 -06:00
def isAuthenticated ( ) :
with open ( f " { Path . home ( ) } /.pytrakt.json " ) as f :
data = json . load ( f )
daysBeforeExpiration = (
datetime . fromtimestamp ( data [ " OAUTH_EXPIRES_AT " ] ) - datetime . now ( )
) . days
if daysBeforeExpiration < 1 :
return False
return True
2021-04-09 09:13:06 -05:00
def getConfiguration ( ) :
configEx = Expando ( )
2022-02-19 05:07:17 -06:00
with open ( " config.json " ) as f :
2021-04-09 09:13:06 -05:00
data = json . load ( f )
configEx . TRAKT_USERNAME = data [ " TRAKT_USERNAME " ]
configEx . CLIENT_ID = data [ " CLIENT_ID " ]
configEx . CLIENT_SECRET = data [ " CLIENT_SECRET " ]
configEx . GDPR_WORKSPACE_PATH = data [ " GDPR_WORKSPACE_PATH " ]
2021-04-09 16:05:46 -05:00
CONFIG_SINGLETON = configEx
return CONFIG_SINGLETON
config = getConfiguration ( )
# Return the path to the CSV file contain the watched episode data from TV Time
def getWatchedShowsPath ( ) :
return config . GDPR_WORKSPACE_PATH + " /seen_episode.csv "
2021-04-09 18:39:10 -05:00
def getFollowedShowsPath ( ) :
return config . GDPR_WORKSPACE_PATH + " /followed_tv_show.csv "
2021-04-09 16:05:46 -05:00
def initTraktAuth ( ) :
2022-02-19 07:15:27 -06:00
if isAuthenticated ( ) :
return True
2021-04-09 16:05:46 -05:00
# Set the method of authentication
trakt . core . AUTH_METHOD = trakt . core . OAUTH_AUTH
2022-02-19 05:07:17 -06:00
return init (
config . TRAKT_USERNAME ,
store = True ,
client_id = config . CLIENT_ID ,
client_secret = config . CLIENT_SECRET ,
)
2021-04-09 16:05:46 -05:00
# With a given title, check if it contains a year (e.g Doctor Who (2005))
# and then return this value, with the title and year removed to improve
# the accuracy of Trakt results.
2021-04-09 09:13:06 -05:00
def getYearFromTitle ( title ) :
ex = Expando ( )
try :
2021-04-09 16:05:46 -05:00
# Use a regex expression to get the value within the brackets e.g The Americans (2017)
2021-04-09 09:13:06 -05:00
yearSearch = re . search ( r " \ (([A-Za-z0-9_]+) \ ) " , title )
yearValue = yearSearch . group ( 1 )
2021-04-09 16:05:46 -05:00
# Then, get the title without the year value included
2022-02-19 05:07:17 -06:00
titleValue = title . split ( " ( " ) [ 0 ] . strip ( )
2021-04-09 16:05:46 -05:00
# Put this together into an object
2021-04-09 09:13:06 -05:00
ex . titleWithoutYear = titleValue
ex . yearValue = int ( yearValue )
return ex
2022-02-19 07:15:27 -06:00
except Exception :
2021-04-09 16:05:46 -05:00
# If the above failed, then the title doesn't include a year
# so return the object as is.
2021-04-09 09:13:06 -05:00
ex . titleWithoutYear = title
ex . yearValue = - 1
return ex
2022-02-19 05:07:17 -06:00
2021-04-09 16:05:46 -05:00
# Shows in TV Time are often different to Trakt.TV - in order to improve results and automation,
# calculate how many words are in the title, and return true if more than 50% of the title is a match,
# It seems to improve automation, and reduce manual selection....
2021-04-09 09:13:06 -05:00
def checkTitleNameMatch ( tvTimeTitle , traktTitle ) :
2021-04-09 16:05:46 -05:00
# If the name is a complete match, then don't bother comparing them!
2021-04-09 09:13:06 -05:00
if tvTimeTitle == traktTitle :
return True
# Split the TvTime title
tvTimeTitleSplit = tvTimeTitle . split ( )
2021-04-09 16:05:46 -05:00
# Create an array of words which are found in the Trakt title
2021-04-09 09:13:06 -05:00
wordsMatched = [ ]
2021-04-09 16:05:46 -05:00
# Go through each word of the TV Time title, and check if it's in the Trakt title
2021-04-09 09:13:06 -05:00
for word in tvTimeTitleSplit :
if word in traktTitle :
wordsMatched . append ( word )
2021-04-09 16:05:46 -05:00
# Then calculate what percentage of words matched
2021-04-09 09:13:06 -05:00
quotient = len ( wordsMatched ) / len ( traktTitle . split ( ) )
percentage = quotient * 100
2021-04-09 16:05:46 -05:00
# If more than 50% of words in the TV Time title exist in the Trakt title,
# then return the title as a possibility to use
2021-04-09 09:13:06 -05:00
return percentage > 50
2022-02-19 05:07:17 -06:00
2021-04-09 16:05:46 -05:00
# Using TV Time data (Name of Show, Season No and Episode) - find the corresponding show
# in Trakt.TV either by automation, or asking the user to confirm.
2021-04-09 09:13:06 -05:00
def getShowByName ( name , seasonNo , episodeNo ) :
2021-04-09 16:05:46 -05:00
# Parse the TV Show's name for year, if one is present in the string
2021-04-09 09:13:06 -05:00
titleObj = getYearFromTitle ( name )
2021-04-09 16:05:46 -05:00
# Create a boolean to indicate if the title contains a year,
# this is used later on to improve the accuracy of picking
# from search results
2021-04-09 09:13:06 -05:00
doesTitleIncludeYear = titleObj . yearValue != - 1
2021-04-09 16:05:46 -05:00
# If the title contains a year, then replace the local variable with the stripped version
2021-04-09 09:13:06 -05:00
if doesTitleIncludeYear :
name = titleObj . titleWithoutYear
2021-04-09 16:05:46 -05:00
# Request the Trakt API for search results, using the name
2021-04-09 09:13:06 -05:00
tvSearch = TVShow . search ( name )
2021-04-09 16:05:46 -05:00
# Create an array of shows which have been matched
2021-04-09 09:13:06 -05:00
showsWithSameName = [ ]
2021-04-09 16:05:46 -05:00
# Go through each result from the search
2021-04-09 09:13:06 -05:00
for show in tvSearch :
2021-04-09 16:05:46 -05:00
# Check if the title is a match, based on our conditions (e.g over 50% of words match)
2021-04-09 09:13:06 -05:00
if checkTitleNameMatch ( name , show . title ) :
2021-04-09 16:05:46 -05:00
# If the title included the year of broadcast, then we can be more picky in the results
# to look for a show with a broadcast year that matches
2022-02-19 07:15:27 -06:00
if doesTitleIncludeYear :
2021-04-09 16:05:46 -05:00
# If the show title is a 1:1 match, with the same broadcast year, then bingo!
2021-04-09 09:13:06 -05:00
if ( name == show . title ) and ( show . year == titleObj . yearValue ) :
2021-04-09 16:05:46 -05:00
# Clear previous results, and only use this one
2021-04-09 09:13:06 -05:00
showsWithSameName = [ ]
showsWithSameName . append ( show )
break
2021-04-09 16:05:46 -05:00
# Otherwise, only add the show if the broadcast year matches
2021-04-09 09:13:06 -05:00
if show . year == titleObj . yearValue :
showsWithSameName . append ( show )
2021-04-09 16:05:46 -05:00
# If the program doesn't have the broadcast year, then add all the results
2021-04-09 09:13:06 -05:00
else :
showsWithSameName . append ( show )
2021-04-09 16:05:46 -05:00
# Sweep through the results once more for 1:1 title name matches,
# then if the list contains one entry with a 1:1 match, then clear the array
# and only use this one!
2021-04-09 09:13:06 -05:00
completeMatchNames = [ ]
for nameFromSearch in showsWithSameName :
if nameFromSearch . title == name :
completeMatchNames . append ( nameFromSearch )
2022-02-19 05:07:17 -06:00
if len ( completeMatchNames ) == 1 :
2021-04-09 09:13:06 -05:00
showsWithSameName = completeMatchNames
2021-04-09 16:05:46 -05:00
# If the search contains multiple results, then we need to confirm with the user which show
# the script should use, or access the local database to see if the user has already provided
# a manual selection
2021-04-09 09:13:06 -05:00
if len ( showsWithSameName ) > 1 :
2021-04-09 16:05:46 -05:00
# Query the local database for existing selection
2021-04-09 09:13:06 -05:00
userMatchedQuery = Query ( )
2022-02-19 05:07:17 -06:00
queryResult = userMatchedShowsTable . search ( userMatchedQuery . ShowName == name )
2021-04-09 09:13:06 -05:00
2021-04-09 16:05:46 -05:00
# If the local database already contains an entry for a manual selection
# then don't bother prompting the user to select it again!
2021-04-09 09:13:06 -05:00
if len ( queryResult ) == 1 :
2021-04-09 16:05:46 -05:00
# Get the first result from the query
2021-04-09 09:13:06 -05:00
firstMatch = queryResult [ 0 ]
2021-04-09 16:05:46 -05:00
# Get the value contains the selection index
2022-02-19 05:07:17 -06:00
firstMatchSelectedIndex = int ( firstMatch . get ( " UserSelectedIndex " ) )
2021-04-09 16:05:46 -05:00
# Check if the user previously requested to skip the show
2022-02-19 05:07:17 -06:00
skipShow = firstMatch . get ( " SkipShow " )
2021-04-09 16:05:46 -05:00
# If the user did not skip, but provided an index selection, get the
# matching show
2022-02-19 07:15:27 -06:00
if not skipShow :
2021-04-09 09:13:06 -05:00
return showsWithSameName [ firstMatchSelectedIndex ]
2021-04-09 16:05:46 -05:00
# Otherwise, return None, which will trigger the script to skip
# and move onto the next show
2021-04-09 09:13:06 -05:00
else :
return None
2021-04-09 16:05:46 -05:00
# If the user has not provided a manual selection already in the process
# then prompt the user to make a selection
2021-04-09 09:13:06 -05:00
else :
print (
2022-02-19 05:07:17 -06:00
f " INFO - MANUAL INPUT REQUIRED: The TV Time data for Show ' { name } ' (Season { seasonNo } , Episode { episodeNo } ) has { len ( showsWithSameName ) } matching Trakt shows with the same name. "
)
2021-04-09 09:13:06 -05:00
2021-04-09 16:05:46 -05:00
# Output each show for manual selection
2021-04-09 09:13:06 -05:00
for idx , item in enumerate ( showsWithSameName ) :
2021-04-09 16:05:46 -05:00
# Display the show's title, broadcast year, amount of seasons and a link to the Trakt page.
# This will provide the user with enough information to make a selection.
2021-04-09 09:13:06 -05:00
print (
2022-02-19 05:07:17 -06:00
f " ( { idx + 1 } ) { item . title } - { item . year } - { len ( item . seasons ) } Season(s) - More Info: https://trakt.tv/ { item . ext } "
)
2021-04-09 09:13:06 -05:00
2022-02-19 05:07:17 -06:00
while True :
2021-04-09 09:13:06 -05:00
try :
2021-04-09 16:05:46 -05:00
# Get the user's selection, either a numerical input, or a string 'SKIP' value
2022-02-19 05:07:17 -06:00
indexSelected = input (
2022-02-19 07:15:27 -06:00
" Please make a selection from above (or enter SKIP): "
2022-02-19 05:07:17 -06:00
)
2021-04-09 09:13:06 -05:00
2022-02-19 05:07:17 -06:00
if indexSelected != " SKIP " :
2021-04-09 16:05:46 -05:00
# Since the value isn't 'skip', check that the result is numerical
2021-04-09 18:39:10 -05:00
indexSelected = int ( indexSelected ) - 1
2021-04-09 16:05:46 -05:00
# Exit the selection loop
2021-04-09 09:13:06 -05:00
break
2021-04-09 16:05:46 -05:00
# Otherwise, exit the loop
2021-04-09 09:13:06 -05:00
else :
break
2021-04-09 16:05:46 -05:00
# Still allow the user to provide the exit input, and kill the program
2021-04-09 09:13:06 -05:00
except KeyboardInterrupt :
sys . exit ( " Cancel requested... " )
2021-04-09 16:05:46 -05:00
# Otherwise, the user has entered an invalid value, warn the user to try again
2022-02-19 07:15:27 -06:00
except Exception :
2022-02-19 05:34:54 -06:00
logging . error (
2022-02-19 05:07:17 -06:00
f " Sorry! Please select a value between 0 to { len ( showsWithSameName ) } "
)
2021-04-09 09:13:06 -05:00
2021-04-09 16:05:46 -05:00
# If the user entered 'SKIP', then exit from the loop with no selection, which
# will trigger the program to move onto the next episode
2022-02-19 05:07:17 -06:00
if indexSelected == " SKIP " :
2021-04-09 16:05:46 -05:00
# Record that the user has skipped the TV Show for import, so that
# manual input isn't required everytime
2021-04-09 09:13:06 -05:00
userMatchedShowsTable . insert (
2022-02-19 05:07:17 -06:00
{ " ShowName " : name , " UserSelectedIndex " : 0 , " SkipShow " : True }
)
2021-04-09 09:13:06 -05:00
return None
2021-04-09 16:05:46 -05:00
# Otherwise, return the selection which the user made from the list
2021-04-09 09:13:06 -05:00
else :
selectedShow = showsWithSameName [ int ( indexSelected ) ]
userMatchedShowsTable . insert (
2022-02-19 05:07:17 -06:00
{
" ShowName " : name ,
" UserSelectedIndex " : indexSelected ,
" SkipShow " : False ,
}
)
2021-04-09 09:13:06 -05:00
return selectedShow
else :
2022-02-19 05:07:17 -06:00
if len ( showsWithSameName ) > 0 :
2021-04-09 18:39:10 -05:00
# If the search returned only one result, then awesome!
# Return the show, so the import automation can continue.
return showsWithSameName [ 0 ]
else :
return None
2021-04-09 09:13:06 -05:00
2022-02-19 05:07:17 -06:00
2021-04-09 16:05:46 -05:00
# Since the Trakt.Py starts the indexing of seasons in the array from 0 (e.g Season 1 in Index 0), then
# subtract the TV Time numerical value by 1 so it starts from 0 as well. However, when a TV series includes
# a 'special' season, Trakt.Py will place this as the first season in the array - so, don't subtract, since
# this will match TV Time's existing value.
2021-04-09 09:13:06 -05:00
def parseSeasonNo ( seasonNo , traktShowObj ) :
2021-04-09 16:05:46 -05:00
# Parse the season number into a numerical value
2021-04-09 09:13:06 -05:00
seasonNo = int ( seasonNo )
2021-04-09 16:05:46 -05:00
# Then get the Season Number from the first item in the array
2021-04-09 09:13:06 -05:00
firstSeasonNo = traktShowObj . seasons [ 0 ] . number
2021-04-09 16:05:46 -05:00
# If the season number is 0, then the Trakt show contains a "special" season
2021-04-09 09:13:06 -05:00
if firstSeasonNo == 0 :
2021-04-09 16:05:46 -05:00
# No need to modify the value, as the TV Time value will match Trakt
2021-04-09 09:13:06 -05:00
return seasonNo
2021-04-09 16:05:46 -05:00
# Otherwise, if the Trakt seasons start with no specials, then return the seasonNo,
# but subtracted by one (e.g Season 1 in TV Time, will be 0)
2021-04-09 09:13:06 -05:00
else :
2022-02-19 06:28:12 -06:00
# Only subtract if the TV Time season number is greater than 0.
2021-04-09 09:13:06 -05:00
if seasonNo != 0 :
return seasonNo - 1
2021-04-09 16:05:46 -05:00
# Otherwise, the TV Time season is a special! Then you don't need to change the starting position
2021-04-09 09:13:06 -05:00
else :
return seasonNo
def processWatchedShows ( ) :
2021-04-09 16:05:46 -05:00
# Total amount of rows which have been processed in the CSV file
2021-04-09 09:13:06 -05:00
rowsCount = 0
2021-04-09 16:05:46 -05:00
# Total amount of rows in the CSV file
2021-04-09 09:13:06 -05:00
errorStreak = 0
2021-04-09 16:05:46 -05:00
# Open the CSV file within the GDPR exported data
2022-02-19 05:07:17 -06:00
with open ( getWatchedShowsPath ( ) , newline = " " ) as csvfile :
2021-04-09 16:05:46 -05:00
# Create the CSV reader, which will break up the fields using the delimiter ','
2022-02-19 05:07:17 -06:00
showsReader = csv . DictReader ( csvfile , delimiter = " , " )
2022-02-19 04:58:40 -06:00
# Get the total amount of rows in the CSV file,
rowsTotal = len ( list ( showsReader ) )
# Move position to the beginning of the file
csvfile . seek ( 0 , 0 )
2021-04-09 16:05:46 -05:00
# Loop through each line/record of the CSV file
2022-02-19 04:58:40 -06:00
# Ignore the header row
next ( showsReader , None )
for rowsCount , row in enumerate ( showsReader ) :
2021-04-09 16:05:46 -05:00
# Get the name of the TV show
2022-02-19 04:58:40 -06:00
tvShowName = row [ " tv_show_name " ]
# Get the TV Time Episode Id
tvShowEpisodeId = row [ " episode_id " ]
# Get the TV Time Season Number
tvShowSeasonNo = row [ " episode_season_number " ]
# Get the TV Time Episode Number
tvShowEpisodeNo = row [ " episode_number " ]
# Get the date which the show was marked 'watched' in TV Time
tvShowDateWatched = row [ " updated_at " ]
# Parse the watched date value into a Python type
tvShowDateWatchedConverted = datetime . strptime (
2022-02-19 05:07:17 -06:00
tvShowDateWatched , " % Y- % m- %d % H: % M: % S "
)
2022-02-19 04:58:40 -06:00
# Query the local database for previous entries indicating that
# the episode has already been imported in the past. Which will
# ease pressure on TV Time's API server during a retry of the import
# process, and just save time overall without needing to create network requests
episodeCompletedQuery = Query ( )
queryResult = syncedEpisodesTable . search (
2022-02-19 05:07:17 -06:00
episodeCompletedQuery . episodeId == tvShowEpisodeId
)
2022-02-19 04:58:40 -06:00
# If the query returned no results, then continue to import it into Trakt
if len ( queryResult ) == 0 :
# Create a repeating loop, which will break on success, but repeats on failures
while True :
# If more than 10 errors occurred in one streak, whilst trying to import the episode
# then give up, and move onto the next episode, but warn the user.
2022-02-19 05:07:17 -06:00
if errorStreak > 10 :
2022-02-19 05:34:54 -06:00
logging . warning (
2022-02-19 07:15:27 -06:00
" An error occurred 10 times in a row... skipping episode... "
2022-02-19 05:07:17 -06:00
)
2022-02-19 04:58:40 -06:00
break
try :
# Sleep for a second between each process, before going onto the next watched episode.
# This is required to remain within the API rate limit, and use the API server fairly.
# Other developers share the service, for free - so be considerate of your usage.
time . sleep ( DELAY_BETWEEN_EPISODES_IN_SECONDS )
# Search Trakt for the TV show matching TV Time's title value
traktShowObj = getShowByName (
2022-02-19 05:07:17 -06:00
tvShowName , tvShowSeasonNo , tvShowEpisodeNo
)
2022-02-19 04:58:40 -06:00
# If the method returned 'None', then this is an indication to skip the episode, and
# move onto the next one
2022-02-19 07:15:27 -06:00
if traktShowObj is None :
2021-04-09 09:13:06 -05:00
break
2022-02-19 04:58:40 -06:00
# Show the progress of the import on-screen
2022-02-19 05:34:54 -06:00
logging . info (
2022-02-19 07:15:27 -06:00
f " ( { rowsCount + 1 } / { rowsTotal } ) - Processing ' { tvShowName } ' Season { tvShowSeasonNo } / Episode { tvShowEpisodeNo } "
2022-02-19 05:07:17 -06:00
)
2022-02-19 04:58:40 -06:00
# Get the season from the Trakt API
2022-02-19 05:07:17 -06:00
season = traktShowObj . seasons [
parseSeasonNo ( tvShowSeasonNo , traktShowObj )
]
2022-02-19 04:58:40 -06:00
# Get the episode from the season
episode = season . episodes [ int ( tvShowEpisodeNo ) - 1 ]
# Mark the episode as watched!
episode . mark_as_seen ( tvShowDateWatchedConverted )
# Add the episode to the local database as imported, so it can be skipped,
# if the process is repeated
2022-02-19 05:07:17 -06:00
syncedEpisodesTable . insert ( { " episodeId " : tvShowEpisodeId } )
2022-02-19 04:58:40 -06:00
# Clear the error streak on completing the method without errors
errorStreak = 0
break
# Catch errors which occur because of an incorrect array index. This occurs when
# an incorrect Trakt show has been selected, with season/episodes which don't match TV Time.
# It can also occur due to a bug in Trakt Py, whereby some seasons contain an empty array of episodes.
except IndexError :
2022-02-19 07:15:27 -06:00
tvShowSlug = traktShowObj . to_json ( ) [ " shows " ] [ 0 ] [ " ids " ] [ " ids " ] [
" slug "
]
2022-02-19 05:34:54 -06:00
logging . warning (
2022-02-19 07:15:27 -06:00
f " ( { rowsCount } / { rowsTotal } ) - { tvShowName } Season { tvShowSeasonNo } , Episode { tvShowEpisodeNo } does not exist in Trakt! (https://trakt.tv/shows/ { tvShowSlug } /seasons/ { tvShowSeasonNo } /episodes/ { tvShowEpisodeNo } ) "
2022-02-19 05:07:17 -06:00
)
2022-02-19 04:58:40 -06:00
break
# Catch any errors which are raised because a show could not be found in Trakt
except trakt . errors . NotFoundException :
2022-02-19 05:34:54 -06:00
logging . warning (
2022-02-19 07:15:27 -06:00
f " ( { rowsCount } / { rowsTotal } ) - { tvShowName } Season { tvShowSeasonNo } , Episode { tvShowEpisodeNo } does not exist (search) in Trakt! "
2022-02-19 05:07:17 -06:00
)
2022-02-19 04:58:40 -06:00
break
# Catch errors because of the program breaching the Trakt API rate limit
except trakt . errors . RateLimitException :
2022-02-19 05:34:54 -06:00
logging . warning (
2022-02-19 07:15:27 -06:00
" The program is running too quickly and has hit Trakt ' s API rate limit! Please increase the delay between "
2022-02-19 05:07:17 -06:00
+ " episdoes via the variable ' DELAY_BETWEEN_EPISODES_IN_SECONDS ' . The program will now wait 60 seconds before "
+ " trying again. "
)
2022-02-19 04:58:40 -06:00
time . sleep ( 60 )
# Mark the exception in the error streak
errorStreak + = 1
# Catch a JSON decode error - this can be raised when the API server is down and produces a HTML page, instead of JSON
except json . decoder . JSONDecodeError :
2022-02-19 05:34:54 -06:00
logging . warning (
2022-02-19 07:15:27 -06:00
f " ( { rowsCount } / { rowsTotal } ) - A JSON decode error occuring whilst processing { tvShowName } "
2022-02-19 05:07:17 -06:00
+ f " Season { tvShowSeasonNo } , Episode { tvShowEpisodeNo } ! This might occur when the server is down and has produced "
+ " a HTML document instead of JSON. The script will wait 60 seconds before trying again. "
)
2022-02-19 04:58:40 -06:00
# Wait 60 seconds
time . sleep ( 60 )
# Mark the exception in the error streak
errorStreak + = 1
# Catch a CTRL + C keyboard input, and exits the program
except KeyboardInterrupt :
sys . exit ( " Cancel requested... " )
# Skip the episode
else :
2022-02-19 05:34:54 -06:00
logging . info (
2022-02-19 07:15:27 -06:00
f " ( { rowsCount } / { rowsTotal } ) - Already imported, skipping ' { tvShowName } ' Season { tvShowSeasonNo } / Episode { tvShowEpisodeNo } . "
2022-02-19 05:07:17 -06:00
)
2021-04-09 09:13:06 -05:00
def start ( ) :
2021-04-09 16:05:46 -05:00
# Create the initial authentication with Trakt, before starting the process
2021-04-09 09:13:06 -05:00
if initTraktAuth ( ) :
2021-04-09 18:39:10 -05:00
# Display a menu selection
2022-02-19 07:15:27 -06:00
print ( " >> What do you want to do? " )
print ( " 1) Import Watch History from TV Time " )
2021-04-09 18:39:10 -05:00
while True :
try :
2022-02-19 07:15:27 -06:00
menuSelection = input ( " Enter your menu selection: " )
2022-02-19 04:58:40 -06:00
menuSelection = 1 if not menuSelection else int ( menuSelection )
2021-04-09 18:39:10 -05:00
break
except ValueError :
2022-02-19 05:34:54 -06:00
logging . warning ( " Invalid input. Please enter a numerical number. " )
2021-04-09 18:39:10 -05:00
# Start the process which is required
if menuSelection == 1 :
# Invoke the method which will import episodes which have been watched
# from TV Time into Trakt
processWatchedShows ( )
else :
2022-02-19 05:34:54 -06:00
logging . warning ( " Sorry - that ' s an unknown menu selection " )
2021-04-09 09:13:06 -05:00
else :
2022-02-19 07:15:27 -06:00
logging . error (
" ERROR: Unable to complete authentication to Trakt - please try again. "
)
2021-04-09 09:13:06 -05:00
if __name__ == " __main__ " :
2021-04-09 16:05:46 -05:00
# Check that the user has created the config file
if os . path . exists ( " config.json " ) :
# Check that the user has provided the GDPR path
if os . path . isdir ( config . GDPR_WORKSPACE_PATH ) :
start ( )
else :
2022-02-19 05:34:54 -06:00
logging . error (
2022-02-19 05:07:17 -06:00
" Oops! The TV Time GDPR folder ' "
+ config . GDPR_WORKSPACE_PATH
+ " ' does not exist on the local system. Please check it, and try again. "
)
2021-04-09 09:13:06 -05:00
else :
2022-02-19 05:34:54 -06:00
logging . error (
2022-02-19 07:15:27 -06:00
" The ' config.json ' file cannot be found - have you created it yet? "
2022-02-19 05:07:17 -06:00
)