Skip to main content

A Possible Official Python SDK for Jellyfin.

Project description

Jellyfin SDK for Python


Logo Banner

A High-level Wrapper for OpenAPI Generated Bindings for Jellyfin API.

Warning: API changes will occur only in the final classes, bindings and legacy don't change

The main goal of this project is to be a wrapper for the API but with high level of abstraction using the power of OpenAPI Specs bindings and good patterns such as Inversion of Control, Method Chaining, JSONPath, and more.

Main unique features:

  • Enables targeting a specific Jellyfin server version to ensure compatibility and prevent breaking changes.
  • Supports accessing multiple servers, each potentially running different Jellyfin versions.
  • Allows reducing the level of abstraction to access advanced or unavailable options through lower-level interfaces.
  • Works like AWS CDK Constructs Level, more abstraction, more simple.
image

How modules work together

There is a thin layer that builds the high-level abstraction (green box/jellyfin) consuming only the bindings that already contain dataclasses and api built using the OpenAPI Generator (blue box/generated), which in turn also allows use only if the user requests jellyfin_apiclient_python (purple box/legacy) to allow for refactoring and incremental development. Both legacy and generated have classes that allow low-level access, being practically just an envelope method for requests that must communicate with the actual jellyfin API (lilac box).

This project is mainly inspired by good python library like these:

Install

pip install jellyfin-sdk

or

uv add jellyfin-sdk

Usage

Drop-in replacement for jellyfin-apiclient-python

This library includes the old legacy client (which is almost unmaintained) to help with migration:

pip uninstall jellyfin-apiclient-python
pip install jellyfin-sdk[legacy]
# change from
from jellyfin_apiclient_python import JellyfinClient
from jellyfin_apiclient_python.api import API

# to this
from jellyfin.legacy import JellyfinClient
from jellyfin.legacy.api import API

List of current problems in legacy project already fixed here:

import jellyfin

jellyfin.api(os.getenv("URL"), os.getenv("API_KEY")).users.libraries

ValueError: User ID is not set. Use the 'of(user_id)' method to set the user context.
import jellyfin

jellyfin.api(os.getenv("URL"), os.getenv("API_KEY")).system.info
import jellyfin
from jellyfin.items import ItemCollection, Item
from jellyfin.generated import PlaylistsApi, BaseItemKind

api = jellyfin.api(os.getenv("URL"), os.getenv("API_KEY"))
api.user = 'justin'
playlist = api.items.search.add('include_item_types', [BaseItemKind.PLAYLIST]).recursive().all.first
playlist

<Item
  name="Editor's Choice",
  original_title=None,
  server_id='REDACTED',
  id=UUID('ec0770d5-bde0-794c-bc1e-c5e8ff0588ca'),
  etag=None,
  source_type=None,
  playlist_item_id=None,
  date_created=None,
  date_last_media_added=None,
  extra_type=None
  ...
]>

# no high level abstraction, let's use bindings for playlist
ItemCollection(Item(
    PlaylistsApi().get_playlist_items(playlist.id, api.user.id)
))

Easy to debug

Debug is a missing peace in every implementation I look at, even the jellyfin team have a hard time to see what happen. To help to remove the SDK of equation and for code more complex we have a feature to allow show a curl command of every request in the server.

import jellyfin
from jellyfin.items import ItemCollection
from jellyfin.generated import PlaylistsApi, BaseItemKind

api = jellyfin.api(os.getenv("URL"), os.getenv("API_KEY"))
api.debug = True

playlist = api.items.search.add('include_item_types', [BaseItemKind.PLAYLIST]).recursive().all.first

The api.debug enable the client to print all requests in curl in prompt. Never use this in production!

curl '-X' GET \n
    '-H' 'Accept: application/json' \n
    '-H' 'User-Agent: OpenAPI-Generator/10.10/python' \n
    '-H' 'Authorization: MediaBrowser Token="API_KEY"' \n
    URL/Items?recursive=true&includeItemTypes=Playlist

Lots of problems happens in the api server or behavior not in documentation, in this way you have data to open a issue in jellyfin.

Login

import os

os.environ["URL"] = "https://jellyfin.example.com"
os.environ["API_KEY"] = "MY_TOKEN"

Using SDK

import jellyfin

api = jellyfin.api(
    os.getenv("URL"), 
    os.getenv("API_KEY")
)

print(
    api.system.info.version,
    api.system.info.server_name
)

Direct with Generated Bindings

from jellyfin.generated.api_10_10 import Configuration, ApiClient, SystemApi

configuration = Configuration(
    host = os.getenv("URL"),
    api_key={'CustomAuthentication': f'Token="{os.getenv("API_KEY")}"'}, 
    api_key_prefix={'CustomAuthentication': 'MediaBrowser'}
)

client = ApiClient(configuration)
system = SystemApi(client)

print(
    system.get_system_info().version, 
    system.get_system_info().server_name
)

It's possible use the proxy direct, this always will point to current stable

# specific
from jellyfin.generated.api_10_10 import Configuration, ApiClient, SystemApi

# current stable
from jellyfin.generated import Configuration, ApiClient, SystemApi

# current unstable
from jellyfin.generated.api_10_11 import BackupApi

# inject from high level abstraction
import jellyfin

api = jellyfin.api(
    os.getenv("URL"), 
    os.getenv("API_KEY")
)

# module loader
api.generated

<module 'jellyfin.generated.api_10_10' from '/code/jellyfin-sdk-python/src/jellyfin/generated/api_10_10/__init__.py'>

# injected from bindings
client = api.generated.ApiClient

<class 'jellyfin.generated.api_10_10.api_client.ApiClient'>

Legacy

from jellyfin.legacy import JellyfinClient
client = JellyfinClient()
client.authenticate(
    {"Servers": [{
        "AccessToken": os.getenv("API_KEY"), 
        "address": os.getenv("URL")
    }]}, 
    discover=False
)
system_info = client.jellyfin.get_system_info()

print(
    system_info.get("Version"), 
    system_info.get("ServerName")
)

Jellyfin Server API Version

This is important because when a new API version is released, breaking changes can affect the entire project. To avoid this, you can set an API target version, similar to how it's done in Android development:

from jellyfin.api import Version
import jellyfin

# By default will use the lastest stable
jellyfin.api(
    os.getenv("URL"), 
    os.getenv("API_KEY")
)

# now let's test the new API (version 10.11) for breaking changes in same endpoint
jellyfin.api(
    os.getenv("URL"), 
    os.getenv("API_KEY"), 
    Version.V10_11
)

# but str is allow to: 10.10, 10.11 and etc
jellyfin.api(
    os.getenv("URL"), 
    os.getenv("API_KEY"), 
    '10.11'
)

# let's test a wrong version
jellyfin.api(
    os.getenv("URL"), 
    os.getenv("API_KEY"), 
    '99'
)

ValueError: Unsupported version: 99. Supported versions are: ['10.10', '10.11']

List all libraries of an user

When using API_KEY some endpoints need the user_id (don't me ask why!), almost all issues with jellyfin is around this. To help to identify this not-so-much-edge-cases we raise a exception to help with that:

api = jellyfin.api(
    os.getenv("URL"), 
    os.getenv("API_KEY")
)

api.users.libraries

ValueError: User ID is not set. Use the 'of(user_id)' method to set the user context.


api.users.of('f674245b84ea4d3ea9cf11').libraries

# works also with the user name
api.users.of('niels').libraries

# when using 'of' the attribute of dataclasses
# user and user_view can be accessed directly
api.users.of('niels').id

List all items

Item can be any object in the server, in fact that's how works, one huge table recursive linked.

api = jellyfin.api(
    os.getenv("URL"), 
    os.getenv("API_KEY")
)

api.items.all

# Same command but without shorthand
search = api.items.search
search

<ItemSearch (no filters set)>

search.paginate(1000)

<ItemSearch filters={
  start_index=0,
  limit=1000,
  enable_total_record_count=True
}>

search.recursive()

<ItemSearch filters={
  start_index=0,
  limit=1000,
  enable_total_record_count=True,
  recursive=True
}>

All filter options is available here.

The pagination uses a Iterator:

api = jellyfin.api(
    os.getenv("URL"), 
    os.getenv("API_KEY")
)

for item in api.items.search.paginate(100).recursive().all:
    print(item.name)

Let's get the User ID by name or ID

api = jellyfin.api(
    os.getenv("URL"), 
    os.getenv("API_KEY")
)

uuid = api.user.by_name('joshua').id

api.user.by_id(uuid).name

Get item by ID

api = jellyfin.api(
    os.getenv("URL"), 
    os.getenv("API_KEY")
)

api.items.by_id('ID')

This is just a shorthand for:

api.items.search.add('ids', ['ID']).all.first

Upload a Primary Image for a Item

import jellyfin
from jellyfin.generated import ImageType

api = jellyfin.api(
    os.getenv("URL"), 
    os.getenv("API_KEY")
)

api.image.upload_from_url(
    'ID', 
    ImageType.PRIMARY,
    'https://upload.wikimedia.org/wikipedia/commons/6/6a/Jellyfin_v10.6.0_movie_detail%2C_web_client.png'
)

Add tags in a collection

Edit item require the user_id, but we make this easy:

api = jellyfin.api(
    os.getenv("URL"), 
    os.getenv("API_KEY")
)

item = api.items.edit('ID', 'joshua')
item.tags = ['branding']
item.save()

item = api.items.edit('ID', 'niels')
item.tags = ['rules']
item.save()

If you want to set a global user:

api = jellyfin.api(
    os.getenv("URL"), 
    os.getenv("API_KEY")
)
api.user = 'niels'

item = api.items.edit('ID')
item.tags = ['branding']
item.save()

item = api.items.edit('OTHER_ID')
item.tags = ['rules']
item.save()

The user on edit method has precedence over global

Register as a client

If necessary register a client to identify ourselves to the server

api = jellyfin.api(
    os.getenv("URL"), 
    os.getenv("API_KEY")
)
api.register_client()

<Api
 url='https://jellyfin.example.com',
 version='10.10',
 auth='Token="***",
       Client="4b8caf670ca1",
       Device="Linux Ubuntu 24.04.3 LTS (noble)",
       DeviceId="a7-17-23-d8-b9-b8",
       Version="24.04.3"'
>

If you need customize the client information:

api.register_client('test')

<Api
 url='https://jellyfin.example.com',
 version='10.10',
 auth='Token="***",
       Client="test",
       Device="Linux Ubuntu 24.04.3 LTS (noble)",
       DeviceId="a7-17-23-d8-b9-b8",
       Version="24.04.3"'
>

For more detail look the docs.

Documentation

Supported Jellyfin Versions

SDK Version Jellyfin API Target
0.1.x 10.10.x-10.11.x

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jellyfin_sdk-0.2.0.tar.gz (789.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jellyfin_sdk-0.2.0-py3-none-any.whl (1.7 MB view details)

Uploaded Python 3

File details

Details for the file jellyfin_sdk-0.2.0.tar.gz.

File metadata

  • Download URL: jellyfin_sdk-0.2.0.tar.gz
  • Upload date:
  • Size: 789.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.8.15

File hashes

Hashes for jellyfin_sdk-0.2.0.tar.gz
Algorithm Hash digest
SHA256 ce0e882c11ced209d69414f9893eaca4e733fe0200eca5e0f1522d8d10a52a99
MD5 f63c4dda5ed15050e8f4a5147d7cc029
BLAKE2b-256 85786c9ec80165cab0375f076b096757570bb27028159c2497a4b6c6bfeb560c

See more details on using hashes here.

File details

Details for the file jellyfin_sdk-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for jellyfin_sdk-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 471424587a2ae1fa6ae44003bf61445efebf6cca70d0c5baa180bbb99d2217bb
MD5 7a78aa941818a38f4a4136e11281d981
BLAKE2b-256 fc6abbb292deb604b621fc7e4d4f2ac6006a01f2c6a955817cd59b32ceca3c7e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page