chrissix666-lab

Tree / Self Hosting / Movies & TV Shows / Jellyfin / VideoOSD

Download Button

Downloads the playing video as a 1:1 direct copy from the player.

Status
Green · Published
Type
script
Hours
about 8
Tokens
about 3.6 M

Runs alone or as one of the eight addons of VideoOSD Tweaks & Candy. HighImKevin helped finish it.

GitHub

In this article
  1. A download in the wrong place
  2. The file, without leaving the film
  3. What a click actually does
  4. Raw name or library name
  5. Where one button got complicated

A download in the wrong place

Jellyfin can already hand you the original file. The catch is where: you leave the player, open the item menu and pick Download. The plan was small and clear: one button in the video OSD, one click, no menu, and playback keeps running.

The file, without leaving the film

  • A Download button inside the video OSD, in the same row as the player's own buttons
  • The currently playing video as a 1:1 original file, not a transcode
  • A download that runs in the background while the video keeps playing
  • Automatic tracking: the button always refers to the video that is playing now, also after shuffle, playlists or the next episode
  • A clear state for items without a file: the button turns grey and its tooltip says that there is nothing to download
  • Optional naming after your library instead of the raw name on the server

It is especially useful if you often take media along for offline use, because you decide while you are watching, and you never lose your place.

What a click actually does

The button sits in the bottom right of the player. It reads the ID of the current video from the OSD, builds a link to Jellyfin's own /Items/{id}/Download endpoint with the server address and the access token of your session, puts that link into a hidden element and lets the browser start its normal download. The file lands in your default download folder.

Player control bar with a yellow arrow on the added download button.
The download arrow sits in the player, so I no longer have to leave it for the file.

Because it uses Jellyfin's own endpoint, you get the exact file from the server. No transcoding, no quality loss. A MutationObserver watches the page, because Jellyfin swaps the player content without a reload: it puts the button back if it disappears and follows you when the next video starts.

Raw name or library name

On its own it is one paste into the JavaScript Injector plugin. You need to be logged in, because the token comes from your session.

The settings are few and sit in one block at the top of the script:

OptionShips asEffect
hideOnNarrowWindowtrueHides the button on narrow windows, like Jellyfin's own buttons do
filenameChoice'original''original' keeps the raw name the server sends, 'library' names the file after the library entry
includeYearMoviestrueLibrary mode only: adds the year to film names
includeYearEpisodesfalseLibrary mode only: adds the year to episode names
includeYearVideosfalseLibrary mode only: adds the year to other videos

The library name is built from what Jellyfin knows, not from the file on disk. A film becomes its title, an episode becomes Show - S02E05 - Episode title, season 0 counts as a real number, colons turn into dashes, and characters Windows does not accept are removed. The file keeps its original extension. Original naming is the safe default, and library naming is the better choice if your files on disk have cryptic names.

The plain script needs no server change, only the injector and a logged-in session. The README is careful about one thing: the way the icon gets into the player can vary between web versions, so it names exactly which web versions it was tried on.

On phones, tablets and TV devices the button stays off unless a plugin setting allows it. In the plugin these are admin settings, and the button can be switched during playback through the On/Off Menu.

Download tab of the plugin with filename and year options.
The same choices as an admin page in the plugin.

Where one button got complicated

Most of what the script grew by since its first version is not new features but edge cases. HighImKevin from the Jellyfin community helped finalize the script after I was stuck on an issue for quite a while, and that credit stays at the top of the README.

The first thing users reported was text instead of an icon: the word "download" sat in the player. The script used an icon font that Jellyfin does not ship. @curiousignite posted a workaround on the issue, and the real fix came later: the icon is now an inline SVG and needs no font at all.

The bigger problems only showed up once the script ran inside the plugin and Claude sessions tested it live in a real browser against both Jellyfin versions. The worst one looked like nothing at all. A function the script called for its admin settings was never defined, so every setting was silently ignored and the script stayed on its defaults. In the same round, one browser session turned out to have logged the same harmless warning more than 59,000 times, once for every page change.

Permissions were the serious one. The library name mode goes through the stream endpoint, which does not check the download permission, so a user who was not allowed to download still got the file. The script now asks first and shows a short message instead.

The last hurdle came from games. When Playnite Bridge brought local game extras into Jellyfin, the download endpoint answered 404 for them. The button now checks whether the item can be downloaded and falls back to the stream endpoint, naming the file after the item in a way Windows accepts. If there is no file at all, the button goes grey and says so.

Connected

Next: Screenshot Button