mirror of
https://github.com/SinTan1729/TvTimeToTrakt.git
synced 2024-12-25 21:08:37 -06:00
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:
parent
6d8818bc14
commit
9436798e47
1 changed files with 114 additions and 125 deletions
239
TimeToTrackt.py
239
TimeToTrackt.py
|
@ -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.")
|
||||||
|
|
Loading…
Reference in a new issue