Use `mpv` features which are not exposed in Plex for Windows/Mac/Linux and Plex HTPC

The methods described in this post are not available in other Plex clients apart from “Plex for Windows/Mac/Linux” and “Plex HTPC”

in this post:

  • Pausing and stepping through a video, frame by frame
  • Video zoom/crop
  • Fine tune vertical position of subtitles

The internal AV engine in these players (called ‘MPV’) supports a lot of features which are not available in the Plex user interface.
Some of these features can be used, nonetheless by “circumventing” the Plex user interface and assigning keyboard buttons (or remote control buttons) to them.

The caveat is this: not all features can be accessed by this method. Some settings are pre-defined by the Plex player.
Some other features are only sensibly usable if the playback mode in Plex is “Direct Play”. (example: selecting audio tracks or subtitle tracks. On top of that, a track selection made in this way is not stored on the Plex server.)

Despite of the caveats, there are still some use cases which might be welcomed by some users. Here are those which have been figured out so far.

The contents of this article are largely based on the work of @German and @o_spring . A huge Thank You! to these users who shared their efforts in the forum here and here.

There are two methods:

  1. customized input maps (only available in Plex HTPC)
  2. custom player scripts in Lua (usable in both Plex for Windows/Mac/Linux and Plex HTPC)

The use of inputmaps is explained in this help article: https://support.plex.tv/articles/plex-htpc-input-maps/
The example keyboard.json inputmap contains already a lot of useful examples on how to define buttons and key combinations. It is also important to know which buttons have already been assigned.
inputmaps are slightly easier to use.
They also enable you to use other non-keyboard input devices like remote controls or game controllers.

The use of Lua scripts only differs slightly.
They go into a subfolder named scripts, which itself must be created in the main configuration folder of the Plex player. Start with the folder locations mentioned in here: https://support.plex.tv/articles/plex-htpc-input-maps/, but go upwards one or two levels – until you can see a file named mpv.conf.md. This means you are in the right place.
The scripts folder will be sitting next to the cache, inputmaps, and Logs folders.
You create the scripts in a text-only editor (like e.g. notepad.exe in Windows). Do not use a regular word processor like MS Word or the like. If your editor only writes .txt files, simply rename the file afterwards and swap the .txt file name extension with .lua.

The only difference between Plex HTPC and the Plex desktop app is that the main folder is named Plex instead of Plex HTPC (at least on Mac and Windows).

I haven’t figured out if non-keyboard devices can be used with the lua script method.

Here is how the folders look like on Windows
Plex HTPC:
configfolder_HTPC

Plex Desktop:
configfolder_Plexdesktop

I’ve broken down all keycombos pertaining to one feature into a separate .lua file.
You can put only one or several of these lua scripts at once in the scripts folder – depending on which functionality you want to use.

Pausing and stepping through a video, frame by frame

The keys which are assigned are . and , for stepping forth and back.
(Resuming normal playback may require pressing space twice.)

inputmap method:

// single frame back and forth
	"\\." : "mpv:frame-step",
	"," : "mpv:frame-back-step",

the . requires “escaping” with double backslashes, because these inputmaps are parsed as regular expressions. The dot matches to anything in regex.

lua script method:
(suggested file name: stepping-keybinds.lua)

function stepFwd()
    mp.command("frame-step")
    mp.osd_message("frame +1", 0.5)
end

function stepBack()
    mp.command("frame-back-step")
    mp.osd_message("frame -1", 0.5)
end

mp.add_forced_key_binding(".", "stepFwd", stepFwd)
mp.add_forced_key_binding(",", "stepBack", stepBack)

Video zoom/crop

inputmap method:
CTRL++ zoom in
CTRL+- zoom out
CTRL+* cycle through default and 3 pre-defined zoom factors
CTRL+0 reset zoom to default

//zoom controls
	"Ctrl\\+\\+" : "mpv:add video-zoom 0.05",
	"Ctrl\\+\\-" : "mpv:add video-zoom -0.05",
	"Ctrl\\+\\*" : "mpv:cycle-values video-zoom 0 0.0574502 0.4025857 0.4269356",
	"Ctrl\\+0" : "mpv:set video-zoom 0",

lua script method:

in its current state this uses different keys than the inputmap above.
please comment below if you have figured out how to use keyboard combos instead or how to implement the cycling through predefined zoom factors.

+ zoom in
- zoom out
0 reset zoom to default

suggested file name: zoom-keybinds.lua

function round(num, numDecimalPlaces)
    local mult = 10^(numDecimalPlaces or 0)
    return math.floor(num * mult + 0.5) / mult
end

function zoomAdd()
    mp.command("add video-zoom +0.05")
    zoom_level = mp.get_property("video-zoom")
    zoom_level = round(zoom_level, 2)
    mp.osd_message("Zoom: "..zoom_level, 0.5)
end

function zoomSub()
    mp.command("add video-zoom -0.05")
    zoom_level = mp.get_property("video-zoom")
    zoom_level = round(zoom_level, 2)
    mp.osd_message("Zoom: "..zoom_level, 0.5)
end

function zoomReset()
    mp.command("set video-zoom 0")
    mp.osd_message("Zoom reset")
end

mp.add_forced_key_binding("+", "zoomAdd", zoomAdd)
mp.add_forced_key_binding("-", "zoomSub", zoomSub)
mp.add_forced_key_binding("0", "zoomReset", zoomReset)

Fine tune vertical position of subtitles

Trying to mimic the default keycombo of the MPV standalone player, by using
r subtitles up
R subtitles down

inputmap method:

	// refine subtitle position
	"Shift\\+R" : "mpv:add sub-pos +1",
	"R" : "mpv:add sub-pos -1",

lua script method:
suggested file name: subpos-keybinds.lua

function round(num, numDecimalPlaces)
    local mult = 10^(numDecimalPlaces or 0)
    return math.floor(num * mult + 0.5) / mult
end

function subDown()
    mp.command("add sub-pos +1")
    sub_pos = mp.get_property("sub-pos")
    sub_pos = round(sub_pos, 2)
    mp.osd_message("subtitle position: "..sub_pos, 0.5)
end

function subUp()
    mp.command("add sub-pos -1")
    sub_pos = mp.get_property("sub-pos")
    sub_pos = round(sub_pos, 2)
    mp.osd_message("subtitle position: "..sub_pos, 0.5)
end

mp.add_forced_key_binding("R", "subDown", subDown)
mp.add_forced_key_binding("r", "subUp", subUp)

The mpv commands called by these keybindings are largely documented in here: mpv.io


Related information on customizations:

Here is another use for keybindings: An on-demand audio dynamics compressor for PMP and Plex HTPC

How to customize the appearance of subtitles in these players: Customizing Subtitle appearance in Plex Player for Windows/Mac/Linux and Plex HTPC and PMP


edit history:
2023-02-08 initial post
2023-02-09 subtitle position key assignments were swapped in the lua script
2023-04-05 corrected key assignment from _ to - in Zoom lua script

8 Likes

Thanks for compiling everything into one post! It’s a shame those features are so hidden away, as they’re really useful. Bring back inputmap support to plex desktop (not HTPC)!. Took me months to figure out I could use scripts for the desktop app…

Wow, this was the unlock I was looking for. Now I can add mpv commands to the keyboard.json, and use remotebuddy to make my apple remote send those key mappings. It works, to bring back functionality I used to have in original desktop plex 8 years ago. Thank you!

Perhaps you can help with an issue I cannot solve: I want to map [ and ] to playback speed ]=+100% and [=normal speed.

I can do
"]":"mpv:set speed 100"
which feels like 2x, but I cannot go to 3x or 4x speed, cos the speed parameter only accepts 0.1 to 100.
Perhaps there is a correct syntax to say
"mpv:set speed=speed+100"?

I also cannot get back to normal speed, cos I can’t find a setting for normal speed (it’s not "set speed 0" or "set speed 1").

I know mpv has the buttons { and } mapped for this. But I cannot get them to work when mpv is invoked from within plex htpc. Not even by removing those bindings form the default keyboard.json.

I don’t think that you will get it to work as you imagine it.
The reason is this: mpv when embedded is not working as a simple file player. Instead, it is requesting its media from the Plex server, over a dedicated streaming protocol.
A very important factor when streaming is the necessary bandwidth. Plex server is using a sophisticated method to determine and control the bandwidth of streamed media.
Now, when you want to increase the playback speed, you are also increasing the bandwidth demands of that video.

If you wanted to keep the bandwidth reasonably stable, you’d need to activate the transcoder in Plex server and instruct it to read only every nth frame from a source file to achieve the speed up.
But there is no support for this kind of thing in Plex server – to my knowledge at least.

So in order to support variable playback speed in Plex, you’d first need to implement support for that into the server. Simply telling the playback engine to consume the streamed data faster won’t work as expected (or only within some sort of close range).

1 Like

Normal speed is achieved with “mpv: set speed 1”

FWIW, I’ve been using (with some caveats) the following speed controls in my keyboard.json going back to PMP, which was also MPV-based:

//Speed Controls
"N": { "short": "mpv:set speed 0.5", "long": "mpv:set speed 0.25" },
"M": { "short": "mpv:set speed 2", "long": "mpv:set speed 4" },
"Alt\\+1|Ctrl\\+1": "mpv: set speed 1",
"Alt\\+2": "mpv: set speed 0.5",
"Alt\\+3": "mpv: set speed 0.25",
"Alt\\+4": "mpv: set speed 0.125",
"Ctrl\\+2": "mpv: set speed 2",
"Ctrl\\+3": "mpv: set speed 4",
"Ctrl\\+4": "mpv: set speed 8",
1 Like

I’ve tried these in Plex HTPC and they don’t do anything, unfortunately.

1 Like

This just works. You’re a scholar and a gentleman!

To the commenter below, try pasting this in the keyboard.json at the end of the // Application shortcuts section. Then edit to the keys you want after checking it works. Note you have to copy keyboard.json out of the /default folder and into the parent /inputmaps folder.

These do work in Plex HTPC in the input maps. Though note there shouldn’t be a space between the mpv: and the actual command.

I tried all of that in my first attempt.
Now that I did it again, it suddenly worked.
The only difference now was that this time I closed the file in my text editor before opening Plex HTPC (although keeping the file open didn’t have any negative effects earlier).

I edit the json, save, tab over to htpc, stop and start a media file, and the changes are working.

For those that care, I have htpc working the way I have been trying to get various desktop players working for ages:

  • I use an apple remote, and need to control volume as well as media. This has historically been an issue in that up/down need to navigate in plex, but control volume in mpv. My remote has no separate volume keys.
  • I also like to watch at 1.25x or 1.5x speed, with subtitles and audio.

Here’s what I did:

  • I use remote buddy to map long up/down and long left/right to keyboard keys for volume
  • I edit the keyboard.json keymap to:
//Speed Controls
"Ctrl\\+1": "mpv: set speed 1",
"Ctrl\\+2": "mpv:cycle-values speed 1.25 1.5",
  • Finally in remote buddy I map long right to Ctrl-2 and long left to Ctrl-1. Now I can toggle 1.25 and 1.5 speed, and revert to 1x speed.

Life changing for me :slightly_smiling_face:

1 Like

Slow-motion playback is addicting! FWIW, I need to relaunch Plex HTPC after making changes to the customized input map before it takes effect, since it only reads the file at startup.

With all of these great options, you might want more buttons. I’ve had success (again, there are caveats) using the keyboard.json to achieve short-and-long button presses, e.g., "X": {"short": "stop", "long": "toggle_watched"},

Plan carefully, assigning short presses to playback options that won’t impact UI functionality (such as keystroke entry for searches).

No, it reads the file whenever it is changed.

If you are grouping commands that work only in playback (or not in playback) you can provide arrays of commands. The first command that can execute is the one that is executed. For example, I use ["skip_previous", "previous_pivot_tab"] on the rewind key. In playback, it skips to the previous item in the play queue but in the UI it switches the display pivot (such as going to the previous season). Similarly ["page_up", "step_forward"], can be used for page up or next chapter skipping.

When in a text entry field, the keystroke is processed first so any commands you have set for these will not be executed but instead you will get the keystrokes.

Not here, with Plex HTPC. For a test, while the program is running, change "I": "info", to "I": {"short": "debug", "long": "info"}, and hit “I.” I still get the info overlay, not debug, and the long press just causes rapid fire I’s to transmit, turning the info overlay on-off-on-off. Once Plex HTPC is shut down and relaunched, the short press “I” debug overlay comes up, and a long press brings up the info overlay.

Try changing "H": "home", to "H": {"short": "home", "long": "exit"}, ( and if a long-press doesn’t bring up the exit screen, you’ll need to relaunch the program :slightly_smiling_face: ). Then call up search with Ctrl-F and type “H” – I print an H, but am immediately be sent to the Home screen.

Now try changing "B": "back", to "B": {"short": "back", "long": "exit"}, and attempt to type “B.” I exit the search form.

Obviously, this post involves input mapping (for Plex HTPC), not exposing features, but I thought it instructive in avoiding confusion like Otto encountered.

Anyone able to suggest a way that I can see current playback speed on screen? Would help a load. Either on:

  • small text in corner using stats
  • within OSD when pressing up
  • a message on screen for a short while after changing

A post was split to a new topic: Resolution settings

As far as I can see, you’ll see the command which is passed to mpv in the upper-right corner of the video picture.

If you need further customization, you may need to find a way to use the lua script method. All the example scripts above have already parameters in them which define what to show and for how long. e.g. mp.osd_message("frame +1", 0.5) (text in quotation marks is shown for 0.5 seconds)

Nice, I’ll read up on how to do a Lua script, thanks.

Is it possible to create screenshots with this method?

It worked for me using (Windows 10) "K": "mpv:screenshot", but you may want to tinker with settings in your mpv.conf:

–screenshot-jpeg-quality=80 ## Default=90
–screenshot-png-compression=4 ## Default=7
–screenshot-directory=~~desktop/Screenshots
–screenshot-template=PlexHTPC-%wH,%wM,%wS

Otherwise, depending on your processor, higher quality settings may interfere with playback. I actually use "K": {"short": "mpv:screenshot", "long": "mpv:screenshot-to-file D:/PlexHTPC.png"}, in my inputmap so that a short press gives me a JPG and a long press gives me a PNG, but I had trouble feeding the screenshot-to-file the same desktop destination as the JPGs…

The screenshot-template setting above names the files based on the playback timing when the snapshot was taken.