Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Github Actions - Validate tvdb seasons for PRs #98

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/validate-tvdb-seasons.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Validate TVDB Seasons
on:
pull_request_target:
branches:
- main
paths:
- "series-tvdb.en.yaml"
- "**.py"
env:
PYTHON_VERSION: "3.10"
jobs:
validate-added-seasons-against-tvdb:
runs-on: ubuntu-latest
steps:
# TODO: maybe make this step a pre-requisite to any CI flow
- name: Prevent file change
uses: xalvarez/prevent-file-change-action@v1
with:
githubToken: ${{ secrets.GITHUB_TOKEN }}
pattern: .*\.py
trustedAuthors: evie-lau,Soitora,reconman
allowNewFiles: false
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Cache python env
uses: actions/cache@v4
with:
path: ${{ env.pythonLocation }}
key: ${{ env.pythonLocation }}-${{ hashFiles('validate-tvdb-seasons.py') }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install tvdb_v4_official python-dotenv pyyaml deepdiff
- name: Run season validation against TVDB
env:
TVDB_API_KEY: ${{ secrets.TVDB_API_KEY }}
run: python validate-tvdb-seasons.py
- name: Create updated entries list
uses: actions/upload-artifact@v4
with:
path: temp.yaml
33 changes: 33 additions & 0 deletions series-tmdb.en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2331,6 +2331,15 @@ entries:
- season: 1
anilist-id: 150972

- title: "SAKAMOTO DAYS"
guid: "plex://show/62ea662444a4745bb2f3a8ce"
# imdb: https://www.imdb.com/title/tt17069148/
# tmdb: https://www.themoviedb.org/tv/207332
# tvdb: https://www.thetvdb.com/dereferrer/series/423732
seasons:
- season: 1
anilist-id: 177709

- title: "Scott Pilgrim Takes Off"
guid: plex://show/61dd97c364b2acfab575e1c3
# imdb: https://www.imdb.com/title/tt16969708/
Expand Down Expand Up @@ -3175,6 +3184,21 @@ entries:
- season: 5
anilist-id: 142481

- title: "Yu Yu Hakusho"
guid: "plex://show/5d9c0813705e7a001e6d1afb"
# imdb: https://www.imdb.com/title/tt0185133/
# tmdb: https://www.themoviedb.org/tv/30669
# tvdb: https://www.thetvdb.com/dereferrer/series/76665
seasons:
- season: 1
anilist-id: 392
- season: 2
anilist-id: 392
- season: 3
anilist-id: 392
- season: 4
anilist-id: 392

- title: "Yu-Gi-Oh! Duel Monsters"
guid: plex://show/5d9c081dba6eb9001fb9c35c
# imdb: https://www.imdb.com/title/tt0247902/
Expand Down Expand Up @@ -3218,3 +3242,12 @@ entries:
- season: 1
anilist-id: 159831

- title: "Übel Blatt"
guid: "plex://show/65db309639d5eabf6d1e207b"
# imdb: https://www.imdb.com/title/tt32581027/
# tmdb: https://www.themoviedb.org/tv/247367
# tvdb: https://www.thetvdb.com/dereferrer/series/446485
seasons:
- season: 1
anilist-id: 175198

35 changes: 34 additions & 1 deletion series-tvdb.en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -738,7 +738,7 @@ entries:
- season: 9
anilist-id: 813

- title: "Drop Kick on My Devil!!"
- title: "Dropkick on My Devil!!"
guid: plex://show/5d9c090502391c001f59412a
seasons:
- season: 1
Expand Down Expand Up @@ -2375,6 +2375,15 @@ entries:
- season: 1
anilist-id: 150972

- title: "SAKAMOTO DAYS"
guid: "plex://show/62ea662444a4745bb2f3a8ce"
# imdb: https://www.imdb.com/title/tt17069148/
# tmdb: https://www.themoviedb.org/tv/207332
# tvdb: https://www.thetvdb.com/dereferrer/series/423732
seasons:
- season: 1
anilist-id: 177709

- title: "Scott Pilgrim Takes Off"
guid: plex://show/61dd97c364b2acfab575e1c3
# imdb: https://www.imdb.com/title/tt16969708/
Expand Down Expand Up @@ -3231,6 +3240,21 @@ entries:
- season: 5
anilist-id: 142481

- title: "Yu Yu Hakusho"
guid: "plex://show/5d9c0813705e7a001e6d1afb"
# imdb: https://www.imdb.com/title/tt0185133/
# tmdb: https://www.themoviedb.org/tv/30669
# tvdb: https://www.thetvdb.com/dereferrer/series/76665
seasons:
- season: 1
anilist-id: 392
- season: 2
anilist-id: 392
- season: 3
anilist-id: 392
- season: 4
anilist-id: 392

- title: "Yu-Gi-Oh! Duel Monsters"
guid: plex://show/5d9c081dba6eb9001fb9c35c
# imdb: https://www.imdb.com/title/tt0247902/
Expand Down Expand Up @@ -3282,3 +3306,12 @@ entries:
- season: 1
anilist-id: 159831

- title: "Übel Blatt"
guid: "plex://show/65db309639d5eabf6d1e207b"
# imdb: https://www.imdb.com/title/tt32581027/
# tmdb: https://www.themoviedb.org/tv/247367
# tvdb: https://www.thetvdb.com/dereferrer/series/446485
seasons:
- season: 1
anilist-id: 175198

130 changes: 130 additions & 0 deletions validate-tvdb-seasons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import os, sys, subprocess
import tvdb_v4_official, yaml
from dotenv import load_dotenv
from deepdiff import DeepDiff


# Get TVDB ID and series information for a show
def getTvdbEntry(showName):
# Get TVDB ID of show
showId = None
series = None
searchResults = tvdb.search(showName, type="series")
for result in searchResults:
if (resultMatchesShow(result, showName)):
# print(result)
showId = result['tvdb_id']
series = tvdb.get_series_extended(showId)
# only mark as found if it's an Anime show
if matchesAnimeCriteria(series):
break
else: # reset stored values if not a match
showId = None
series = None
return showId, series


# Check if showName matches anything result name, aliases, or translation names
def resultMatchesShow(result, showName):
# TODO: need to normalize some characters, such as dashes and apostrophes. Use unicodedata.normalize. Test with "KonoSuba – God's blessing on this wonderful world!!" (curly vs straight apostrophe)
return ((showName == result['name']) or
('aliases' in result and showName in result['aliases']) or
('translations' in result and showName in result['translations'].values()))


# Match for AniList anime criteria (arbitrary): genre=[Anime,Animation], country=[jpn,kor,chn,twn]
validGenres = {'Anime','Animation'}
validCountries = {'jpn','kor','chn','twn'}
def matchesAnimeCriteria(series):
seriesGenreSet = {item['name'] for item in series['genres']}
return ((seriesGenreSet.intersection(validGenres)) and
(series['originalCountry'] in validCountries))


# Validate user-mapped season entries against TVDB seasons
def validateShowSeasons(showName, seasonsToFind):
errors = 0

showId, series = getTvdbEntry(showName)
print("Validating: " + showName + " [" + str(showId) + "] - Seasons " + str(seasonsToFind))

if (showId is None):
print("\t[WARNING] No TVDB series result: " + showName)
return errors
# TODO: does not work for primary_type: movie, maybe separate method for those? Test with 5cm per second
tvdbSeasons = [season['number'] for season in series['seasons'] if season['type']['type'] == 'official']

# print("Found show: " + showName + " (" + showId + "), with seasons: " + str(tvdbSeasons))
# print("Validating user-mapped seasons: " + str(seasonsToFind))
invalidSeasons = []
for s in seasonsToFind:
if s not in tvdbSeasons:
errors += 1
invalidSeasons.append(s)

if errors > 0:
print("\t[ERROR] Did not find season(s): " + str(invalidSeasons) + " in show: " + showName)
return errors


# Parse temp.yaml and validate shows/seasons against TVDB
def validateMappings(file="temp.yaml"):
errors = 0
with open(file, mode='r') as f:
mappings = yaml.safe_load(f)
for show in sorted(mappings['entries'], key=lambda entry: (entry['title'], entry['seasons'])):
showName = show['title']
seasons = set([s['season'] for s in show['seasons'] if 'season' in s])
errors += validateShowSeasons(showName, seasons)
return errors


# Finds new and changed entries and writes them into a temp yaml file
def extractNewMappings():
old_mappings = dict()
new_mappings = dict()

# get old version of TVDB file
# TODO read old_mappings YAML from stdout so the file is not needed anymore
with open('tvdb-old.yaml', mode='w') as f:
subprocess.run(
['git', 'show', 'origin/main:series-tvdb.en.yaml'],
text=True,
check=True,
stdout=f
)

with open('tvdb-old.yaml', mode='r') as f:
old_mappings = yaml.safe_load(f)

with open('series-tvdb.en.yaml', mode='r') as f:
new_mappings = yaml.safe_load(f)

# create new mapping file with just the changed entries
diff = DeepDiff(old_mappings['entries'], new_mappings['entries'], ignore_order=True)
change_groups = { "entries": [] }
# diff.affected_root_keys contains the indices of the changed entries
for key in diff.affected_root_keys:
change_groups['entries'].append(new_mappings['entries'][key])

# write the changed entries to temp.yaml
with open('temp.yaml', mode='w') as file:
yaml.dump(change_groups, file, encoding='utf-8', allow_unicode=True)


def cleanup():
os.remove("tvdb-old.yaml")
os.remove("temp.yaml")


# TODO: cross reference anilist-id show name
extractNewMappings()
# Load API Key and initialize tvdb
load_dotenv()
apikey = os.getenv("TVDB_API_KEY")
tvdb = tvdb_v4_official.TVDB(apikey)

errors = validateMappings()
if errors != 0:
sys.exit("Found " + str(errors) + " error(s) in the season mappings")
cleanup()