307 lines
12 KiB
Lua
307 lines
12 KiB
Lua
-- Copyright (c) 2025 dyphire <qimoge@gmail.com>
|
|
-- License: MIT
|
|
-- link: https://github.com/dyphire/mpv-scripts
|
|
-- Automatically switches the display's SDR and HDR modes for HDR passthrough
|
|
-- based on the content of the video being played by the mpv, only works on Windows 10 and later systems
|
|
|
|
--! Required for use with mpv-display-plugin: https://github.com/dyphire/mpv-display-plugin
|
|
|
|
local msg = require 'mp.msg'
|
|
local utils = require 'mp.utils'
|
|
local options = require 'mp.options'
|
|
|
|
local o = {
|
|
-- Specify the script working mode, value: noth, pass, switch. default: noth
|
|
-- noth: Do nothing
|
|
-- pass: Passing HDR signals for HDR content when the monitor is in HDR mode
|
|
-- switch: Automatically switch between HDR displays and SDR displays
|
|
-- on Windows 10 and later based on video specifications
|
|
hdr_mode = "noth",
|
|
-- Specify whether to switch HDR mode only when the window is in fullscreen or window maximized
|
|
-- only works with hdr_mode = "switch", default: false
|
|
fullscreen_only = false,
|
|
-- Specify the target peak of the HDR display, default: 203
|
|
-- must be the true peak brightness of the monitor,
|
|
-- otherwise it will cause HDR content to display incorrectly
|
|
target_peak = "203",
|
|
-- Specifies the measured contrast of the output display.
|
|
-- Used in black point compensation during HDR tone-mapping and HDR passthrough.
|
|
-- Must be the true contrast information of the display, e.g. 100000 means 100000:1 maximum contrast
|
|
-- OLED display do not need to change this, default: auto
|
|
target_contrast = "auto",
|
|
}
|
|
options.read_options(o, _, function() end)
|
|
|
|
local hdr_active = false
|
|
local hdr_supported = false
|
|
local first_switch_check = true
|
|
local file_loaded = false
|
|
|
|
local state = {
|
|
icc_profile = mp.get_property_native("icc-profile"),
|
|
icc_profile_auto = mp.get_property_native("icc-profile-auto"),
|
|
target_peak = mp.get_property_native("target-peak"),
|
|
target_prim = mp.get_property_native("target-prim"),
|
|
target_trc = mp.get_property_native("target-trc"),
|
|
target_contrast = mp.get_property_native("target_contrast"),
|
|
colorspace_hint = mp.get_property_native("target-colorspace-hint"),
|
|
inverse_mapping = mp.get_property_native("inverse-tone-mapping")
|
|
}
|
|
|
|
local function query_hdr_state()
|
|
hdr_supported = mp.get_property_native("user-data/display-info/hdr-supported")
|
|
hdr_active = mp.get_property_native("user-data/display-info/hdr-status") == "on"
|
|
end
|
|
|
|
local function switch_display_mode(enable)
|
|
if enable == hdr_active then return end
|
|
local arg = enable and "on" or "off"
|
|
mp.commandv('script-message', 'toggle-hdr-display', arg)
|
|
end
|
|
|
|
local function apply_hdr_settings()
|
|
mp.set_property_native("icc-profile", "")
|
|
mp.set_property_native("icc-profile-auto", false)
|
|
mp.set_property_native("target-prim", "bt.2020")
|
|
mp.set_property_native("target-trc", "pq")
|
|
mp.set_property_native("target-peak", o.target_peak)
|
|
mp.set_property_native("target-contrast", o.target_contrast)
|
|
mp.set_property_native("target-colorspace-hint", "yes")
|
|
mp.set_property_native("inverse-tone-mapping", "no")
|
|
end
|
|
|
|
local function apply_sdr_settings()
|
|
mp.set_property_native("icc-profile", state.icc_profile)
|
|
mp.set_property_native("icc-profile-auto", state.icc_profile_auto)
|
|
mp.set_property_native("target-peak", "203")
|
|
mp.set_property_native("target-contrast", state.target_contrast)
|
|
mp.set_property_native("target-colorspace-hint", "no")
|
|
if state.target_prim ~= "bt.2020" then
|
|
mp.set_property_native("target-prim", state.target_prim)
|
|
else
|
|
mp.set_property_native("target-prim", "auto")
|
|
end
|
|
if state.target_trc ~= "pq" then
|
|
mp.set_property_native("target-trc", state.target_trc)
|
|
else
|
|
mp.set_property_native("target-trc", "auto")
|
|
end
|
|
end
|
|
|
|
local function reset_target_settings()
|
|
mp.set_property_native("target-peak", state.target_peak)
|
|
mp.set_property_native("target-prim", state.target_prim)
|
|
mp.set_property_native("target-trc", state.target_trc)
|
|
mp.set_property_native("target-contrast", state.target_contrast)
|
|
mp.set_property_native("target-colorspace-hint", state.colorspace_hint)
|
|
mp.set_property_native("inverse-tone-mapping", state.inverse_mapping)
|
|
end
|
|
|
|
local function pause_if_needed()
|
|
local paused = mp.get_property_native("pause")
|
|
if not paused then
|
|
mp.set_property_native("pause", true)
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function resume_if_needed(paused_before)
|
|
if paused_before then
|
|
mp.add_timeout(1, function()
|
|
mp.set_property_native("pause", false)
|
|
end)
|
|
end
|
|
end
|
|
|
|
local function handle_hdr_logic(paused_before, target_peak, target_prim, target_trc)
|
|
query_hdr_state()
|
|
if hdr_active and o.hdr_mode ~= "noth" then
|
|
apply_hdr_settings()
|
|
resume_if_needed(paused_before)
|
|
elseif not hdr_active and o.hdr_mode ~= "noth" and
|
|
(tonumber(target_peak) ~= 203 or target_prim == "bt.2020" or target_trc == "pq") then
|
|
apply_sdr_settings()
|
|
end
|
|
end
|
|
|
|
local function handle_sdr_logic(paused_before, target_peak, target_prim, target_trc)
|
|
query_hdr_state()
|
|
if not hdr_active or o.hdr_mode ~= "noth" then
|
|
if (not hdr_active or not state.inverse_mapping) and
|
|
(tonumber(target_peak) ~= 203 or target_prim == "bt.2020" or target_trc == "pq") then
|
|
apply_sdr_settings()
|
|
elseif hdr_active and state.inverse_mapping then
|
|
reset_target_settings()
|
|
end
|
|
resume_if_needed(paused_before)
|
|
end
|
|
if hdr_active and o.hdr_mode == "pass" and state.inverse_mapping then
|
|
reset_target_settings()
|
|
end
|
|
end
|
|
|
|
local function should_switch_hdr(hdr_active, is_fullscreen)
|
|
if o.hdr_mode ~= "switch" then return false end
|
|
if not hdr_active and (not o.fullscreen_only or is_fullscreen) then
|
|
return true
|
|
elseif hdr_active and o.fullscreen_only and not is_fullscreen then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function switch_hdr()
|
|
query_hdr_state()
|
|
local params = mp.get_property_native("video-params")
|
|
local gamma = params and params["gamma"]
|
|
local max_luma = params and params["max-luma"]
|
|
local is_hdr = max_luma and max_luma > 203
|
|
if not gamma then return end
|
|
|
|
local current_state = is_hdr and "hdr" or "sdr"
|
|
local pause_changed = false
|
|
local fullscreen = mp.get_property_native("fullscreen")
|
|
local maximized = mp.get_property_native("window-maximized")
|
|
local target_peak = mp.get_property_native("target-peak")
|
|
local target_prim = mp.get_property_native("target-prim")
|
|
local target_trc = mp.get_property_native("target-trc")
|
|
local is_fullscreen = fullscreen or maximized
|
|
|
|
if current_state == "hdr" then
|
|
local function continue_hdr()
|
|
handle_hdr_logic(pause_changed, target_peak, target_prim, target_trc)
|
|
end
|
|
|
|
if first_switch_check and o.fullscreen_only and not is_fullscreen then
|
|
first_switch_check = false
|
|
elseif should_switch_hdr(hdr_active, is_fullscreen) then
|
|
pause_changed = pause_if_needed()
|
|
if hdr_active and o.fullscreen_only and not is_fullscreen then
|
|
msg.info("Switching to SDR output...")
|
|
switch_display_mode(false)
|
|
else
|
|
msg.info("Switching to HDR output...")
|
|
switch_display_mode(true)
|
|
end
|
|
mp.add_timeout(3, continue_hdr)
|
|
return
|
|
end
|
|
|
|
handle_hdr_logic(false, target_peak, target_prim, target_trc)
|
|
|
|
elseif current_state == "sdr" then
|
|
local function continue_sdr()
|
|
handle_sdr_logic(pause_changed, target_peak, target_prim, target_trc)
|
|
end
|
|
|
|
if hdr_active and o.hdr_mode == "switch" and (not o.fullscreen_only or is_fullscreen) then
|
|
msg.info("Switching back to SDR output...")
|
|
pause_changed = pause_if_needed()
|
|
switch_display_mode(false)
|
|
mp.add_timeout(3, continue_sdr)
|
|
return
|
|
end
|
|
|
|
handle_sdr_logic(false, target_peak, target_prim, target_trc)
|
|
end
|
|
end
|
|
|
|
local function check_paramet()
|
|
query_hdr_state()
|
|
local target_peak = mp.get_property_native("target-peak")
|
|
local target_prim = mp.get_property_native("target-prim")
|
|
local target_trc = mp.get_property_native("target-trc")
|
|
local target_contrast = mp.get_property_native("target-contrast")
|
|
local colorspace_hint = mp.get_property_native("target-colorspace-hint")
|
|
local inverse_mapping = mp.get_property_native("inverse-tone-mapping")
|
|
local params = mp.get_property_native("video-params")
|
|
local gamma = params and params["gamma"]
|
|
local max_luma = params and params["max-luma"]
|
|
local is_hdr = max_luma and max_luma > 203
|
|
if not gamma then return end
|
|
|
|
if is_hdr and hdr_active and o.hdr_mode ~= "noth" then
|
|
if target_peak ~= o.target_peak then
|
|
mp.set_property_native("target-peak", o.target_peak)
|
|
end
|
|
if target_contrast ~= o.target_contrast then
|
|
mp.set_property_native("target-contrast", o.target_contrast)
|
|
end
|
|
if target_prim ~= "bt.2020" then
|
|
mp.set_property_native("target-prim", "bt.2020")
|
|
end
|
|
if target_trc ~= "pq" then
|
|
mp.set_property_native("target-trc", "pq")
|
|
end
|
|
if colorspace_hint ~= "yes" then
|
|
mp.set_property_native("target-colorspace-hint", "yes")
|
|
end
|
|
if inverse_mapping then
|
|
mp.set_property_native("inverse-tone-mapping", "no")
|
|
end
|
|
end
|
|
if not is_hdr and o.hdr_mode ~= "noth" and not state.inverse_mapping
|
|
and (tonumber(target_peak) ~= 203 or target_prim == "bt.2020" or target_trc == "pq") then
|
|
apply_sdr_settings()
|
|
end
|
|
end
|
|
|
|
local function on_start()
|
|
if o.hdr_mode == "noth" or tonumber(o.target_peak) <= 203 then
|
|
return
|
|
end
|
|
local vo = mp.get_property("vo")
|
|
if vo and vo ~= "gpu-next" then
|
|
msg.warn("The current video output is not supported, please use gpu-next")
|
|
return
|
|
end
|
|
file_loaded = true
|
|
query_hdr_state()
|
|
mp.observe_property("video-params", "native", switch_hdr)
|
|
mp.observe_property("target-peak", "native", check_paramet)
|
|
mp.observe_property("target-prim", "native", check_paramet)
|
|
mp.observe_property("target-trc", "native", check_paramet)
|
|
mp.observe_property("target-contrast", "native", check_paramet)
|
|
mp.observe_property("target-colorspace-hint", "native", check_paramet)
|
|
mp.observe_property("user-data/display-info/hdr-status", "native", switch_hdr)
|
|
if o.fullscreen_only then
|
|
mp.observe_property("fullscreen", "native", switch_hdr)
|
|
mp.observe_property("window-maximized", "native", switch_hdr)
|
|
end
|
|
end
|
|
|
|
local function on_end(event)
|
|
query_hdr_state()
|
|
first_switch_check = true
|
|
mp.unobserve_property(switch_hdr)
|
|
mp.unobserve_property(check_paramet)
|
|
if event["reason"] == "quit" and o.hdr_mode == "switch" then
|
|
if hdr_active then
|
|
msg.info("Restoring display to SDR on shutdown")
|
|
switch_display_mode(false)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function on_idle(_, active)
|
|
local target_peak = mp.get_property_native("target-peak")
|
|
local target_prim = mp.get_property_native("target-prim")
|
|
local target_trc = mp.get_property_native("target-trc")
|
|
if active and o.hdr_mode ~= "noth" and
|
|
(tonumber(target_peak) ~= 203 or target_prim == "bt.2020" or target_trc == "pq") then
|
|
apply_sdr_settings()
|
|
end
|
|
if active and file_loaded and o.hdr_mode == "switch" then
|
|
file_loaded = false
|
|
query_hdr_state()
|
|
if hdr_active then
|
|
msg.info("Restoring display to SDR on shutdown")
|
|
switch_display_mode(false)
|
|
end
|
|
end
|
|
end
|
|
|
|
mp.register_event("start-file", on_start)
|
|
mp.register_event("end-file", on_end)
|
|
mp.observe_property("idle-active", "native", on_idle) |