From a356c53954b2ef90767cd6782281d9b635c41bfe Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Tue, 3 Oct 2023 22:26:40 +0200 Subject: [PATCH] Use DOMjudge API to fetch images and team names for domlogo --- domlogo/domlogo.py | 108 +++++++++++++++++- .../ansible/roles/domlogo/files/README.md | 31 +++-- .../ansible/roles/domlogo/tasks/main.yml | 5 +- 3 files changed, 120 insertions(+), 24 deletions(-) diff --git a/domlogo/domlogo.py b/domlogo/domlogo.py index bd511716..1e67ce11 100755 --- a/domlogo/domlogo.py +++ b/domlogo/domlogo.py @@ -1,4 +1,5 @@ -#!/usr/bin/python3 +#!/usr/bin/env python3 +import subprocess import PySimpleGUI as sg import glob @@ -9,6 +10,29 @@ import platform import yaml + +def download_image(image_type: str, entity_id: str, file: dict): + href = file['href'] + filename = file['filename'] + photo_head = requests.head(f'{api_url}/{href}', auth=(user, passwd)) + etag = photo_head.headers['ETag'] + etag_file = f'domlogo-files/{image_type}s/{entity_id}.etag.txt' + temp_file = f'domlogo-files/{image_type}s/temp-{entity_id}-{filename}' + existing_etag = None + if os.path.isfile(etag_file): + with open(etag_file) as f: + existing_etag = f.readline().strip() + + if existing_etag != etag: + print(f'Downloading and converting {image_type} for entity with ID {entity_id}...') + with open(temp_file, 'wb') as f: + f.write(requests.get(f'{api_url}/{href}', auth=(user, passwd)).content) + + return True, temp_file, etag_file, etag + + return False, None, None, None + + font = ('Roboto', 14) mono_font = ('Liberation Mono', 32) host = platform.node() @@ -50,6 +74,73 @@ break print(f'Using {api_url} as endpoint.') +print('Loading teams and organizations from API') +teams = {team['id']: team for team in requests.get(f'{api_url}/teams', auth=(user, passwd)).json()} +for team_id in teams: + if 'display_name' not in teams[team_id]: + teams[team_id]['display_name'] = teams[team_id]['name'] +organizations = {org['id']: org for org in requests.get(f'{api_url}/organizations', auth=(user, passwd)).json()} + + +print('Downloading any new or changed logos and photos...') +for organization in organizations.values(): + if 'logo' in organization: + organization_id = organization['id'] + logo = organization['logo'][0] + downloaded, downloaded_to, etag_file, etag = download_image('logo', organization_id, logo) + if downloaded_to: + # Convert to both 64x64 (for sidebar) and 160x160 (for overlay over photo) + target = f'domlogo-files/logos/{organization_id}.png' + command = f'convert {downloaded_to} -resize 64x64 -background none -gravity center -extent 64x64 {target}' + os.system(command) + + target = f'domlogo-files/logos/{organization_id}.160.png' + command = f'convert {downloaded_to} -resize 160x160 -background none -gravity center -extent 160x160 {target}' + os.system(command) + + with open(etag_file, 'w') as f: + f.write(etag) + + os.unlink(downloaded_to) + +for team in teams.values(): + if 'photo' in team and team['display_name'] != 'DOMjudge': + team_id = team['id'] + photo = team['photo'][0] + downloaded, downloaded_to, etag_file, etag = download_image('photo', team_id, photo) + if downloaded_to: + # First convert to a good known size because adding the annotation and logo assumes this + intermediate_target = f'domlogo-files/photos/{team_id}-intermediate.png' + command = f'convert {downloaded_to} -resize 1024x1024 -gravity center {intermediate_target}' + os.system(command) + + # Now add logo and team name. We use subprocess.run here to escape the team name + target = f'domlogo-files/photos/{team_id}.png' + organization_id = team['organization_id'] + logo_file = f'domlogo-files/logos/{organization_id}.png' + command = [ + 'convert', + intermediate_target, + '-fill', 'white', + '-undercolor', '#00000080', + '-gravity', 'south', + '-font', 'Ubuntu', + '-pointsize', '30', + '-annotate', '+5+5', f' {team["display_name"]} ', + logo_file, + '-gravity', 'northeast', + '-composite', + target + ] + + subprocess.run(command) + + with open(etag_file, 'w') as f: + f.write(etag) + + os.unlink(downloaded_to) + os.unlink(intermediate_target) + latest_logfile = max(glob.glob('output/log/judge.*-2.log'), key=os.path.getctime) print(f'Checking logfile {latest_logfile}') with open(latest_logfile, 'r') as logfile: @@ -79,7 +170,7 @@ team_id = submission_data['team_id'] last_seen = (submission_id, judging_id, team_id) new_filename = f'domlogo-files/photos/{team_id}.png' - if not team_id.isdigit(): + if not os.path.isfile(new_filename): new_filename = f'domlogo-files/photos/crew.png' team_image.update(filename=new_filename) metadata_text.update(f's{submission_id} / {submission_data["problem_id"]} / {submission_data["language_id"]}') @@ -108,9 +199,16 @@ color = 'DeepSkyBlue' for i in range(len(cache)-1): cache[i] = cache[i+1] - if not tid.isdigit(): - tid = 'DOMjudge' - cache[-1] = (f'domlogo-files/logos/{tid}.png', f's{sid}/j{jid}\n{verdict}', color, jid) + organization_id = None + if tid in teams: + organization_id = teams[tid]['organization_id'] + organization_logo = 'domlogo-files/logos/DOMjudge.png' + # Organization ID is null for internal teams so explicitly check for it + if organization_id: + potential_organization_logo = f'domlogo-files/logos/{organization_id}.png' + if os.path.isfile(potential_organization_logo): + organization_logo = potential_organization_logo + cache[-1] = (organization_logo, f's{sid}/j{jid}\n{verdict}', color, jid) for i in range(len(cache)): previous_column[i][0].update(filename=cache[i][0]) previous_column[i][1].update(cache[i][1]) diff --git a/provision-contest/ansible/roles/domlogo/files/README.md b/provision-contest/ansible/roles/domlogo/files/README.md index c2759f15..43879e78 100644 --- a/provision-contest/ansible/roles/domlogo/files/README.md +++ b/provision-contest/ansible/roles/domlogo/files/README.md @@ -1,20 +1,17 @@ -# Generating logos from a Contest Package +# Preparing DOMlogo -```bash -for team in $(cat ~/wf2021/contests/finals/teams.json | jq -r '.[].id'); do - echo $team - ORG_ID=$(cat ~/wf2021/contests/finals/teams.json | jq -r ".[] | select(.id == \"$team\") | .organization_id") - convert ~/wf2021/contests/finals/organizations/$ORG_ID/logo.png -resize 64x64 -background none -gravity center -extent 64x64 $team.png -done -``` +First, create the following files: +- `images/logos/DOMjudge.png`, a 64x64 DOMjudge logo with transparent background. +- `images/photos/crew.png`, an image with a width of 1024 (and any height) to show for teams without a photo. +- `images/photos/idle.png`, an image with a width of 1024 (and any height) to show when the judgedaemon is idle. + +Next, add the needed Python dependencies to the `lib` folder, within a folder called `python3.8`. You can copy this +folder from a local machine and it should contain the `PySimpleGUI` and `requests` Python packages. -# Generating photos from a Contest package +Optionally you can create a file `images/config.yaml` with something like: + +```yaml +host-bg-color: '#013370' +``` -```bash -for team in $(cat ~/wf2021/contests/finals/teams.json | jq -r '.[].id'); do - echo $team - ORG_ID=$(cat ~/wf2021/contests/finals/teams.json | jq -r ".[] | select(.id == \"$team\") | .organization_id") - TEAM_NAME=$(cat ~/wf2021/contests/finals/teams.json | jq -r ".[] | select(.id == \"$team\") | .display_name") - convert ~/wf2021/contests/finals/teams/$team/photo.jpg -fill white -undercolor '#00000080' -gravity south -font 'Ubuntu' -pointsize 30 -annotate +5+5 " $TEAM_NAME " ~/wf2021/contests/finals/organizations/$ORG_ID/logo.160x160.png -gravity northeast -composite -resize 1024x1024 $team.png -done -``` \ No newline at end of file +DOMlogo will use the DOMjudge API to download logos and photos for all teams, so no further configuration should be needed. diff --git a/provision-contest/ansible/roles/domlogo/tasks/main.yml b/provision-contest/ansible/roles/domlogo/tasks/main.yml index ce2cb112..aa7331b8 100644 --- a/provision-contest/ansible/roles/domlogo/tasks/main.yml +++ b/provision-contest/ansible/roles/domlogo/tasks/main.yml @@ -1,11 +1,12 @@ --- # These tasks install domlogo -- name: Install python3 modules for domlogo +- name: Install python3 modules and imagemagick for domlogo apt: state: present pkg: - python3-tk + - imagemagick - name: Install domlogo copy: @@ -17,7 +18,7 @@ loop: - domlogo -- name: Install domlogo +- name: Install domlogo Python libraries synchronize: src: lib dest: /home/domjudge/.local/