streamystats

repository·main·Indexed 20 days ago

https://github.com/fredrikburmester/streamystats

A statistics and analytics platform for Jellyfin providing data visualization, user watch history, and AI-powered features such as semantic search and personalized recommendations. It supports multiple servers and users, utilizing OpenAI-compatible APIs and vector similarity for AI integration. The service can be deployed via Docker or an All-in-One (AIO) container including PostgreSQL with VectorChord.

Tokens
28.9K
Snippets
83
Records
109
Agent score
72%

What's inside streamystats

  1. Overview of @streamystats/database

    main
    The @streamystats/database package manages the database schema, migrations, and connection logic for the Streamystats ecosystem. It uses Drizzle ORM for schema definitions and Drizzle Kit for managing SQL migration files. This package provides shared connection logic used by all other services in the project.
  2. Streamystats Overview and Features

    main

    Streamystats is a statistics service for Jellyfin that provides analytics and data visualization. It relies solely on the Jellyfin API for statistics (the Playback reporting plugin is no longer required).

    Core Features:

    • Dashboards: Overview statistics, live sessions, and recommendations.
    • User Analytics: Individual watch history, statistics, and watch time graphs with advanced filtering.
    • Library & Client Stats: Detailed insights into library composition and client usage.
    • Multi-support: Supports multiple servers and multiple users.
    • AI Integration: AI chat for library interaction and embedding-supported watch recommendations.
  3. How AI Chat and Recommendations work

    main

    Streamystats provides AI-driven features using embeddings and vector similarity:

    Embeddings

    Library items are embedded using OpenAI-compatible APIs (if enabled). These embeddings are stored in vectorchord, which supports any dimension and automatically creates optimized vector indexes for similarity search.

    AI Chat

    The interactive chat interface supports multiple providers via any OpenAI-compatible API. It utilizes function calling with 13 specialized tools, including:

    • Personalized recommendations based on watch history.
    • Semantic library search using embeddings.
    • Watch statistics and most-watched content.
    • Genre filtering and top-rated items.
    • Recently added content discovery.

    AI Recommendations

    Recommendations are generated using cosine distance (vector similarity) to find content similar to your watch history. The system analyzes viewing patterns and provides explanations detailing which specific watched items triggered the suggestion.

  4. Understand the Streamystats AIO Architecture and Startup Order

    main

    The AIO container uses supervisord to manage three internal services. To ensure stability, the services follow a strict startup sequence:

    1. PostgreSQL (VectorChord): Starts first.
    2. Migrations: Run automatically once PostgreSQL is ready.
    3. Job Server: Starts once migrations are complete.
    4. Next.js: Starts last, once the Job Server is healthy. The application is accessible on Port 3000.

    Data Persistence: All database data is stored in /var/lib/postgresql/data. You must mount a volume to this path to prevent data loss when the container is removed.

  5. How database migrations work in Streamystats

    main

    Streamystats uses Drizzle ORM for database migrations. In a standard Docker deployment, migrations follow a strict startup order to ensure data integrity:

    1. PostgreSQL starts and passes health checks.
    2. Job-server starts, waits for PostgreSQL, and then automatically runs all pending migrations from the ./drizzle folder using a compiled binary (migrate-bin).
    3. Next.js app starts only after the job-server becomes healthy (which happens after migrations complete).

    Migrations are idempotent, meaning they can be run multiple times safely without causing errors if they have already been applied.

  6. Identify a Jellyfin Server in API Requests

    main

    When querying endpoints that require a specific server context, use one of the following query parameters to identify the target Jellyfin server:

    ParameterDescriptionExample
    serverIdInternal Streamystats server ID?serverId=1
    serverNameServer name (exact match, case-insensitive)?serverName=MyServer
    serverUrlServer URL (partial match)?serverUrl=jellyfin.example.com
    jellyfinServerIdJellyfin's unique server ID (from /System/Info)?jellyfinServerId=abc123...
  7. How migrations work in production

    main

    In production environments, migrations are automatically handled by the job-server container during its startup sequence:

    1. The job-server image includes a compiled migration runner (migrate-bin) derived from packages/database/src/migrate-entrypoint.ts.
    2. SQL migration files located in packages/database/drizzle/ are bundled into the container.
    3. The Next.js application is configured to start only after the job-server is healthy, ensuring that all database migrations have successfully completed before the app attempts to connect.
  8. Run Streamystats using systemd

    main

    For a production-ready Dockerless setup, use systemd to manage the three components: the database migration task, the job server, and the Next.js application.

    1. Create a dedicated streamystats system user.
    2. Set permissions for /opt/streamystats/{src,etc}.
    3. Create and enable the following services:
      • streamystats-db-migration.service: Runs bun run db:migrate (Type: oneshot).
      • streamystats-job-server.service: Runs the job server (Type: simple).
      • streamystats-nextjs-app.service: Builds and starts the Next.js app (Type: simple).

    Note: The job server service requires jellyfin.service to be present, and the Next.js app requires the job server to be running.

    # 1. Setup user and permissions
    useradd --system --home /opt/streamystats --create-home --shell /usr/sbin/nologin --user-group streamystats
    chown -R streamystats:streamystats /opt/streamystats/{src,etc}
    
    # 2. (After creating the .service files in /lib/systemd/system/)
    systemctl daemon-reload
    systemctl enable --now streamystats-db-migration.service
    systemctl enable --now streamystats-job-server.service
    systemctl enable --now streamystats-nextjs-app.service
  9. Quick Start: Deploy Streamystats AIO via Docker Run

    main

    If you prefer not to use Docker Compose, you can run the AIO container directly using docker run. Ensure you map port 3000 and mount a volume for data persistence.

    docker run -d \
      --name streamystats \
      -p 3000:3000 \
      -v streamystats_data:/var/lib/postgresql/data \
      -e SESSION_SECRET="$(openssl rand -hex 32)" \
      -e POSTGRES_PASSWORD="your-secure-password" \
      ghcr.io/fredrikburmester/streamystats-aio:latest
    docker run -d \
      --name streamystats \
      -p 3000:3000 \
      -v streamystats_data:/var/lib/postgresql/data \
      -e SESSION_SECRET="$(openssl rand -hex 32)" \
      -e POSTGRES_PASSWORD="your-secure-password" \
      ghcr.io/fredrikburmester/streamystats-aio:latest
  10. Backup and Restore Streamystats database

    main

    Standard SQL Backup

    Use pg_dump to create a portable SQL backup:

    docker exec streamystats pg_dump -U postgres \
      --clean --if-exists --no-owner \
      streamystats > backup.sql

    Flags:

    • --clean: Adds DROP statements before CREATE (enables restore to existing DB).
    • --if-exists: Adds IF EXISTS to DROP (prevents errors if objects don't exist).
    • --no-owner: Omits ownership commands (makes the backup portable).

    Restore:

    cat backup.sql | docker exec -i streamystats psql -U postgres streamystats

    For more efficient storage, use the custom format (-Fc):

    docker exec streamystats pg_dump -U postgres \
      --clean --if-exists --no-owner -Fc \
      streamystats > backup.dump

    Restore Compressed:

    docker exec -i streamystats pg_restore -U postgres \
      --clean --if-exists -d streamystats < backup.dump
    # Standard Backup
    docker exec streamystats pg_dump -U postgres \
      --clean --if-exists --no-owner \
      streamystats > backup.sql
    
    # Restore
    cat backup.sql | docker exec -i streamystats psql -U postgres streamystats
  11. How to make schema changes

    main

    To update the database structure, follow this workflow:

    1. Modify the schema: Edit the table definitions in packages/database/src/schema.ts.
    2. Generate migration: Run bun run db:generate to create new SQL files.
    3. Review: Inspect the generated .sql files in the drizzle/ directory to ensure they match your intentions.
    4. Test locally: Apply the changes to your local database using bun run db:migrate.
    5. Commit: Add both the src/schema.ts changes and the new files in drizzle/ to your version control.
    6. Deploy: Once deployed, the job-server will automatically execute the new migrations on startup.
  12. Authenticate with the Streamystats External API

    main

    All API endpoints require authentication. External clients (mobile apps, scripts, etc.) should use the MediaBrowser token format in the Authorization header.

    MediaBrowser Token Format

    Use the Authorization header with the following structure:

    Authorization: MediaBrowser Token="<access-token>"

    You can also include optional metadata for better tracking:

    Authorization: MediaBrowser Client="MyApp", Device="iPhone", DeviceId="abc123", Version="1.0.0", Token="<access-token>"

    Parameter Reference

    ParameterRequiredDescription
    TokenYesJellyfin access token from AuthenticationResult.AccessToken
    ClientNoClient application name
    DeviceNoDevice name
    DeviceIdNoUnique device identifier
    VersionNoClient version

    Obtaining a Token

    To get a token, authenticate against your Jellyfin server's /Users/AuthenticateByName endpoint:

    curl -X POST "https://your-jellyfin-server/Users/AuthenticateByName" \
      -H "Content-Type: application/json" \
      -d '{"Username": "your-username", "Pw": "your-password"}'

    The response will contain an AccessToken. Use this value in the Token parameter of your Streamystats requests.