add jellyfin api client

This commit is contained in:
2025-12-09 23:48:05 +01:00
parent f041499eb7
commit 265e646d96
5 changed files with 119 additions and 90 deletions

View File

@@ -1,6 +1,6 @@
from pypresence import Presence
from settings import settings
from discord.models import DiscordRPCPayload
from discord.models import DiscordRPCUpdatePayload
import logging
class DiscordRPC:
@@ -12,7 +12,7 @@ class DiscordRPC:
self.rpc.connect()
self.logger.info("Connected to Discord RPC.")
def update(self, payload: DiscordRPCPayload):
def update(self, payload: DiscordRPCUpdatePayload):
self.logger.info("Updating Discord RPC presence...")
self.rpc.update(
activity_type=payload.activity_type,

View File

@@ -1,45 +0,0 @@
from jellyfin_apiclient_python import JellyfinClient
from getmac import get_mac_address
import os
def get_client():
server_address = os.getenv('JELLYFIN_ADDRESS')
username = os.getenv('JELLYFIN_USER')
password = os.getenv('JELLYFIN_PASSWORD')
host_mac = get_mac_address(hostname="localhost")
client = JellyfinClient()
client.config.app('jellydisc', '0.0.1', os.uname().nodename, host_mac)
client.config.data['auth.ssl'] = True
client.auth.connect_to_address(server_address)
client.auth.login(server_address, username, password)
return client
def get_active_media(client):
server_address = os.getenv('JELLYFIN_ADDRESS')
sessions = client.jellyfin.get_sessions()
for session in sessions:
# Check if there is a NowPlayingItem
media = session.get('NowPlayingItem')
if not media:
continue
# Skip non-audio media
media_type = media.get('Type')
if media_type != 'Audio':
continue
media_id = media.get('Id')
image = f"{server_address}/Items/{media_id}/Images/Primary?maxWidth=300&maxHeight=300"
return {
'id': media_id,
'artist': media.get('AlbumArtist', 'Unknown Artist'),
'title': media.get('Name', 'Unknown Title'),
'image': image,
}
return None

95
jellyfin/api_client.py Normal file
View File

@@ -0,0 +1,95 @@
from settings import settings
from jellyfin_apiclient_python import JellyfinClient
from getmac import get_mac_address
from jellyfin.models import JellyfinMediaItem, JellyfinMediaType
import logging
import os
class JellyfinApiClient:
def __init__(self):
machine_name = os.uname().nodename
unique_id = get_mac_address(hostname="localhost")
self.logger = logging.getLogger('JellyfinApiClient')
self.logger.info("Connecting to Jellyfin server...")
self.client = JellyfinClient()
self.client.config.app('jellydisc', '0.0.1', machine_name, unique_id)
self.client.config.data['auth.ssl'] = True
self.client.auth.connect_to_address(settings.jellyfin_server_url)
self.client.auth.login(
settings.jellyfin_server_url,
settings.jellyfin_username,
settings.jellyfin_password
)
self.logger.info("Connected to Jellyfin server.")
def get_current_playback(self) -> JellyfinMediaItem | None:
self.logger.info("Fetching current playback information...")
sessions = self.client.jellyfin.get_sessions()
if not sessions:
self.logger.info("No active playback found.")
return None
for session in sessions:
if session.get('NowPlayingItem'):
self.logger.info("Current playback information fetched.")
return self.to_model(session['NowPlayingItem'])
self.logger.info("No active playback found.")
return None
def get_image_url(self, media_id: str) -> str:
server_address = settings.jellyfin_server_url.rstrip('/')
return f"{server_address}/Items/{media_id}/Images/Primary?maxWidth=300&maxHeight=300"
def to_model(self, item: dict) -> JellyfinMediaItem:
media_type = item.get('Type')
if media_type == 'Audio':
return self.to_music_model(item)
elif media_type == 'Episode':
return self.to_episode_model(item)
elif media_type == 'Movie':
return self.to_movie_model(item)
raise ValueError(f"Unsupported media type: {media_type}")
def to_music_model(self, item: dict) -> JellyfinMediaItem:
media_id = item.get('Id')
return JellyfinMediaItem(
name=item.get('Name'),
type=JellyfinMediaType.AUDIO,
image_url=self.get_image_url(media_id),
metadata={
'artist': item.get('AlbumArtist'),
}
)
def to_movie_model(self, item: dict) -> JellyfinMediaItem:
media_id = item.get('Id')
return JellyfinMediaItem(
name=item.get('Name'),
type=JellyfinMediaType.MOVIE,
image_url=self.get_image_url(media_id),
metadata={}
)
def to_episode_model(self, item: dict) -> JellyfinMediaItem:
media_id = item.get('Id')
return JellyfinMediaItem(
name=item.get('Name'),
type=JellyfinMediaType.EPISODE,
image_url=self.get_image_url(media_id),
metadata={
'series': item.get('SeriesName'),
'season': item.get('ParentIndexNumber'),
'episode': item.get('IndexNumber'),
}
)

13
jellyfin/models.py Normal file
View File

@@ -0,0 +1,13 @@
from pydantic import BaseModel
from enum import Enum
class JellyfinMediaType(str, Enum):
AUDIO = 'Audio'
MOVIE = 'Movie'
EPISODE = 'Episode'
class JellyfinMediaItem(BaseModel):
name: str
type: JellyfinMediaType
image_url: str
metadata: dict

52
main.py
View File

@@ -1,46 +1,12 @@
from dotenv import load_dotenv
from jellyfin import get_client, get_active_media
from discord import get_rpc
from pypresence.types import ActivityType
import time
from discord.rpc import DiscordRPC
from jellyfin.api_client import JellyfinApiClient
import logging
load_dotenv()
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
rpc = get_rpc()
client = get_client()
discordRPC = DiscordRPC()
jellyfinApiClient = JellyfinApiClient()
def generate_media_id(media):
return f"{media['artist']}-{media['title']}"
def main_loop(last_media_id):
media = get_active_media(client)
if media is None:
print("No active media found.")
rpc.clear()
return None
media_id = generate_media_id(media)
if media_id != last_media_id:
print(f"Updating Discord RPC: Listening to {media['title']} by {media['artist']}")
rpc.update(
activity_type=ActivityType.LISTENING,
state=f"by {media['artist']}",
details=f"Listening to {media['title']}",
large_image=media['image'],
large_text=media['title'],
)
return media_id
else:
print("No change in media. Skipping update.")
return last_media_id
if __name__ == "__main__":
last_media_id = None
print("Starting Jellyfin Discord Rich Presence...")
while True:
last_media_id = main_loop(last_media_id)
time.sleep(15)
print(jellyfinApiClient.get_current_playback())