Change field selection to named columns instead of indexes. (csv.DictReader)

Allow empty menu input, defaulting to choice #1
Change total line count to reuse already opened CSV file
Use enumerate() to track row ID
This commit is contained in:
Baptiste Roux 2022-02-19 11:58:40 +01:00
parent 6d8818bc14
commit 9436798e47
No known key found for this signature in database
GPG key ID: F2D53AA58807C6B5

View file

@ -1,17 +1,15 @@
# main.py #!/usr/bin/env python3
from logging import error
import sys
from trakt import *
import trakt.core
import os
import csv import csv
from datetime import datetime
import time
from tinydb import TinyDB, Query
import json import json
import os
import re import re
import sys import sys
import time
from datetime import datetime
import trakt.core
from tinydb import Query, TinyDB
from trakt import Expando
from trakt.tv import TVShow from trakt.tv import TVShow
# Adjust this value to increase/decrease your requests between episodes. # Adjust this value to increase/decrease your requests between episodes.
@ -58,6 +56,7 @@ def getFollowedShowsPath():
def initTraktAuth(): def initTraktAuth():
return True
# Set the method of authentication # Set the method of authentication
trakt.core.AUTH_METHOD = trakt.core.OAUTH_AUTH trakt.core.AUTH_METHOD = trakt.core.OAUTH_AUTH
return init(config.TRAKT_USERNAME, store=True, client_id=config.CLIENT_ID, client_secret=config.CLIENT_SECRET) return init(config.TRAKT_USERNAME, store=True, client_id=config.CLIENT_ID, client_secret=config.CLIENT_SECRET)
@ -291,133 +290,122 @@ def processWatchedShows():
# Total amount of rows which have been processed in the CSV file # Total amount of rows which have been processed in the CSV file
rowsCount = 0 rowsCount = 0
# Total amount of rows in the CSV file # Total amount of rows in the CSV file
rowsTotal = 0
# Total amount of errors which have occurred in one streak
errorStreak = 0 errorStreak = 0
# Get the total amount of rows in the CSV file,
# which is helpful for keeping track of progress.
# However, if you have a VERY large CSV file (e.g above 100,000 rows)
# then it might be a good idea to remove this due to the performance
# overhead.
with open(getWatchedShowsPath()) as f:
rowsTotal = sum(1 for line in f)
# Open the CSV file within the GDPR exported data # Open the CSV file within the GDPR exported data
with open(getWatchedShowsPath(), newline='') as csvfile: with open(getWatchedShowsPath(), newline='') as csvfile:
# Create the CSV reader, which will break up the fields using the delimiter ',' # Create the CSV reader, which will break up the fields using the delimiter ','
showsReader = csv.reader(csvfile, delimiter=',') showsReader = csv.DictReader(csvfile, delimiter=',')
# 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)
# Loop through each line/record of the CSV file # Loop through each line/record of the CSV file
for row in showsReader: # Ignore the header row
# Increment the row counter to keep track of progress completing the next(showsReader, None)
# records during the import process. for rowsCount, row in enumerate(showsReader):
rowsCount += 1
# Get the name of the TV show # Get the name of the TV show
tvShowName = row[8] 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
print(tvShowDateWatched)
tvShowDateWatchedConverted = datetime.strptime(
tvShowDateWatched, '%Y-%m-%d %H:%M:%S')
# Ignore the header row # Query the local database for previous entries indicating that
if rowsCount > 1: # the episode has already been imported in the past. Which will
# Get the TV Time Episode Id # ease pressure on TV Time's API server during a retry of the import
tvShowEpisodeId = row[4] # process, and just save time overall without needing to create network requests
# Get the TV Time Season Number episodeCompletedQuery = Query()
tvShowSeasonNo = row[5] queryResult = syncedEpisodesTable.search(
# Get the TV Time Episode Number episodeCompletedQuery.episodeId == tvShowEpisodeId)
tvShowEpisodeNo = row[6]
# Get the date which the show was marked 'watched' in TV Time
tvShowDateWatched = row[7]
# Parse the watched date value into a Python type
tvShowDateWatchedConverted = datetime.strptime(
tvShowDateWatched, '%Y-%m-%d %H:%M:%S')
# Query the local database for previous entries indicating that # If the query returned no results, then continue to import it into Trakt
# the episode has already been imported in the past. Which will if len(queryResult) == 0:
# ease pressure on TV Time's API server during a retry of the import # Create a repeating loop, which will break on success, but repeats on failures
# process, and just save time overall without needing to create network requests while True:
episodeCompletedQuery = Query() # If more than 10 errors occurred in one streak, whilst trying to import the episode
queryResult = syncedEpisodesTable.search( # then give up, and move onto the next episode, but warn the user.
episodeCompletedQuery.episodeId == tvShowEpisodeId) if (errorStreak > 10):
print(
# If the query returned no results, then continue to import it into Trakt f"WARNING: An error occurred 10 times in a row... skipping episode...")
if len(queryResult) == 0: break
# Create a repeating loop, which will break on success, but repeats on failures try:
while True: # Sleep for a second between each process, before going onto the next watched episode.
# If more than 10 errors occurred in one streak, whilst trying to import the episode # This is required to remain within the API rate limit, and use the API server fairly.
# then give up, and move onto the next episode, but warn the user. # Other developers share the service, for free - so be considerate of your usage.
if (errorStreak > 10): time.sleep(DELAY_BETWEEN_EPISODES_IN_SECONDS)
print( # Search Trakt for the TV show matching TV Time's title value
f"WARNING: An error occurred 10 times in a row... skipping episode...") traktShowObj = getShowByName(
tvShowName, tvShowSeasonNo, tvShowEpisodeNo)
# If the method returned 'None', then this is an indication to skip the episode, and
# move onto the next one
if traktShowObj == None:
break break
try: # Show the progress of the import on-screen
# Sleep for a second between each process, before going onto the next watched episode. print(
# This is required to remain within the API rate limit, and use the API server fairly. f"({rowsCount}/{rowsTotal}) Processing Show {tvShowName} on Season {tvShowSeasonNo} - Episode {tvShowEpisodeNo}")
# Other developers share the service, for free - so be considerate of your usage. # Get the season from the Trakt API
time.sleep(DELAY_BETWEEN_EPISODES_IN_SECONDS) season = traktShowObj.seasons[parseSeasonNo(
# Search Trakt for the TV show matching TV Time's title value tvShowSeasonNo, traktShowObj)]
traktShowObj = getShowByName( # Get the episode from the season
tvShowName, tvShowSeasonNo, tvShowEpisodeNo) episode = season.episodes[int(tvShowEpisodeNo) - 1]
# If the method returned 'None', then this is an indication to skip the episode, and # Mark the episode as watched!
# move onto the next one episode.mark_as_seen(tvShowDateWatchedConverted)
if traktShowObj == None: # Add the episode to the local database as imported, so it can be skipped,
break # if the process is repeated
# Show the progress of the import on-screen syncedEpisodesTable.insert(
print( {'episodeId': tvShowEpisodeId})
f"({rowsCount}/{rowsTotal}) Processing Show {tvShowName} on Season {tvShowSeasonNo} - Episode {tvShowEpisodeNo}") # Clear the error streak on completing the method without errors
# Get the season from the Trakt API errorStreak = 0
season = traktShowObj.seasons[parseSeasonNo( break
tvShowSeasonNo, traktShowObj)] # Catch errors which occur because of an incorrect array index. This occurs when
# Get the episode from the season # an incorrect Trakt show has been selected, with season/episodes which don't match TV Time.
episode = season.episodes[int(tvShowEpisodeNo) - 1] # It can also occur due to a bug in Trakt Py, whereby some seasons contain an empty array of episodes.
# Mark the episode as watched! except IndexError:
episode.mark_as_seen(tvShowDateWatchedConverted) print(
# Add the episode to the local database as imported, so it can be skipped, f"({rowsCount}/{rowsTotal}) WARNING: {tvShowName} Season {tvShowSeasonNo}, Episode {tvShowEpisodeNo} does not exist (season/episode index) in Trakt!")
# if the process is repeated break
syncedEpisodesTable.insert( # Catch any errors which are raised because a show could not be found in Trakt
{'episodeId': tvShowEpisodeId}) except trakt.errors.NotFoundException:
# Clear the error streak on completing the method without errors print(
errorStreak = 0 f"({rowsCount}/{rowsTotal}) WARNING: {tvShowName} Season {tvShowSeasonNo}, Episode {tvShowEpisodeNo} does not exist (search) in Trakt!")
break break
# Catch errors which occur because of an incorrect array index. This occurs when # Catch errors because of the program breaching the Trakt API rate limit
# an incorrect Trakt show has been selected, with season/episodes which don't match TV Time. except trakt.errors.RateLimitException:
# It can also occur due to a bug in Trakt Py, whereby some seasons contain an empty array of episodes. print(
except IndexError: "WARNING: The program is running too quickly and has hit Trakt's API rate limit! Please increase the delay between " +
print( "episdoes via the variable 'DELAY_BETWEEN_EPISODES_IN_SECONDS'. The program will now wait 60 seconds before " +
f"({rowsCount}/{rowsTotal}) WARNING: {tvShowName} Season {tvShowSeasonNo}, Episode {tvShowEpisodeNo} does not exist (season/episode index) in Trakt!") "trying again.")
break time.sleep(60)
# Catch any errors which are raised because a show could not be found in Trakt
except trakt.errors.NotFoundException:
print(
f"({rowsCount}/{rowsTotal}) WARNING: {tvShowName} Season {tvShowSeasonNo}, Episode {tvShowEpisodeNo} does not exist (search) in Trakt!")
break
# Catch errors because of the program breaching the Trakt API rate limit
except trakt.errors.RateLimitException:
print(
"WARNING: The program is running too quickly and has hit Trakt's API rate limit! Please increase the delay between " +
"episdoes via the variable 'DELAY_BETWEEN_EPISODES_IN_SECONDS'. The program will now wait 60 seconds before " +
"trying again.")
time.sleep(60)
# Mark the exception in the error streak # Mark the exception in the error streak
errorStreak += 1 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 # 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: except json.decoder.JSONDecodeError:
print( print(
f"({rowsCount}/{rowsTotal}) WARNING: A JSON decode error occuring whilst processing {tvShowName} " + f"({rowsCount}/{rowsTotal}) WARNING: A JSON decode error occuring whilst processing {tvShowName} " +
f"Season {tvShowSeasonNo}, Episode {tvShowEpisodeNo}! This might occur when the server is down and has produced " + 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.") "a HTML document instead of JSON. The script will wait 60 seconds before trying again.")
# Wait 60 seconds # Wait 60 seconds
time.sleep(60) time.sleep(60)
# Mark the exception in the error streak # Mark the exception in the error streak
errorStreak += 1 errorStreak += 1
# Catch a CTRL + C keyboard input, and exits the program # Catch a CTRL + C keyboard input, and exits the program
except KeyboardInterrupt: except KeyboardInterrupt:
sys.exit("Cancel requested...") sys.exit("Cancel requested...")
# Skip the episode # Skip the episode
else: else:
print( print(
f"({rowsCount}/{rowsTotal}) Skipping '{tvShowName}' Season {tvShowSeasonNo} Episode {tvShowEpisodeNo}. It's already been imported.") f"({rowsCount}/{rowsTotal}) Skipping '{tvShowName}' Season {tvShowSeasonNo} Episode {tvShowEpisodeNo}. It's already been imported.")
def start(): def start():
@ -429,7 +417,8 @@ def start():
while True: while True:
try: try:
menuSelection = int(input(f"Enter your menu selection: ")) menuSelection = input(f"Enter your menu selection: ")
menuSelection = 1 if not menuSelection else int(menuSelection)
break break
except ValueError: except ValueError:
print("Invalid input. Please enter a numerical number.") print("Invalid input. Please enter a numerical number.")