Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f854dc735 | |||
| f29272a853 | |||
| 4e56d9420a | |||
| 852a257159 | |||
| 68ff230f13 | |||
| 57a952a6d1 | |||
| 718fcf7392 |
56
PROJECT.org
56
PROJECT.org
@ -88,7 +88,7 @@ fetching and simple saving.
|
||||
*** Metadata sources
|
||||
**** Scraper
|
||||
|
||||
* Backlog [0/21] :vrobbler:project:personal:
|
||||
* Backlog [0/22] :vrobbler:project:personal:
|
||||
** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :bug:music:scrobbles:
|
||||
:PROPERTIES:
|
||||
:ID: 702462cf-d54b-48c6-8a7c-78b8de751deb
|
||||
@ -579,6 +579,18 @@ named constants for maintainability.
|
||||
- ~vrobbler/apps/scrobbles/importers/tsv.py~ (line 55) -- ="S"= completion status
|
||||
|
||||
|
||||
** TODO [#A] Deduplicate BGG plays before posting :boardgames:bgg:duplication:
|
||||
:PROPERTIES:
|
||||
:ID: e9b842bf-0049-42e7-a060-f3ebd0067d2f
|
||||
:END:
|
||||
|
||||
*** Description
|
||||
|
||||
No check for existing BGG plays before posting, which can create duplicates.
|
||||
Should look up past plays by =bggeek_id= first.
|
||||
|
||||
File: ~vrobbler/apps/boardgames/bgg.py~ (line 117)
|
||||
|
||||
** TODO [#C] Clean up naming of =bgsplay= parsing :importers:refactoring:
|
||||
:PROPERTIES:
|
||||
:ID: c751dbbc-464a-4e63-9fe3-e034303f7b54
|
||||
@ -591,6 +603,48 @@ a helper method to create board game scrobbles given a json blob. It's
|
||||
independent of the email flow it was originally creatdd for
|
||||
|
||||
** TODO [#B] Is there way to create unique slugs for media instances :media_types:
|
||||
|
||||
* Version 55.3 [3/3]
|
||||
** DONE [#C] =alt_names= feature for artists (commented out / dead code) :music:dead-code:
|
||||
:PROPERTIES:
|
||||
:ID: e22060a2-5f7a-4f33-9056-309ecd27159c
|
||||
:END:
|
||||
|
||||
*** Description
|
||||
|
||||
File: ~vrobbler/apps/music/models.py~ (line 236)
|
||||
|
||||
An entire block of code for tracking alternate artist names is commented
|
||||
out. The TODO questions whether it even works. Review: either implement
|
||||
properly or remove the dead code.
|
||||
|
||||
** DONE [#A] Put chart rebuilds in a lower priority task queue :charts:tasks:
|
||||
:PROPERTIES:
|
||||
:ID: 43c90de0-fc1c-1139-dac7-9b7c82006b2e
|
||||
:END:
|
||||
** DONE [#A] Check for existing book scrobble and update page count :books:scrobbling:
|
||||
:PROPERTIES:
|
||||
:ID: 1a0609bc-6b16-4da4-96c1-59588229e4b4
|
||||
:END:
|
||||
|
||||
*** Description
|
||||
|
||||
File: ~vrobbler/apps/scrobbles/scrobblers.py~ (line 330)
|
||||
|
||||
When scrobbling a book (comic), the code doesn't check for prior scrobbles to
|
||||
update reading progress. Needed for proper page-count tracking.
|
||||
|
||||
|
||||
* Version 55.2 [2/2]
|
||||
** DONE [#A] Fix bug in scrobble id in calendar view :templates:
|
||||
:PROPERTIES:
|
||||
:ID: 8cb34852-b18f-e794-cd9b-fb1ecad70a0d
|
||||
:END:
|
||||
** DONE [#A] Video game cleanup script should clear out broken images :metadata:videogames:
|
||||
:PROPERTIES:
|
||||
:ID: ca1f1ea9-0f79-082c-5ff7-867671faff4b
|
||||
:END:
|
||||
|
||||
* Version 55.1 [1/1]
|
||||
** DONE [#A] Clean up metadata scrapping for video games :metadata:videogames:
|
||||
:PROPERTIES:
|
||||
|
||||
2
Procfile
2
Procfile
@ -1,2 +1,2 @@
|
||||
web: python manage.py runserver 0.0.0.0:8014
|
||||
worker: celery -A vrobbler worker -l DEBUG
|
||||
worker: celery -A vrobbler worker -Q default,charts -l DEBUG
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "vrobbler"
|
||||
version = "55.1"
|
||||
version = "55.3"
|
||||
description = ""
|
||||
authors = ["Colin Powell <colin@unbl.ink>"]
|
||||
|
||||
|
||||
@ -236,19 +236,6 @@ class Artist(TimeStampedModel):
|
||||
)
|
||||
artist.fix_metadata()
|
||||
|
||||
# TODO: See if this alt_names stuff actually works or causes hard to debug problems
|
||||
# If we did find our artist, but the found name is slightly differnt, record that
|
||||
# if artist and alt_name:
|
||||
# if not artist.alt_names:
|
||||
# artist.alt_names = alt_name
|
||||
# else:
|
||||
# artist.alt_names += f"\\{alt_name}"
|
||||
# logger.info(
|
||||
# f"Add alt_name {alt_name} to artist {artist}",
|
||||
# extra={"alt_name": alt_name, "artist_id": artist.id},
|
||||
# )
|
||||
# artist.save(update_fields=["alt_names"])
|
||||
|
||||
return artist
|
||||
|
||||
|
||||
|
||||
@ -330,8 +330,6 @@ def manual_scrobble_book(
|
||||
|
||||
source = READCOMICSONLINE_URL.replace("https://", "")
|
||||
|
||||
# TODO: Check for scrobble of this book already and if so, update the page count
|
||||
|
||||
book = Book.find_or_create(title, url=url, enrich=True)
|
||||
|
||||
scrobble_dict = {
|
||||
|
||||
@ -1717,6 +1717,7 @@ class ScrobbleCalendarView(LoginRequiredMixin, TemplateView):
|
||||
for scrobble in day_map[day_num]:
|
||||
day_scrobbles.append(
|
||||
{
|
||||
"id": scrobble.pk,
|
||||
"uuid": scrobble.uuid,
|
||||
"emoji": self.MEDIA_EMOJI.get(scrobble.media_type, "📌"),
|
||||
"title": (
|
||||
|
||||
@ -69,6 +69,11 @@ class Command(BaseCommand):
|
||||
type=int,
|
||||
help="Only process a specific game by ID",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--fix-broken-images",
|
||||
action="store_true",
|
||||
help="Check and refetch broken/deleted game images (cover, screenshot, hltb_cover)",
|
||||
)
|
||||
for flag in MISSING_ALL:
|
||||
parser.add_argument(
|
||||
f"--missing-{flag}",
|
||||
@ -92,15 +97,16 @@ class Command(BaseCommand):
|
||||
sleep_secs = options["sleep"]
|
||||
force = options["force"]
|
||||
game_id = options["game_id"]
|
||||
fix_broken_images = options.get("fix_broken_images", False)
|
||||
flags = options.get("missing_flags") or []
|
||||
all_missing = options["all_missing"]
|
||||
|
||||
if all_missing:
|
||||
flags = MISSING_ALL
|
||||
|
||||
if not flags and not game_id and not force:
|
||||
if not flags and not game_id and not force and not fix_broken_images:
|
||||
self.stdout.write(
|
||||
"No filters specified. Use --all, --missing-*, --game-id, or --force."
|
||||
"No filters specified. Use --all, --missing-*, --game-id, --force, or --fix-broken-images."
|
||||
)
|
||||
return
|
||||
|
||||
@ -135,25 +141,33 @@ class Command(BaseCommand):
|
||||
"release_year_fixed": 0,
|
||||
"igdb_id_found": 0,
|
||||
"hltb_id_found": 0,
|
||||
"images_fixed": 0,
|
||||
}
|
||||
|
||||
for batch_num, offset in enumerate(range(0, len(qs), batch_size)):
|
||||
batch = qs[offset : offset + batch_size]
|
||||
for game in batch:
|
||||
result = self._enrich_game(game, sleep_secs, force)
|
||||
self._check_retroarch_name(game, title_mismatches)
|
||||
if result:
|
||||
enriched += 1
|
||||
for key in stats:
|
||||
if result.get(key):
|
||||
stats[key] += 1
|
||||
else:
|
||||
skipped += 1
|
||||
enriched_any = bool(flags or game_id or force)
|
||||
|
||||
self.stdout.write(
|
||||
f" Batch {batch_num + 1}: {offset + len(batch)}/{total} — "
|
||||
f"enriched: {enriched}, skipped: {skipped}"
|
||||
)
|
||||
if enriched_any:
|
||||
for batch_num, offset in enumerate(range(0, len(qs), batch_size)):
|
||||
batch = qs[offset : offset + batch_size]
|
||||
for game in batch:
|
||||
result = self._enrich_game(game, sleep_secs, force)
|
||||
self._check_retroarch_name(game, title_mismatches)
|
||||
if result:
|
||||
enriched += 1
|
||||
for key in stats:
|
||||
if result.get(key):
|
||||
stats[key] += 1
|
||||
else:
|
||||
skipped += 1
|
||||
|
||||
self.stdout.write(
|
||||
f" Batch {batch_num + 1}: {offset + len(batch)}/{total} — "
|
||||
f"enriched: {enriched}, skipped: {skipped}"
|
||||
)
|
||||
|
||||
if fix_broken_images:
|
||||
broken_stats = self._fix_broken_images(qs, sleep_secs)
|
||||
stats["images_fixed"] = broken_stats["images_fixed"]
|
||||
|
||||
self.stdout.write(
|
||||
f"\nResults (commit={commit}):\n"
|
||||
@ -168,6 +182,8 @@ class Command(BaseCommand):
|
||||
f" IGDB IDs found: {stats['igdb_id_found']}\n"
|
||||
f" HLtB IDs found: {stats['hltb_id_found']}"
|
||||
)
|
||||
if fix_broken_images:
|
||||
self.stdout.write(f" Broken images fixed: {stats['images_fixed']}")
|
||||
|
||||
if title_mismatches:
|
||||
self.stdout.write("\nTitle vs retroarch_name mismatches (not auto-fixed):")
|
||||
@ -450,3 +466,64 @@ class Command(BaseCommand):
|
||||
self.stdout.write(f" [TAG] {game} — tagged hltb-enriched")
|
||||
|
||||
return changed if any(changed.values()) else None
|
||||
|
||||
def _fix_broken_images(self, games, sleep_secs):
|
||||
from django.core.files.base import ContentFile
|
||||
|
||||
import requests
|
||||
|
||||
from videogames.igdb import lookup_game_from_igdb
|
||||
from videogames.howlongtobeat import lookup_game_from_hltb
|
||||
|
||||
stats = {"cover_fixed": 0, "screenshot_fixed": 0, "images_fixed": 0}
|
||||
|
||||
for game in games:
|
||||
for field_name, source in [
|
||||
("cover", "igdb"),
|
||||
("screenshot", "igdb"),
|
||||
("hltb_cover", "hltb"),
|
||||
]:
|
||||
field = getattr(game, field_name)
|
||||
if not field.name:
|
||||
continue
|
||||
if field.storage.exists(field.name):
|
||||
continue
|
||||
|
||||
self.stdout.write(
|
||||
f" [IMAGE] {game} — {field_name} is broken (file missing), refetching…"
|
||||
)
|
||||
|
||||
if source == "igdb" and game.igdb_id:
|
||||
data = lookup_game_from_igdb(str(game.igdb_id))
|
||||
time.sleep(sleep_secs)
|
||||
if not data:
|
||||
continue
|
||||
url = data.get("cover_url" if field_name == "cover" else "screenshot_url")
|
||||
if not url:
|
||||
continue
|
||||
r = requests.get(url)
|
||||
if r.status_code != 200:
|
||||
continue
|
||||
fname = f"{game.title}_{game.uuid}.jpg"
|
||||
getattr(game, field_name).save(fname, ContentFile(r.content), save=True)
|
||||
stats["images_fixed"] += 1
|
||||
self.stdout.write(f" [IMAGE] {game} — {field_name} refetched from IGDB")
|
||||
|
||||
elif source == "hltb" and game.hltb_id:
|
||||
data = lookup_game_from_hltb(str(game.hltb_id))
|
||||
time.sleep(sleep_secs)
|
||||
if not data:
|
||||
continue
|
||||
url = data.get("cover_url")
|
||||
if not url:
|
||||
continue
|
||||
headers = {"User-Agent": "Vrobbler 0.11.12"}
|
||||
r = requests.get(url, headers=headers)
|
||||
if r.status_code != 200:
|
||||
continue
|
||||
fname = f"{game.title}_cover_{game.uuid}.jpg"
|
||||
game.hltb_cover.save(fname, ContentFile(r.content), save=True)
|
||||
stats["images_fixed"] += 1
|
||||
self.stdout.write(f" [IMAGE] {game} — hltb_cover refetched from HLtB")
|
||||
|
||||
return stats
|
||||
|
||||
@ -122,6 +122,15 @@ CELERY_ACCEPT_CONTENT = ["json"]
|
||||
CELERY_RESULT_EXTENDED = True
|
||||
CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True
|
||||
|
||||
CELERY_TASK_CREATE_MISSING_QUEUES = True
|
||||
CELERY_TASK_ROUTES = {
|
||||
"scrobbles.tasks.update_charts_for_timestamp": {"queue": "charts"},
|
||||
"scrobbles.tasks.create_yesterdays_charts": {"queue": "charts"},
|
||||
"scrobbles.tasks.rebuild_weekly_charts": {"queue": "charts"},
|
||||
"scrobbles.tasks.rebuild_monthly_charts": {"queue": "charts"},
|
||||
"scrobbles.tasks.rebuild_yearly_charts": {"queue": "charts"},
|
||||
}
|
||||
|
||||
CELERY_BEAT_SCHEDULE = {
|
||||
"build-yesterdays-charts": {
|
||||
"task": "scrobbles.tasks.create_yesterdays_charts",
|
||||
|
||||
Reference in New Issue
Block a user