Vrobbler Project
[3/27]
[3/3]
[3/3]
[2/2]
[3/3]
[1/1]
[1/1]
[2/2]
[1/1]
[4/4]
[6/6]
[19/19]
[9/9]
Vrobbler began humbly enough as a way to use Jellyfin's webhook to keep track of the shows and movies I was watching. More specifically, I broke my ankle a few days after Christmas in 2022 and spent the next four months very slowly recovering after surgical repair. So once I had the webhook working, and scrobbling videos, it was only a matter of time till I expaned it to mopidy to replicate LastFM. Then I added board games, books via KoReader, sports events, podcasts … it just keeps going. Vrobbler is now a sort of Frankenstein's monster of scrobbling an entire life.
I am still unconvinced I can keep this going, but being able to scrobble org tasks, Todoist tasks, web pages I've read and trails I've hiked has turned out to be sometimes cathartic and sometimes functional as I try to remember when I did a thing.
This is the preferred method at this time. Also, the Book model implements a `find_or_create` classmethod which is an example of an interface we can use for other data models to get metadata in a way that provides easy testing, bulk fetching and simple saving.
CLOCK: [2025-07-09 Wed 09:55]–[2025-07-09 Wed 10:15] => 0:20
[3/27]
{ "type": "event", "event": "track_playback_ended", "data": { "tl_track": { "__model__": "TlTrack", "tlid": 13, "track": { "__model__": "Track", "uri": "file:///var/lib/mopidy/media/podcasts/The%20Prince/2022-09-28-Wolf-warriors.mp3", "name": "Wolf warriors", "artists": [ { "__model__": "Artist", "name": "The Economist" } ], "album": { "__model__": "Album", "name": "The Prince", "date": "2022" }, "genre": "Blues", "date": "2022", "length": 2437778, "bitrate": 127988 } }, "time_position": 3290 } }
{ "type": "event", "event": "playback_state_changed", "data": { "old_state": "paused", "new_state": "playing" } }
{ "type": "event", "event": "playback_state_changed", "data": { "old_state": "stopped", "new_state": "playing" } }
{ "type": "event", "event": "track_playback_started", "data": { "tl_track": { "__model__": "TlTrack", "tlid": 13, "track": { "__model__": "Track", "uri": "file:///var/lib/mopidy/media/podcasts/The%20Prince/2022-09-28-Wolf-warriors.mp3", "name": "Wolf warriors", "artists": [ { "__model__": "Artist", "name": "The Economist" } ], "album": { "__model__": "Album", "name": "The Prince", "date": "2022" }, "genre": "Blues", "date": "2022", "length": 2437778, "bitrate": 127988 } } } }
{ "type": "status", "data": { "state": "paused", "current_track": { "__model__": "Track", "uri": "file:///var/lib/mopidy/media/podcasts/The%20Prince/2022-09-28-Wolf-warriors.mp3", "name": "Wolf warriors", "artists": [ { "__model__": "Artist", "name": "The Economist" } ], "album": { "__model__": "Album", "name": "The Prince", "date": "2022" }, "genre": "Blues", "date": "2022", "length": 2437778, "bitrate": 127988 }, "time_position": 2350 } }
{ "type": "event", "event": "track_playback_started", "data": { "tl_track": { "__model__": "TlTrack", "tlid": 14, "track": { "__model__": "Track", "uri": "local:track:Various%20Artists%20-%202008%20-%20Twilight%20OST/01-muse-supermassive_black_hole.mp3", "name": "Supermassive Black Hole", "artists": [ { "__model__": "Artist", "uri": "local:artist:md5:250dd6551b66a58a6b4897aa697f200c", "name": "Muse", "musicbrainz_id": "9c9f1380-2516-4fc9-a3e6-f9f61941d090" } ], "album": { "__model__": "Album", "uri": "local:album:md5:455343d54cdd89cb5a3b5ad537ea99d0", "name": "Twilight: Original Motion Picture Soundtrack", "artists": [ { "__model__": "Artist", "uri": "local:artist:md5:54e4db2d5624f80b0cc290346e696756", "name": "Various Artists", "musicbrainz_id": "89ad4ac3-39f7-470e-963a-56509c546377" } ], "num_tracks": 12, "num_discs": 1, "date": "2008-11-04", "musicbrainz_id": "b4889eaf-d9f4-434c-a68d-69227b12b6a4" }, "composers": [ { "__model__": "Artist", "uri": "local:artist:md5:4d49cbca0b347e0a89047bb019d2779d", "name": "Matt Bellamy" } ], "genre": "Rock", "track_no": 1, "disc_no": 1, "date": "2008-11-04", "length": 211121, "musicbrainz_id": "ff1e3e1a-f6e8-4692-b426-355880383bb6", "last_modified": 1672712949510 } } } }
{ "type": "status", "data": { "state": "playing", "current_track": { "__model__": "Track", "uri": "local:track:Various%20Artists%20-%202008%20-%20Twilight%20OST/01-muse-supermassive_black_hole.mp3", "name": "Supermassive Black Hole", "artists": [ { "__model__": "Artist", "uri": "local:artist:md5:250dd6551b66a58a6b4897aa697f200c", "name": "Muse", "musicbrainz_id": "9c9f1380-2516-4fc9-a3e6-f9f61941d090" } ], "album": { "__model__": "Album", "uri": "local:album:md5:455343d54cdd89cb5a3b5ad537ea99d0", "name": "Twilight: Original Motion Picture Soundtrack", "artists": [ { "__model__": "Artist", "uri": "local:artist:md5:54e4db2d5624f80b0cc290346e696756", "name": "Various Artists", "musicbrainz_id": "89ad4ac3-39f7-470e-963a-56509c546377" } ], "num_tracks": 12, "num_discs": 1, "date": "2008-11-04", "musicbrainz_id": "b4889eaf-d9f4-434c-a68d-69227b12b6a4" }, "composers": [ { "__model__": "Artist", "uri": "local:artist:md5:4d49cbca0b347e0a89047bb019d2779d", "name": "Matt Bellamy" } ], "genre": "Rock", "track_no": 1, "disc_no": 1, "date": "2008-11-04", "length": 211121, "musicbrainz_id": "ff1e3e1a-f6e8-4692-b426-355880383bb6", "last_modified": 1672712949510 }, "time_position": 17031 } }
{ "type": "event", "event": "track_playback_paused", "data": { "tl_track": { "__model__": "TlTrack", "tlid": 14, "track": { "__model__": "Track", "uri": "local:track:Various%20Artists%20-%202008%20-%20Twilight%20OST/01-muse-supermassive_black_hole.mp3", "name": "Supermassive Black Hole", "artists": [ { "__model__": "Artist", "uri": "local:artist:md5:250dd6551b66a58a6b4897aa697f200c", "name": "Muse", "musicbrainz_id": "9c9f1380-2516-4fc9-a3e6-f9f61941d090" } ], "album": { "__model__": "Album", "uri": "local:album:md5:455343d54cdd89cb5a3b5ad537ea99d0", "name": "Twilight: Original Motion Picture Soundtrack", "artists": [ { "__model__": "Artist", "uri": "local:artist:md5:54e4db2d5624f80b0cc290346e696756", "name": "Various Artists", "musicbrainz_id": "89ad4ac3-39f7-470e-963a-56509c546377" } ], "num_tracks": 12, "num_discs": 1, "date": "2008-11-04", "musicbrainz_id": "b4889eaf-d9f4-434c-a68d-69227b12b6a4" }, "composers": [ { "__model__": "Artist", "uri": "local:artist:md5:4d49cbca0b347e0a89047bb019d2779d", "name": "Matt Bellamy" } ], "genre": "Rock", "track_no": 1, "disc_no": 1, "date": "2008-11-04", "length": 211121, "musicbrainz_id": "ff1e3e1a-f6e8-4692-b426-355880383bb6", "last_modified": 1672712949510 } }, "time_position": 67578 } }
Pretty clear, I would love to make trails more useful. Historically I wasn't hiking a lot, which made the source for this a bit silly. But it's clear that AllTrails is the best source, though having TrailForks is nice to.
Would be nice to have some loose connection to the actual event in my Garmin profile.
Could be as simple as a JSON form on the scrobble detail page (do I have have one of those yet?).
ERROR django.request:241 log_response Internal Server Error: /series/c24100d1-da45-4abe-86bf-27cfce9b1f89/ Traceback (most recent call last): File "/usr/local/lib/python3.11/site-packages/django/core/handlers/exception.py", line 55, in inner response = get_response(request) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/django/core/handlers/base.py", line 197, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/django/views/generic/base.py", line 104, in view return self.dispatch(request, *args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/django/contrib/auth/mixins.py", line 73, in dispatch return super().dispatch(request, *args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/django/views/generic/base.py", line 143, in dispatch return handler(request, *args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/django/views/generic/detail.py", line 109, in get context = self.get_context_data(object=self.object) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.11/site-packages/vrobbler/apps/videos/views.py", line 33, in get_context_data context_data["next_episode_id"] = "tt" + next_episode_id ~~~~~^~~~~~~~~~~~~~~~~ TypeError: can only concatenate str (not "NoneType") to str
Rather than pick up an existing Podcast using the podcast title in the mopidy file name, Vrobbler creates a new podcast with no enriched data. Not a big deal for my use as the volume of podcasts I listen to makes manual fixes easy. But it's annoying.
77e01251
-cb20-4609-b577-d48e985d2e
This is great, because there's more context there, but it has to read out of
the RSS feed. We should add a check in the podcast util to sniff out the file
referenced in the # in that url and populate the info from there. This should
actually be much more reliable than the current state of the podcast lookup
which depends on the file to be name properly.[3/3]
[3/3]
[2/2]
[3/3]
[1/1]
[1/1]
[2/2]
[2025-07-11 14:23]
[1/1]
[4/4]
{ "about": "This is a Play file that can be read by Board Game Stats. If you see this text, try to use a share, export or open-in function to open it with Board Game Stats.", "players": [ { "uuid": "31f8b92e-11d8-4162-88b1-fd9c79eea249", "id": 2, "name": "Colin", "isAnonymous": false, "modificationDate": "2025-07-01 18:10:32", "metaData": "{\"isNpc\":0}" }, { "uuid": "00074700-cf4e-4ad3-b334-d35805bb0d90", "id": 4, "name": "Asa Sewell", "isAnonymous": false, "modificationDate": "2025-07-01 18:03:37" } ], "locations": [ { "uuid": "14f7389c-767f-4725-9b35-906c407b293c", "id": 3, "name": "Timberwyck Farm", "modificationDate": "2025-07-01 18:03:38" } ], "games": [ { "uuid": "043a2851-f201-467a-a60c-0b0a7e9c33d2", "id": 333, "name": "Ghost Fightin' Treasure Hunters: Anniversary Edition", "modificationDate": "2025-07-02 01:37:14", "cooperative": true, "highestWins": true, "noPoints": false, "usesTeams": false, "urlThumb": "https://cf.geekdo-images.com/DHA-mcH3zzw_OjfDxOPj1A__thumb/img/UhaIm4KIDIiraUc44QIvSAbMUXI=/fit-in/200x150/filters:strip_icc()/pic8266874.jpg", "urlImage": "https://cf.geekdo-images.com/DHA-mcH3zzw_OjfDxOPj1A__original/img/2-Lb6nLePhn0I0Hh2j1pOtbO4rg=/0x0/filters:format(jpeg)/pic8266874.jpg", "bggName": "Ghost Fightin' Treasure Hunters: Anniversary Edition", "bggYear": 2024, "bggId": 422668, "designers": "Brian Yu", "isBaseGame": 1, "isExpansion": 0, "rating": 75, "minPlayerCount": 2, "maxPlayerCount": 5, "minPlayTime": 30, "maxPlayTime": 0, "minAge": 8 } ], "plays": [ { "uuid": "bae3f29e-5e1e-45d8-b409-47a665c8d5b5", "modificationDate": "2025-07-02 01:37:59", "entryDate": "2025-07-02 01:31:38", "playDate": "2025-07-02 01:31:38", "usesTeams": false, "durationMin": 23, "ignored": false, "manualWinner": true, "rounds": 3, "scoresheet": "{\"bggId\":244711,\"version\":1,\"langCode\":\"en\",\"scoreType\":\"bestTotalWins\",\"groups\":[{\"templateId\":\"1\",\"maxRepeat\":-1,\"repetition\":1,\"hasSubTotal\":false,\"hideSingleGroupLabel\":false,\"isExtra\":false,\"rows\":[{\"templateId\":\"vptrack\",\"label\":\"VP track\",\"repetition\":1,\"repeatable\":false,\"negative\":false,\"isExtra\":false,\"scores\":{}},{\"templateId\":\"objectives\",\"label\":\"Objectives\",\"repetition\":1,\"repeatable\":false,\"negative\":false,\"isExtra\":false,\"scores\":{}},{\"templateId\":\"mastercards\",\"label\":\"Master cards\",\"repetition\":1,\"repeatable\":false,\"negative\":false,\"isExtra\":false,\"scores\":{}}]}]}", "locationRefId": 3, "gameRefId": 333, "board": "", "scoringSetting": 4, "metaData": "{\"playUsedGameCopy\":2}", "playerScores": [ { "score": "", "winner": true, "newPlayer": true, "startPlayer": false, "playerRefId": 4, "role": "", "rank": 0, "seatOrder": 0, "metaData": "{\"scoreUuid\":\"00074700-cf4e-4ad3-b334-d35805bb0d90\"}" }, { "score": "", "winner": true, "newPlayer": true, "startPlayer": false, "playerRefId": 2, "role": "", "rank": 0, "seatOrder": 0, "metaData": "{\"scoreUuid\":\"31f8b92e-11d8-4162-88b1-fd9c79eea249\"}" } ], "expansionPlays": [] } ], "userInfo": { "meRefId": 2 } }
[6/6]
Cinemagoer broke and I probably should find a more reilable source of video data.
<2025-06-13 Fri>
<2025-06-12 Thu 9:30>
Not sure if the problem is in my Emacs hook sending or Vrobbler itself.
490d60cbbb
`[19/19]
Effectively, any track that comes in without a MusicBrainz ID does some funky lookup where it doesn't find a track without an MB id and the track title / artist combination and creates a new track every time. This has to be cleaned up by condensing the duplicated tracks into the original proper track.
But it opens a bigger question about how much MB id should the drive the app lookup. If it can't be depended on to exist from all sources, it really can't be canonical. Instead, the combination of track title / artist is really the best we can do. Last.fm also has this problem, where it doesn't know about albums and definitely does not know or care about MB ids.
93c16d80ec
CLOSED: [2023-04-06 Thu 14:09]
CLOSED: [2023-04-02 Sun 23:58]
Essentially, we currently have the timestamp as when the content began scrobbling and then calculate the finish time from the length of the content. This works pretty well because we know how long most things are.
But in some cases, sports events or long podcasts, we may start mid-way through an event or finish halfway through but still want to mark it as done. In these cases, knowing the finish time could be useful, especially when interfacing with other scrobblers which may have different definitions of when a scrobble finishes or started.
CLOSED: [2023-03-27 Mon 20:18]
CLOCK: [2023-03-26 Sun 22:01]–[2023-03-27 Mon 01:07] => 3:06
CLOSED: [2023-03-26 Sun 13:52]
CLOSED: [2023-03-26 Sun 13:51]
CLOCK: [2023-03-26 Sun 13:11]–[2023-03-26 Sun 13:51] => 0:40
CLOSED: [2023-03-24 Fri 14:46]
CLOCK: [2023-03-24 Fri 10:47]–[2023-03-24 Fri 14:46] => 3:59 CLOCK: [2023-03-24 Fri 10:36]–[2023-03-24 Fri 10:40] => 0:04
CLOSED: [2023-03-24 Fri 10:45]
CLOCK: [2023-03-24 Fri 10:40]–[2023-03-24 Fri 10:46] => 0:06
CLOSED: [2023-03-24 Fri 00:31] The live view will be blank every Monday, no reason to tie it to a day of the week. It should be "the last 7 days"
CLOSED: [2023-03-22 Wed 17:05]
CLOSED: [2023-03-22 Wed 17:05]
CLOSED: [2023-03-22 Wed 17:04]
CLOSED: [2023-03-22 Wed 17:01]
CLOSED: [2023-03-07 Tue 11:13]
CLOSED: [2023-03-22 Wed 17:06]
An example: https://github.com/web-scrobbler/web-scrobbler/blob/master/src/core/background/scrobbler/maloja-scrobbler.js
This is actually going to be moot because we can import from LastFM, and web-scrobbler integrates well with LastFM. The only thing to think through here now is what to do with all the garbage web-scrobbler sometimes pushes to LastFM from Youtube (all videos get pushed, sigh).
This turned out to be a non-starter … Amazon is aggressive at disallowing scraping quality. And all the OSS tools out there are stuck in an arms race trying to keep them from breaking.
That said, Google Books actually has a decent API (for now), and I've built this out using that.
This was fixed a while ago, but there's a new manifested bug. Going to create a separate bug tracking ticket for that.
[9/9]
CLOSED: [2023-03-07 Tue 11:11]
CLOSED: [2023-03-07 Tue 11:11]
CLOSED: [2023-01-29 Sun 14:27]
When we get artwork from Musicbrianz, and it's not found, we should check for release groups as well. This will stop issues with missing artwork because of obscure MB release matches.
CLOSED: [2023-01-30 Mon 18:31]
CLOCK: [2023-01-30 Mon 18:00]–[2023-01-30 Mon 18:31] => 0:31
If we play music from Jellyfin and the track reaches 90% completion, the scrobbling goes crazy and starts creating new scrobbles with every update.
The cause is pretty simple, but the solution is hard. We want to mark a scrobble as complete for the following conditions:
But if we keep listening beyond 90, we should basically ignore updates (or just update the existing scrobble)
CLOSED: [2023-02-03 Fri 16:52]
An example of the format:
, #AUDIOSCROBBLER/1.1 #TZ/UNKNOWN #CLIENT/Rockbox sansaclipplus $Revision$ 75 Dollar Bill I Was Real I Was Real 4 1015 S 1740494944 64ff5f53-d187-4512-827e-7606c69e66ff 75 Dollar Bill I Was Real I Was Real 4 1015 S 1740494990 64ff5f53-d187-4512-827e-7606c69e66ff 311 311 Down 1 173 S 1740495003 00476c23-fd9e-464b-9b27-a62d69f3d4f4 311 311 Down 1 173 L 1740495049 00476c23-fd9e-464b-9b27-a62d69f3d4f4 311 311 Down 1 173 L 1740495113 00476c23-fd9e-464b-9b27-a62d69f3d4f4 311 311 Random 2 187 S 1740495190 530c09f3-46fe-4d90-b11f-7b63bcb4b373 311 311 Random 2 187 L 1740495194 530c09f3-46fe-4d90-b11f-7b63bcb4b373 311 311 Jackolantern’s Weather 3 204 L 1740495382 cc3b2dec-5d99-47ea-8930-20bf258be4ea 311 311 All Mixed Up 4 182 L 1740495586 980a78b5-5bdd-4f50-9e3a-e13261e2817b 311 311 Hive 5 179 L 1740495768 18f6dc98-d3a2-4f81-b967-97359d14c68c 311 311 Guns (Are for Pussies) 6 137 L 1740495948 5e97ed9f-c8cc-4282-9cbe-f8e17aee5128 311 311 Misdirected Hostility 7 179 S 1740496085 61ff2c1a-fc9c-44c3-8da1-5e50a44245af ,
CLOSED: [2023-02-17 Fri 00:10]
This would allow a few nice flows. One, you'd be able to record the play of an entire album by just dropping the muscibrainz_id in. This could be helpful for offline listening. It would also mean bad metadata from mopidy would not break scrobbling.
CLOSED: [2023-02-17 Fri 00:11]
CLOSED: [2023-03-07 Tue 11:09]
Given a UUID from musicbrainz, we should be able to scrobble an album or individual track.
CLOSED: [2023-03-07 Tue 11:10]
CLOCK: [2023-01-30 Mon 16:30]–[2023-01-30 Mon 18:00] => 1:30
Maloja does this cool thing where artists and tracks get recorded as the top track of a given week, month or year. They get gold, silver or bronze stars for their place in the time period.
I could see this being implemented as a separate Chart table which gets populated at the end of a time period and has a start and end date that defines a period, along with a one, two, three instance.
Of course, it could also be a data model without a table, where it runs some fun calculations, stores it's values in Redis as a long-term lookup table and just has to re-populate when the server restarts.