- ID
- 89b6e1e6-411f-4a0b-bb6d-1944b3083372
Vrobbler
A basic webhook consumer for Jellyfin's media and Mopidy's music
Design
At it's most basic, Vrobbler presents a frontend for browing through peronal data related to "scrobbled" media, and a handful of endpoints for services to post data to the service for getting playback history into the database.
One of the design goals is to keep the layout of data super clear so it's possible to manipulate data via the database, not just the Django webapp that imports all of it.
Debugging info
#+begin_src python
import json
from flask import Flask, request
app = Flask(__name__)
@app.route("/webhook", methods=["POST", "GET"])
def webhook():
data_dict = json.loads(request.data.decode("utf-8"))
try:
run_time_ticks = data_dict["RunTimeTicks"]
play_back_ticks = data_dict["PlaybackPositionTicks"]
play_percent = int((play_back_ticks / run_time_ticks) * 100)
episode_num = data_dict["EpisodeNumber"]
season_num = data_dict["SeasonNumber"]
show_name = data_dict["SeriesName"]
app.logger.info(
f"You are {play_percent}% through an episode {episode_num} of season {season_num} of {show_name}"
)
except:
pass
return "Webhook received!"
app.run(host="0.0.0.0", port=8000, debug=True)
#+end_src
What Jellyfin dumps via the Webhook plugin
#+begin_src json
{
"ServerId": "2f31a8e3d3e9416a91df2ed5f0e9dfb2",
"ServerName": "jellyfin1",
"ServerVersion": "10.8.8",
"ServerUrl": "",
"NotificationType": "PlaybackStop",
"Timestamp": "2023-01-04T14:42:36.4243951-05:00",
"UtcTimestamp": "2023-01-04T19:42:36.4243987Z",
"Name": "Daybreak",
"Overview": "John Dutton, patriarch of a Montana ranching family and owner of the largest ranch in the U.S., fights to defend his land and his family from the modern-day forces that threaten his way of life; land developers, an Indian reservation and America\\u0027s first National Park.",
"Tagline": "",
"ItemId": "74eafb665657e902a34b4c78c779472b",
"ItemType": "Episode",
"RunTimeTicks": 55637880832,
"RunTime": "01:32:43",
"Year": 2018,
"SeriesName": "Yellowstone (2018)",
"SeasonNumber": 1,
"SeasonNumber00": "01",
"SeasonNumber000": "001",
"EpisodeNumber": 1,
"EpisodeNumber00": "01",
"EpisodeNumber000": "001",
"Provider_tvdb": "6507316",
"Provider_imdb": "tt7529124",
"Provider_tvrage": "785164",
"Video_0_Title": "720p H264 SDR",
"Video_0_Type": "Video",
"Video_0_Codec": "h264",
"Video_0_Profile": "Main",
"Video_0_Level": 31,
"Video_0_Height": 720,
"Video_0_Width": 1280,
"Video_0_AspectRatio": "2:1",
"Video_0_Interlaced": false,
"Video_0_FrameRate": 23.976025,
"Video_0_VideoRange": "SDR",
"Video_0_ColorSpace": "bt709",
"Video_0_ColorTransfer": "bt709",
"Video_0_ColorPrimaries": "bt709",
"Video_0_PixelFormat": "yuv420p",
"Video_0_RefFrames": 1,
"Audio_0_Title": "Stereo - English - AAC - Default",
"Audio_0_Type": "Audio",
"Audio_0_Language": "eng",
"Audio_0_Codec": "aac",
"Audio_0_Channels": 2,
"Audio_0_Bitrate": 192000,
"Audio_0_SampleRate": 48000,
"Audio_0_Default": true,
"Subtitle_0_Title": "English - ASS",
"Subtitle_0_Type": "Subtitle",
"Subtitle_0_Language": "eng",
"Subtitle_0_Codec": "ass",
"Subtitle_0_Default": false,
"Subtitle_0_Forced": false,
"Subtitle_0_External": false,
"PlaybackPositionTicks": 7820973099,
"PlaybackPosition": "00:13:02",
"MediaSourceId": "74eafb665657e902a34b4c78c779472b",
"IsPaused": false,
"IsAutomated": false,
"DeviceId": "TW96aWxsYS81LjAgKE1hY2ludG9zaDsgSW50ZWwgTWFjIE9TIFggMTAuMTU7IHJ2OjEwNi4wKSBHZWNrby8yMDEwMDEwMSBGaXJlZm94LzEwNi4wfDE2NjgyMjI3MDAxODk1",
"DeviceName": "Firefox",
"ClientName": "Jellyfin Web",
"PlayedToCompletion": false,
"NotificationUsername": "secstate",
"UserId": "f78f0902128343e7957593c976636419"
}
#+end_src
#+begin_src json
{
"ServerId": "2f31a8e3d3e9416a91df2ed5f0e9dfb2",
"ServerName": "jellyfin1",
"ServerVersion": "10.8.8",
"ServerUrl": "",
"NotificationType": "PlaybackProgress",
"Timestamp": "2023-01-04T17:04:24.5253584-05:00",
"UtcTimestamp": "2023-01-04T22:04:24.5253596Z",
"Name": "That Darn Cat!",
"Overview": "A young woman suspects foul play when her cat comes home wearing a wristwatch. Convincing the FBI, though, and catching the bad guys is tougher than she imagined.",
"Tagline": "It takes a Siamese secret agent to unravel the PURR-fect crime!",
"ItemId": "77c6c3a53082a68896fb2823702314cf",
"ItemType": "Movie",
"RunTimeTicks": 69870510080,
"RunTime": "01:56:27",
"Year": 1965,
"Provider_tmdb": "20723",
"Provider_imdb": "tt0059793",
"Video_0_Title": "720p H264 SDR",
"Video_0_Type": "Video",
"Video_0_Codec": "h264",
"Video_0_Profile": "High",
"Video_0_Level": 41,
"Video_0_Height": 726,
"Video_0_Width": 1280,
"Video_0_AspectRatio": "16:9",
"Video_0_Interlaced": false,
"Video_0_FrameRate": 23.976025,
"Video_0_VideoRange": "SDR",
"Video_0_ColorSpace": "bt709",
"Video_0_ColorTransfer": "bt709",
"Video_0_ColorPrimaries": "bt709",
"Video_0_PixelFormat": "yuv420p",
"Video_0_RefFrames": 1,
"Audio_0_Title": "AAC - Stereo - Default",
"Audio_0_Type": "Audio",
"Audio_0_Language": "und",
"Audio_0_Codec": "aac",
"Audio_0_Channels": 2,
"Audio_0_Bitrate": 128459,
"Audio_0_SampleRate": 48000,
"Audio_0_Default": true,
"PlaybackPositionTicks": 32384675839,
"PlaybackPosition": "00:53:58",
"MediaSourceId": "77c6c3a53082a68896fb2823702314cf",
"IsPaused": false,
"IsAutomated": false,
"DeviceId": "TW96aWxsYS81LjAgKE1hY2ludG9zaDsgSW50ZWwgTWFjIE9TIFggMTAuMTU7IHJ2OjEwNi4wKSBHZWNrby8yMDEwMDEwMSBGaXJlZm94LzEwNi4wfDE2NjgyMjI3MDAxODk1",
"DeviceName": "Firefox",
"ClientName": "Jellyfin Web",
"NotificationUsername": "secstate",
"UserId": "f78f0902128343e7957593c976636419"
}
#+end_src