Skip to content

fe80Grau/ytdlp2STRM

Repository files navigation

ytdlp2STRM

Buy Me A Coffee

  • Youtube / Twitch / Crunchyroll etc. to STRM files
  • Watch Youtube through Jellyfin or Emby
  • Watch Twitch through Jellyfin or Emby
  • Watch Crunchyroll through Jellyfin or Emby #52 ytdlp2STRM
image

Prerequisite

  • Python 3 https://www.python.org/downloads/
    • Tested with Python 3.10 and 3.11. Very new major/minor releases occasionally ship breaking changes against some of the Python dependencies; if you hit unexplained import or runtime errors, try Python 3.11.
    • On Windows make sure "Add Python to PATH" is checked during install.
  • FFmpeg https://ffmpeg.org/ — must be available on the system PATH (ffmpeg -version from a terminal should work).
  • yt-dlp https://github.com/yt-dlp/yt-dlp — must be available on the system PATH as a binary (yt-dlp --version must work from a terminal), because ytdlp2STRM calls it as an external process.
    • pip install -r requierments.txt installs the Python package, but on some systems the entry-point binary is not picked up on PATH. If that happens, install it as a binary instead:
      • Windows (PowerShell): winget install yt-dlp.yt-dlp
      • Linux (Debian/Ubuntu): sudo apt install yt-dlp or grab the release binary from the yt-dlp repo.
      • macOS: brew install yt-dlp.

Installation and usage

  • To allocate ytdlp2STRM, I suggest using /opt/ in Linux or C:\ProgramData in Windows.

Linux

  • The next steps have been tested on Ubuntu 22.04 LTS. In terminal:
cd /opt && git clone https://github.com/fe80Grau/ytdlp2STRM.git
  • Install requierments.txt
cd /opt/ytdlp2STRM && pip install -r requierments.txt
  • Copy service file to system services folder
sudo cp config/ytdlp2strm.service /etc/systemd/system/ytdlp2strm.service
  • Enable service
sudo systemctl enable ytdlp2strm.service
  • Start service
sudo systemctl start ytdlp2strm.service
  • Check it
sudo systemctl status ytdlp2strm.service
  • Check GUI in browser
http://localhost:5000/

Windows

  • The next steps have been tested on Windows 11 Pro 22H2. Using Powershell or Windows Terminal with Administrator privileges:
cd C:\ProgramData; git clone https://github.com/fe80Grau/ytdlp2STRM.git;
  • Install requierments.txt
cd C:\ProgramData\ytdlp2STRM; pip install -r requierments.txt
  • Create a task that is scheduled to run main.py at startup. If you plan to install ytdlp2STRM in a different folder than C:\ProgramData\ytdlp2STRM, edit ./config/MS-TASK-ytdlp2STRM.xml
schtasks.exe /create /tn "ytdlp2STRM" /xml C:\ProgramData\ytdlp2STRM\config\MS-TASK-ytdlp2STRM.xml
  • Run task
schtasks.exe /run /tn "ytdlp2STRM"
  • In case everything is working, these commands will return "Running"
(Get-ScheduledTask | Where TaskName -eq ytdlp2STRM ).State
  • Check the GUI in the browser.
http://localhost:5000/

Docker

To deploy this as a Docker container, follow these steps in the ytdlp2STRM root folder.

  • Build Docker image. The default host port is 5005, to change it edit Dockerfile and change the env value of DOCKER_PORT with the same port that you will configure in the docker run command.
docker build . -t "ytdlp2strm" 
  • Create a volume to preserve data.
docker create --name ytdlp2strm-data ytdlp2strm 
  • Run the container with volume and mount D:\media (host folder for accessing strm files, change it as you prefer) in /media (container folder).
docker run -p 5005:5000 --restart=always -d -v D:\media:/media --volumes-from ytdlp2strm-data --name ytdlp2STRM ytdlp2strm
  • Check the GUI in the browser
http://localhost:5005/

Docker HUB

  • https://hub.docker.com/r/fe80grau/ytdlp2strm image

    • Run container. To persist strm files, you must configure a volume, that is, a directory on the host that points to the /media directory in the container. Also, the default env value of DOCKER_PORT is 5005, make sure to put 5005 as host port or re-declare the env value of DOCKER_PORT like I do in the following screenshot. image
  • Check the GUI in the browser

http://localhost:5001/

Docker compose YAML template

  • Thank you INPoppoRTUNE
  • INPoppoRTUNE was here
---
services:
 ytdlp2strm:
    image: fe80grau/ytdlp2strm
    container_name: ytdlp2STRM
    environment:
      - AM_I_IN_A_DOCKER_CONTAINER=Yes
      - DOCKER_PORT=5005
    volumes:
      - /local/path/to/media:/media
      - /local/path/to/channel_list.json:/opt/ytdlp2STRM/plugins/youtube/channel_list.json
      - /local/path/to/yt_config.json:/opt/ytdlp2STRM/plugins/youtube/config.json
      - /local/path/to/config.json:/opt/ytdlp2STRM/config/config.json
      - /local/path/to/crons.json:/opt/ytdlp2STRM/config/crons.json
      - ytdlp2strm-data:/opt/ytdlp2STRM
    ports:
      - 5005:5000
    restart: always
volumes:
  ytdlp2strm-data:

Where:

  • /local/path/to/media is the local folder where the .strm file will be created
  • /local/path/to/config.json is optional and will set your ytdlp2STRM general settings; formatted as config.example.json
  • /local/path/to/crons.json is optional and will set your ytdlp2STRM cronjob settings; formatted as crons.example.json
  • /local/path/to/channel_list.json is optional and will set your Youtube channel list; formatted as channel_list.example.json
  • /local/path/to/yt_config.json is optional and will set your Youtube plugin settings; formatted as config.example.json
  • The default env value of DOCKER_PORT is 5005, make sure to put 5005 as host port or re-declare the env value of DOCKER_PORT

Additional info

  • After that you can view all channels folders within /media/Youtube and their strm files. If you are using Jellyfin/Emby, add /media/Youtube, /media/Twitch and /media/Crunchyroll as folders in Library and enjoy it!

New in v1.1.1

  • Fix: duplicate companion files in YouTube channels: when a YouTube video was renamed or the episode counter had advanced, every cron run regenerated .nfo, .png, .vtt and .srt files under a new episode number with no .strm next to them, while the real .strm lived at the original number. The existence check now runs before the new title/episode number is computed and reuses the path of the existing .strm for subtitle and metadata refreshes. The lookup also tolerates unreadable/locked files.
  • Fix #119: video_quality change ignored until cache TTL expired: the direct-mode HLS cache (in memory and in <strm_output_folder>/.direct_cache) was keyed only by youtube_id + lang, so a previously-resolved low-quality variant kept being served after changing video_quality in plugins/youtube/config.json. The quality is now part of the cache key and filename, so changing the setting invalidates the previous caches immediately.
  • Cleanup tool: scripts/clean_youtube_orphans.py removes companion files (.nfo, .png/.jpg/.webp, .vtt, .srt, .ass) that no longer have a matching .strm in the same folder. Runs in dry-run by default; pass --delete to actually remove files.
    python scripts/clean_youtube_orphans.py "/media/Youtube"           # dry-run
    python scripts/clean_youtube_orphans.py "/media/Youtube" --delete  # actually delete
  • Centralized version: the application version is now read from version.py (__version__) and injected into every UI template as app_version, instead of being hardcoded in each HTML page and in cli.py. To cut a release just bump version.py.
  • YouTube iframe mode: new generation method for the YouTube plugin. The .strm file is written with the public YouTube watch URL (https://www.youtube.com/watch?v=<id>) instead of a ytdlp2STRM endpoint, so players/apps that can resolve YouTube URLs natively don't need to go through this service. Use it from cron or CLI as --params iframe.

New in v1.1.0

  • Season folders by year: YouTube and Twitch videos are now organized in year-based Season folders
    • Structure: Channel/Season {year}/S{year}E{XX} - video.strm
    • Example: Channel [ID]/Season 2025/S2025E01 - Video Title.strm
  • UI improvements:
    • New UI
  • Authentication improvements:
    • All yt-dlp commands now properly use cookie authentication
    • Better handling of subscriber-only content on Twitch

Youtube

  • SponsorBlock doesn't work in redirect mode.
  • Local NFO for each video
  • audio- prefix
  • Season folders by year: Videos are organized in Season {year} folders (e.g., Season 2025, Season 2026)
  • Episode numbering: Files are named S{year}E{XX} format, resetting to E01 each year
  • Cookie authentication: Supports both browser cookies and cookie files for age-restricted content
  • Language configuration: Configurable audio language via lang parameter in config.json
  • Video quality limit: video_quality can limit playback quality. Use best for the current behavior or values like 1080, 720, 480 to avoid selecting higher resolutions.
  • Subtitles: When download_subtitles is enabled, YouTube .vtt subtitles are downloaded next to each .strm file. WebVTT files are post-processed to improve Jellyfin/Emby rendering:
    • Force centered subtitle alignment.
    • Remove YouTube karaoke inline timestamps.
    • Collapse repeated rollup/persiana cues.
    • The same cleanup is also applied when subtitles are served through /youtube/subtitles/<id>.vtt.
    • convert_subtitles_to_srt can also create .srt sidecar files from the cleaned VTT subtitles for better compatibility with Android/TV clients.
    • keep_vtt_subtitles keeps the original cleaned .vtt files when SRT conversion is enabled.
  • Direct playback cache: YouTube direct mode resolves the best HLS stream and stores the processed manifest in <strm_output_folder>/.direct_cache.
    • direct_stream_cache_hours controls how long cached direct manifests are considered valid.
    • Cached manifests avoid repeating the slow yt-dlp resolution on repeated playback.
  • Optimized direct HLS: With direct_serve_media_playlist enabled, ytdlp2STRM serves the selected media playlist directly instead of the full master playlist, reducing startup work for Jellyfin/Emby while keeping the selected highest-quality stream.
    • The generated VOD playlist is cacheable for direct_stream_cache_hours, which can reduce extra playlist reloads during seek operations.
    • Seeking still depends on Jellyfin/Emby fetching the target YouTube HLS segment from googlevideo.com. For near-instant seeking like YouTube, the only fully reliable options are local/download playback or a future segment proxy/cache mode, both with higher disk/bandwidth cost.
  • Direct prewarm:
    • direct_prewarm_latest_per_channel precaches the latest detected videos per channel during the YouTube scan.
    • direct_prewarm_neighbors precaches the previous and next video in the same folder when a video is played.
    • Prewarm runs in background and is limited to avoid launching too many yt-dlp resolutions at once.

Twitch

  • If a live video is on air the !000-live-channel.strm will be created. The script will download the strm for each video in the /videos channel tab in any manner. Take a look at the limits and daterange values for videos in ./plugins/twitch/config.json.
  • SponsorBlock doesn't work in redirect mode, Twitch only works in direct mode at the moment.
  • Season folders by year: Videos are organized in Season {year} folders (e.g., Season 2025, Season 2026)
  • Episode numbering: Files are named S{year}E{XX} format, resetting to E01 each year
  • Cookie authentication: Supports browser cookies for subscriber-only content

TV3

  • Plugin for 3cat, content in Catalan.

Crunchyroll

  • Requieres a cookie file from Premium user login (you can extract the cookie file from Crunchyroll with browser extension like https://chrome.google.com/webstore/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc) or load a fresh cookie from browser (check discusion in yt-dlp/yt-dlp#7442 (comment)).
  • Requires yt-dlp nightly build to work. yt-dlp >=2024.05.22.232749.dev0
  • Only works with login auth.
  • I'm using a filter language your crunchyroll_audio_language config value and extractor crunchyrollbeta:hardsub=your crunchyroll_subtitle_language config value to get a version with one language and subs embedded
  • To avoid constant rewriting of the strm files, a file called last_episode.txt is generated in the series directory, it contains the playlist position of the last strm downloaded, this will only generate strm for new episodes.
  • Patch yt-dlp if Crunchyroll not works yt-dlp/yt-dlp#7442 (comment)
  • mutate_values.json A particular file for this plugin. Overwrites the value of a specified field. For example, in cases where the season given by yt-dlp - season_number does not correspond to the actual season of the series. The available fields are as follows: season_number, season, episode_number, and episode.
  • ~~direct mode. On Crunchyroll, the direct mode inherits the functionality of the download mode. ~~
  • bridge mode. Given the latest updates, it is necessary to obtain the audio and video streams separately, redirect their output, and remux both tracks to finally serve them over HTTP. Experimental, sometimes it may fail to start playback and you need to try playing it again. There is no timestamp and it is not possible to navigate through the video's timeline.
  • With download mode, the audio and video streams will be downloaded separately, and after downloading, they will be remuxed to finally serve a final video/mp4 file. The files will be downloaded to ./temp/ and their lifespan will be 24 hours. This is configurable in config.json -> ytdlp2strm_temp_file_duration. The Crunchyroll plugin in download mode will automatically download the latest discovered episode of each series declared in channel_list.json.
  • In the config.json file, and specifically for the Crunchyroll plugin, there are 4 parameters: jellyfin_preload_last_episode, jellyfin_base_url, jellyfin_user_id, and jellyfin_api_key. When configured with their correct values, they allow detecting if an episode is being played and pre-downloading the next one to achieve a seamless playback flow without interruptions.

main.py

A little script to serve yt-dlp video/audio as HTTP data throught Flask and dynamic URLs. We can use this dynamic URLs with youtube id video in url like http://127.0.0.1:5000/youtube/direct/FxCqhXVc9iY and open it with VLC or save it in .strm file (works in Jellyfin)

cli.py

  • Controller that loads plugins functions, used in crons to manage strm files
  • Build strms manually:
cd /opt/ytdlp2STRM/ && python3 cli.py --media youtube --params direct

You can change --media value for another plugin

config/config.json

  • ytdlp2strm_host
  • ytdlp2strm_port
  • ytdlp2strm_keep_old_strm
  • ytdlp2strm_temp_file_duration

config/crons.json

  • Working with Schedule library (https://schedule.readthedocs.io/en/stable/examples.html)

  • Do attribute needs a list with commands ["--media", "youtube", "--params", "direct"], replace youtube with your plugin name and direct with your prefered mode.

  • Custom timezone for each cron

  • direct : A simple redirect to final stream URL. (faster, no disk usage, sponsorblock not works)

  • bridge : Remuxing on fly. (fast, no disk usage)

  • download : First download full video then it's served. (slow, temp disk usage)

  • iframe : The .strm stores the public YouTube watch URL (https://www.youtube.com/watch?v=<id>) instead of a ytdlp2STRM URL. Useful for players/apps that can play YouTube URLs natively without going through this service.

  • With download mode, the files in the temp folder older than 24h will be deleted.

plugins/media/config.json

  • strm_output_folder
  • channels_list_file
  • days_dateafter
  • videos_limit
  • [YOUTUBE] sponsorblock
  • [YOUTUBE] sponsorblock_cats
  • [YOUTUBE] cookies *Required to obtain the manifest for age-protected videos. It can be (cookies-from-browser or cookies)
  • [YOUTUBE] cookie_value *If you set cookies as browser cookies you must indicate the browser (i recommend firefox). In the case of cookies, you must indicate the cookie file path stored in text format
  • [YOUTUBE] lang *Language for yt-dlp extractor
  • [YOUTUBE] episode_format *Episode naming mode. sequential uses incremental episode numbers; mmdd uses month/day style numbering.
  • [YOUTUBE] video_quality *Maximum video height for yt-dlp playback selection. Use best for no limit, or values like 1080, 720, 480.
  • [YOUTUBE] download_subtitles *Download YouTube subtitles/auto-subtitles as .vtt sidecar files and fix alignment/rollup issues for Jellyfin/Emby.
  • [YOUTUBE] convert_subtitles_to_srt *Generate .srt sidecar files from cleaned .vtt subtitles for better Android/TV compatibility.
  • [YOUTUBE] keep_vtt_subtitles *Keep .vtt files after SRT conversion. Set False to leave only .srt files.
  • [YOUTUBE] direct_stream_cache_hours *TTL in hours for cached direct playback manifests stored in <strm_output_folder>/.direct_cache.
  • [YOUTUBE] direct_serve_media_playlist *Serve the selected HLS media playlist directly instead of the master playlist to improve Jellyfin/Emby startup time.
  • [YOUTUBE] direct_prewarm_latest_per_channel *Number of latest videos per channel to prewarm during YouTube scans. Set 0 to disable.
  • [YOUTUBE] direct_prewarm_neighbors *When a direct video is played, prewarm previous/next videos in background.
  • [YOUTUBE] jellyfin_integration *Trigger a Jellyfin/Emby library scan after new YouTube STRM files are generated.
  • [YOUTUBE] jellyfin_base_url *Base URL of your Jellyfin/Emby server.
  • [YOUTUBE] jellyfin_api_key *API key used to trigger library scans.
  • [YOUTUBE] jellyfin_library_name *Library name to refresh when new YouTube content is added.
  • [CRUNCHYROLL] crunchyroll_auth (browser, cookies or login), browser option in addition with background task opening firefox is the best way to keep unatended workflow.
  • [CRUNCHYROLL] crunchyroll_browser (set if your choice in curnchyroll_auth is browser) You can read more about this searching --cookies-from-browser in https://github.com/yt-dlp/yt-dlp
  • [CRUNCHYROLL] crunchyroll_useragent (set if your choice in curnchyroll_auth is browser) Needs the same user agent that your browser. If you search current user-agent in Google you can see your user-agent, copy it.
  • [CRUNCHYROLL] crunchyroll_username (set if your choice in curnchyroll_auth is login)
  • [CRUNCHYROLL] crunchyroll_password (set if your choice in curnchyroll_auth is login)
  • [CRUNCHYROLL] crunchyroll_cookies_file (set if your choice in curnchyroll_auth is cookies)
  • [CRUNCHYROLL] crunchyroll_audio_language
  • [CRUNCHYROLL] crunchyroll_subtitle_language <- embedded in video
  • [CRUNCHYROLL] jellyfin_preload (False by default, set True to preload the next episode while the current is playing in Jellyfin)
  • [CRUNCHYROLL] jellyfin_preload_last_episode (An @Floflo10 idea. False by default, set True to preloads the last episode at the time its strm is generated. Remember in 24h will be deleted from temp folder)
  • [CRUNCHYROLL] jellyfin_base_url (Your Jellyfin URL, without final slash)
  • [CRUNCHYROLL] jellyfin_user_id (Your Jellyfin user_id)
  • [CRUNCHYROLL] jellyfin_api_key (Your Jellyfin api_key)

plugins/media/channel_list.json

  • [YOUTUBE] With "keyword-" prefix you can search for a keyword and this script will create the folders of channels founds dinamically and put inside them the strm files for each video. See an exaple in channel_list.example.json
  • [YOUTUBE] Playlist needs "list-" prefix before playlist id, you can see an exaple in channel_list.example.json
  • [YOUTUBE] If you want to get livestream from /streams youtube channel tab you need to add a new channel in channel_list with /streams (Check an example in ./plugins/youtube/channel_list.example.json)
  • [TWITCH] This script makes a NFO file (tvshow.nfo) for each youtube or twitch channel (to get name, description and images). *Description only works in Linux systems at the moment
  • [CRUNCHYROLL] Only support URL series (not episodes), the script will create a folder for each serie, and subfolders for each season, inside season folder the strm episodes files will be created

Service

  • LINUX: ytdlp2strm.service example service to run main.py with systemctl.
  • WINDOWS: MS-TASK-ytdlp2STRM.xml example scheduled task with schtasks.

Jellyfin / Emby integration

ytdlp2STRM generates .strm files under the strm_output_folder configured in each plugin (plugins/<media>/config.json). Each .strm just contains a URL that points back to this service, so Jellyfin/Emby must have access both to the STRM files and to the ytdlp2STRM HTTP server (http://<host>:5000 by default).

Typical setup:

  1. Decide where the STRM files will live, e.g. /media/Youtube on Linux or D:\media\Youtube on Windows.
  2. Make sure that same path is the strm_output_folder value in plugins/youtube/config.json (and equivalent for Twitch/etc.).
  3. In Jellyfin/Emby create a Library of type "Shows" and point it to that folder (/media/Youtube). Do the same for Twitch if you use that plugin.
  4. Each channel becomes a Show, each year a Season, and each video an Episode (Channel [ID]/Season {year}/S{year}E{XX} - Video title.strm).
  5. In the ytdlp2STRM Youtube plugin config you can enable jellyfin_integration so the service triggers a scan of the configured jellyfin_library_name after new STRM files are written. Populate jellyfin_base_url and jellyfin_api_key accordingly.

If Jellyfin runs on a different host/container than ytdlp2STRM, the URL stored inside the .strm files (http://127.0.0.1:5000/... by default) must be reachable from that host. Change ytdlp2strm_host in config/config.json to the IP/DNS name the Jellyfin server can reach.

Troubleshooting

  • FileNotFoundError: [WinError 2] The system cannot find the file specified (or the equivalent on Linux/macOS) when opening a direct URL or running the plugin from the Dashboard: the service tried to run yt-dlp as a subprocess but could not find the binary on PATH. Install yt-dlp as a binary (see the Prerequisite section) and restart the ytdlp2STRM service.
  • Cron runs but nothing happens / it always starts on the first channel: usually a symptom of the previous point. Check ytdlp2strm.log for FileNotFoundError or command not found: yt-dlp.
  • Dashboard › Run says only python cli.py command can be executed from here: fixed in recent versions by resolving the interpreter server-side. If you still hit it, update to the latest version.
  • Audio is in the wrong language even when lang is set: YouTube videos with dubbed tracks default to the original. Recent versions add -S lang:<lang> automatically so the configured language is preferred. Update and restart.

Credits

GitHub - ShieldsIO GitHub - Flask GitHub - yt-dlp GitHub - andreztz

About

A little script to serve Youtube / Twitch / Crunchyroll videos without storage it. Uses yt-dlp HTTP data throught Flask and dynamic URLs. We can use this dynamic URLs to set STRM files.

Topics

Resources

License

Stars

Watchers

Forks

Contributors