prefetcharr

repository·latest·Indexed 42 days ago

https://github.com/p-hueber/prefetcharr

A tool that automates the prefetching of upcoming TV show episodes by monitoring active playback sessions on media servers such as Jellyfin, Emby, Plex, or Tautulli and triggering searches in Sonarr. Version 1.6.2 supports TOML configuration, Docker Compose deployment, and custom media server client traits.

Tokens
7.1K
Snippets
30
Records
39
Agent score
55%

What's inside prefetcharr

  1. How prefetcharr works

    latest

    Core Logic

    prefetcharr periodically polls your media server (Jellyfin, Emby, or Plex) for active playback sessions of TV shows. It then checks if a configured number of successive episodes (prefetch_num) is available. If episodes are missing, it instructs Sonarr to search for them.

    Depending on your configuration, it will either search for individual missing episodes or attempt to fetch entire seasons (if request_seasons is true). If no more seasons are available, it defaults to monitoring the series for new seasons.

  2. Obtain API keys for prefetcharr

    latest

    You need two API keys for prefetcharr to function:

    Sonarr API Key

    Navigate to Settings -> General -> Security in Sonarr and copy the API key.

    Media Server API Key

    Depending on your server type:

    • Jellyfin: Administration -> Dashboard -> Advanced -> Api Keys.
    • Emby: Settings (gear icon) -> Advanced -> Api Keys.
    • Plex: Extract the server token from a configuration file.
    • Tautulli (for Plex): Settings -> Web Interface -> API. Ensure Enable API is checked.
  3. Install prefetcharr using Docker Compose

    latest

    You can run prefetcharr as a Docker container. The configuration can be provided via the PREFETCHARR_CONFIG environment variable containing a TOML string, or by mounting a configuration file to /config using a volume.

    services:
      prefetcharr:
        image: phueber/prefetcharr:latest
        container_name: prefetcharr
        user: 1000:1000
        environment:
          - |
            PREFETCHARR_CONFIG=
            interval = 900
            log_dir = "/log"
            log_level = "Debug"
            prefetch_num = 2
            request_seasons = true
            append_to_queue = false
            connection_retries = 6
    
            [media_server]
            type = "Jellyfin"
            url = "http://example.com/jellyfin"
            api_key = "<YOUR KEY HERE>"
    
            [sonarr]
            url = "http://example.com/sonarr"
            api_key = "<YOUR KEY HERE>"
    
        volumes:
          - /path/to/log/dir:/log
          # - /path/to/config.toml:/config
  4. Configure prefetcharr settings

    latest

    Configuration is written in TOML format.

    Global Settings

    • interval: Polling interval in seconds.
    • log_dir: Logging directory.
    • log_level: Log verbosity (Trace, Debug, Info, Warn, or Error).
    • prefetch_num: Number of episodes to make available in advance.
    • request_seasons: If true, always request full seasons to prefer season packs.
    • append_to_queue: (Experimental) Append upcoming episodes to the player's active queue. Note: Not supported by all clients and incompatible with Tautulli.
    • connection_retries: Number of retries for the initial connection probing.

    [media_server] Section

    • type: The media server type (Jellyfin, Emby, Plex, or Tautulli).
    • url: The base URL of the media server.
    • api_key: The API key or server token.
    • users (Optional): List of specific user IDs or names to monitor.
    • libraries (Optional): List of specific libraries to monitor.

    [sonarr] Section

    • url: The Sonarr base URL.
    • api_key: The Sonarr API key.
    • exclude_tag (Optional): A tag used to exclude specific series from prefetching.
  5. Use NowPlaying and PrefetchKey for deduplication

    latest

    When working with playback data, the project distinguishes between a live session (NowPlaying) and a unique identifier used for prefetching logic (PrefetchKey).

    • NowPlaying: Contains full session details, including session_id and user. This is used to identify exactly who is watching what right now.
    • PrefetchKey: A derived version of NowPlaying that excludes the session_id. This allows the prefetcher to recognize that the same episode is being watched across different sessions or after a restart, preventing redundant prefetch requests for the same content.

    You can convert a NowPlaying instance into a PrefetchKey using PrefetchKey::from(&now_playing).

    // Converting a live session to a deduplication key
    let key = PrefetchKey::from(&now_playing_instance);
  6. Work with Sonarr Tags

    latest

    Tags in Sonarr can be represented as either a Label (string) or an Id (integer).

    • Use Tag::from("label_name".to_string()) to create a label-based tag.
    • Use client.update_tag(&mut tag) to resolve a Tag::Label into a Tag::Id by querying the Sonarr API. If the label cannot be resolved, it remains a Label and a warning is logged.
    • Use series.is_tagged_with(&tag) to check if a SeriesResource contains a specific tag ID.
    let mut tag = Tag::from("my_tag".to_string());
    client.update_tag(&mut tag).await;
    
    if let Tag::Id(id) = tag {
        if series.is_tagged_with(&Tag::Id(id)).unwrap_or(false) {
            // ...
        }
    }
  7. How Plex session and queue tracking works

    latest

    Prefetcharr's Plex integration uses a hybrid approach to track active playback and manage queues:

    1. REST Polling: The client polls /status/sessions to get a list of active sessions and metadata (like grandparentKey and librarySectionTitle).
    2. WebSocket Notifications: Because the REST endpoint does not provide the playQueueID, the client establishes a WebSocket connection to :websockets/notifications. It listens for PlaySessionStateNotification messages to map sessionKey to playQueueID.
    3. Internal Cache: These mappings are stored in an internal PlayQueueCache. This cache is updated in real-time as Plex pushes notifications. When a session state becomes stopped, the mapping is evicted from the cache.
    4. Queue Injection: When appending to a queue, the client uses the machine_id (from /identity) and the playQueueID to construct a PUT request to the Plex plugin endpoint: server://{machine_id}/com.plexapp.plugins.library/library/metadata/{rating_key}.
  8. Understand the Media Server client traits

    latest

    The project uses a trait-based system to abstract different media server backends (like Plex or Emby). To implement a new client, you must satisfy two primary traits:

    1. ProvideNowPlaying: Responsible for discovering active playback sessions and extracting metadata from them.

      • sessions(): Returns a list of active sessions.
      • extract(session): Converts a specific session into a NowPlaying object.
      • now_playing(): A helper method that returns a stream of all current NowPlaying data.
    2. Client: The high-level interface for interacting with the media server.

      • now_playing(): Returns a stream of current playback data.
      • now_playing_updates(interval, has_pending): Returns a stream that polls the server at a specified interval. It uses an AtomicBool (has_pending) to allow the system to trigger an immediate poll if new data needs processing, rather than waiting for the full interval.
      • probe(): Checks if the server is reachable.
      • run(): Starts the client loop.

    If your client also supports adding items to a playback queue, implement the Queue trait.

    • append(np, episodes): Adds specific EpisodeRef pairs to the session referenced by np.
    // Example of the relationship between traits
    pub trait ProvideNowPlaying {
        type Session;
        fn sessions(&self) -> impl std::future::Future<Output = anyhow::Result<Vec<Self::Session>>> + Send;
        fn extract(&self, session: Self::Session) -> impl std::future::Future<Output = anyhow::Result<NowPlaying>> + Send;
    }
    
    pub trait Client {
        fn now_playing(&self) -> BoxFuture<'_, anyhow::Result<BoxStream<'_, NowPlaying>>>;
        fn probe(&self) -> LocalBoxFuture<'_, anyhow::Result<()>>;
        fn run(&self) -> BoxFuture<'_, ()>;
    }
  9. Configure prefetcharr using a TOML file

    latest

    The modern way to configure prefetcharr is by providing a path to a TOML configuration file via the --config CLI flag. This is the recommended method to avoid legacy configuration issues.

    To run the application with a specific configuration file:

    prefetcharr --config /path/to/config.toml
  10. Configure the MediaServer section

    latest

    The media_server configuration defines the connection to your media playback server (e.g., Jellyfin, Emby, Plex, or Tautulli).

    Key fields:

    • type: The type of media server being used.
    • url: The base URL of the media server.
    • api_key: The API key for Jellyfin/Emby/Tautulli or the server token for Plex.
    • users (optional): A list of specific user IDs or names to monitor. If omitted, all users are monitored.
    • libraries (optional): A list of specific library names to monitor. If omitted, all libraries are monitored.
    [[media_server]]
    type = "jellyfin"
    url = "http://localhost:8096"
    api_key = "your_api_key"
    users = [ "John", "12345" ]
    libraries = [ "TV Shows", "Anime" ]
  11. Configure the Sonarr section

    latest

    The sonarr configuration defines how prefetcharr interacts with your Sonarr instance.

    Key fields:

    • url: The base URL of your Sonarr instance.
    • api_key: Your Sonarr API key.
    • exclude_tag (optional): A specific tag used to exclude certain series from being prefetched.
    [[sonarr]]
    url = "http://localhost:8989"
    api_key = "your_sonarr_api_key"
    exclude_tag = "no_prefetch"