Introduction
Jellything is a free server software to serve media like movies, videos and music through a website.
Source Code
Getting Started
Installation
From the AUR
This is the recommended option. It will also install a systemd service and system user for that service.
git clone https://aur.archlinux.org/jellything-git.git
cd jellything-git
makepkg -si
From source
Requirements:
- rustup
- esbuild
- nasm
- meson
- ninja
- cmake
- dav1d
- ffmpeg (only if you use transcoding)
Jellything was only tested for x86_64-unknown-linux-gnu
and
aarch64-unknown-linux-gnu
targetss. Others probably work too.
git clone --recursive https://codeberg.org/metamuffin/jellything.git
cd jellything
cargo build --release
# Global installation
cp target/release/{jellything,jellytool} /usr/local/bin
# User installation
cp target/release/{jellything,jellytool} ~/.local/bin
Setup
1. Writing configurations
First write your configuration files whereever you want. The AUR package uses
/etc/jellything.yaml
.
# This hostname must be identical to how other instances reach you.
hostname: example.org
brand: "Jellything"
slogan: ""
admin_username: admin
# All of these paths can be customized. See "Paths"
media_path: "/srv/media"
asset_path: "/var/lib/jellything/assets"
database_path: "/var/lib/jellything/db"
temp_path: "/tmp/jellything"
cache_path: "/var/cache/jellything"
secrets_path: "/etc/jellysecrets.yaml" # points to the file below
# jellysecrets.yaml; filled with placeholders
admin_password: "xxxxxx"
# Both these keys should be initialized randomly.
# Use `head -c 32 /dev/random | base64`
cookie_key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx="
session_key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx="
# Credentials for remote instances. Keep this empty if you are just starting.
federation:
"example.org": { username: "examplefed", password: "xxxxxxx" }
api:
fanart_tv: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
tmdb: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
omdb: xxxxxxxx
# This is the Trakt Application `client_id`
trakt: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
tvdb: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
2. Creating required directories
Next create directories in place of cache_path
, temp_path
, media_path
. if
jellything is not permitted to do so itself. Also obtain the default assets from
the jellything-assets repo.
mkdir -p $cache_path $temp_path $media_path
git clone https://codeberg.org/metamuffin/jellything-assets.git $asset_path
3. Preparing your first import
In library_path, create files like below. This file provides an entry point to your library. Its exact meaning is described in The Import Guide.
Next place your favorite movies in media_path and use the import helper to quickly generate import instructions for it. This requires at least Trakt and TMDB keys to work.
# Jellytool will show an interactive wizard to select the correct metadata source.
# The files will be renamed to include the Trakt id in the name.
jellytool add big-buck-bunny.mkv
jellytool add agent-327-operation-barbershop.mkv
jellytool add spring.mkv
Launch your Platform
tip
If you used the AUR package, this should be as simple as
systemctl start jellything
.
For other installations, run the jellything
binary with the configuration
file's path as its first argument or set it in JELLYTHING_CONFIG
. Jellything
will start and serve your library at
https://127.0.0.1:8080/
jellything /etc/jellything.yaml
# Or
JELLYTHING_CONFIG=/etc/jellything.yaml jellything
It is also advised to use jellything with a reverse proxy. Configure the network
interface with the environment variables BIND_ADDR
and PORT
. Logging to
stderr can be configured with the LOG
variable and env_logger syntax.
Initial import
Next log in with the admin accound and visit /admin/dashboard
to run an
initial import.
Migrating the Database
caution
The current architecture for saving to the database will be completely rewritten soon.
With some updates the database serialization for changes. This requires a migration process. You need to follow this procedure either with every update.
1. Export the database
Use the jellytool
of the previous version to export the database to JSON.
mv /path/to/db /path/to/db.old # Rename the DB to avoid conflict later
jellytool.old migrate export /path/to/db.old export /tmp/jdb
2. Run migrations on the JSON dump
This is not implemented yet. It usually just works without anyway.
3. Import the database
Now import your library back to where it usually lives using the jellytool
of
the current version.
jellytool migrate export /path/to/db import /tmp/jdb
4. Delete old Databases
Delete the old database and the JSON dump, they are not required anymore.
caution
Confirm that everything still works and no data is lost before!
rm /path/to/db.old
rm -r /tmp/jdb
Paths
- media_path: All your media (videos, movies, etc.) and import flags live here. Jellything will only require read access to this directory.
- asset_path: Static assets for the page. This includes fallback images,
fonts and the front page. Used read-only.
error.avif
: Used when images cant be displayed because of an error. This image is not required to be AVIF despite the name extension.fallback-<kind>.avif
: Fallback image when there is no asset available. AVIF also not required.<kind>
is a node kind or "Person".front.htm
: Contents of the front page. The typical jellything page scaffold is placed around it.logo.svg
: Logo of the platform to replace the text in the top left of every page.fonts/material-icons.woff2
Material Icons Font. Get this from online.
- database_path: The database stores all imported nodes, user accounts and such. Write access required.
- temp_path: Where to place short-lived files. Write access required. Can be in volatile memory (e.g. /tmp).
- cache_path: In here jellything will place dozens of transcoded imagery, saved api responses and media metadata. This folder will likely be the biggest. Write access required.
- secrets_path: Path to the
secrets
YAML file. Read access required. Should ideally not be readable by anything else than jellything.
Jellything's Import System
In normal operation, jellything serves all metadata from only the database.
Jellything Rust API
For making your own applications that implement client functionality, use the
jellyclient
crate. The jellycommon
crate exposes commonly used structs like
those used in the library and for jhls.
Generated Documentation
- jellything
- jellycommon
- jellyremuxer
- jellytranscode
- jellyimport
- jellymatroska
- jellybase
- jellystream
- jellytool
Jellything HTTP API
Most endpoints require the Accept
header to be present and set to
application/json
and image/avif
respectively. Any endpoint returning JSON,
will report errors with an object containing error string in the error
key.
Routes marked with *
require authentification.
The jellyclient
crate already implements most API functionality. The
jellycommon
crate provides useful structs for deserializing data (also
reexported in jellyclient).
# Cargo.toml
[depedencies]
jellyclient = { git = "https://codeberg.org/metamuffin/jellything.git" }
General
GET /api/version
Returns API version number.
POST /api/create_session
Request body contains JSON with keys username
, password
, expire
(in
seconds) and drop_permissions
(a list of permissions, that this session cannot
use). The Response contains the session cookie as a string in JSON.
GET* /n/<id>?<parents>&<children>&<filter..>
Request a library node with userdata attached. If parents
or children
flag
is set, all parents/children will be returned too, otherwise those lists are
empty. Returns ApiNodeResponse
.
GET* /n/<id>/userdata
Returns only NodeUserData
for that node.
GET* /search?<query>&<page>
Returns ApiSearchResponse
.
GET* /items?<filter..>&<page>
Returns ApiItemsResponse
.
GET* /home
Returns ApiHomeResponse
.
Assets
Endpoints like .../poster
redirect to the asset that you need and
automatically choose fallbacks. Alternatively you can directly request assets
with /asset/<token>
, but there you need to implement the fallback behaviour
yourself. Returned images are coded with AVIF. The width
parameter is the
width of the resolution you want to image to be.
[!WARNING] The actual returned resolution must not be exactly what you requested. Currently it is rounded up to the next power of two.
GET* /asset/<token>?<width>
This is the final asset endpoint where images are returned from. Other endpoints
redirect here. The token
part is an opaque string that you obtain from
somewhere else like a redirect or from within the Node
struct.
GET* /n/<id>/poster?<width>
GET* /n/<id>/backdrop?<width>
GET* /n/<id>/thumbnail?<t>&<width>
Returns a single frame from some track video of the media at a given time t
.
GET* /n/<id>/person/<index>/asset?<group>&<width>
Returns headshot of a person from that node.
Stream
GET* /n/<id>/stream?<params..>
Responds with the stream directly or a redirect to the actual source in case of federation.
?whep&<track...>&<seek>
- WHEP Endpoint for streaming that set of tracks. The format used is decided by the server.
?whepcontrol&<token>
- WebSocket endpoint for controlling WHEP playback. TODO schema
?remux&<track...>&<container>
?hlssupermultivariant&<container>
- Returns m3u8/HLS playlist of all known multi-variant playlists, one for each segment. The plylist is updated for live media.
?hlsmultivariant&<segment>&<container>
- Returns m3u8/HLS playlist of all track formats' variant playlists.
?hlsvariant&<segment>&<track>&<container>&<format>
- Returns m3u8/HLS playlist of all known fragments of this track format. The playlist is updated for live media.
?info
- Returns JSON
StreamInfo
.
- Returns JSON
?fragmentindex&<segment>&<track>
- Returns time ranges for every fragment of this track.
?fragment&<segment>&<track>&<index>&<container>&<format>
export type FragmentIndex = TimeRange[];
export interface TimeRange {
start: number;
end: number;
}
export interface SubtitleCue extends TimeRange {
content: string;
}
export interface StreamInfo {
name?: string;
segments: SegmentInfo[];
}
export interface SegmentInfo {
name?: string;
duration: number;
tracks: TrackInfo[];
}
export type TrackKind = "video" | "audio" | "subtitles";
export interface TrackInfo {
name?: string;
language?: string;
kind: TrackKind;
formats: FormatInfo[];
}
export type StreamContainer = "webm" | "matroska" | "mpeg4" | "jvtt" | "webvtt";
export interface FormatInfo {
codec: string;
bitrate: number;
remux: boolean;
containers: StreamContainer[];
width?: number;
height?: number;
channels?: number;
samplerate?: number;
bit_depth?: number;
}