This commit is contained in:
2026-08-15 17:50:48 +02:00
parent 22246060e6
commit 4dc6687de7
35 changed files with 4978 additions and 1509 deletions
+176 -74
View File
@@ -1,10 +1,13 @@
-- trackselect.lua
-- https://github.com/po5/trackselect
-- Because --slang isn't smart enough.
--
-- This script tries to select non-dub
-- audio and subtitle tracks.
-- Idea from https://github.com/siikamiika/scripts/blob/master/mpv%20scripts/dualaudiofix.lua
--[[
SOURCE_: https://github.com/po5/trackselect
Modify_: https://github.com/dyphire/mpv-scripts
-- Because --slang isn't smart enough.
--
-- This script tries to select non-dub
-- audio and subtitle tracks.
-- Idea from https://github.com/siikamiika/scripts/blob/master/mpv%20scripts/dualaudiofix.lua
]]
local opt = require 'mp.options'
local utils = require 'mp.utils'
@@ -14,9 +17,10 @@ local defaults = {
selected = nil,
best = {},
lang_score = nil,
expected_score = nil,
channels_score = -math.huge,
preferred = "jpn/japanese",
excluded = "",
excluded = "commentary/cast/staff/dub/guide",
expected = "",
id = ""
},
@@ -24,6 +28,7 @@ local defaults = {
selected = nil,
best = {},
lang_score = nil,
expected_score = nil,
preferred = "",
excluded = "",
expected = "",
@@ -33,8 +38,9 @@ local defaults = {
selected = nil,
best = {},
lang_score = nil,
expected_score = nil,
preferred = "eng",
excluded = "sign",
excluded = "sign/song",
expected = "",
id = ""
}
@@ -54,11 +60,6 @@ local options = {
-- Try to re-select the last track if mpv cannot do it e.g. when fingerprint changes
smart_keep = false,
--add above (after a comma) any protocol to disable
special_protocols = [[
["://", "^magnet:"]
]],
}
for _type, track in pairs(defaults) do
@@ -72,41 +73,82 @@ options["preferred_audio_channels"] = ""
local tracks = {}
local last = {}
local fingerprint = ""
local trackselect_ran = false
opt.read_options(options, _, function() end)
options.special_protocols = utils.parse_json(options.special_protocols)
local function is_protocol(path)
return type(path) == 'string' and (path:find('^%a[%w.+-]-://') ~= nil or path:find('^%a[%w.+-]-:%?') ~= nil)
end
local function need_ignore(tab, val)
for index, element in ipairs(tab) do
if string.find(val, element) then
return true
local function hex_to_char(x)
return string.char(tonumber(x, 16))
end
local function url_decode(str)
if str ~= nil then
str = str:gsub('^%a[%a%d-_]+://', '')
:gsub('^%a[%a%d-_]+:\\?', '')
:gsub('%%(%x%x)', hex_to_char)
if str:find('://localhost:?') then
str = str:gsub('^.*/', '')
end
str = str:gsub("%?.+", ""):gsub("%+", " ")
local last_pos = str:match('.*[\\/:%?]()')
if last_pos then
str = str:sub(last_pos)
end
end
return str
end
local function is_empty(input)
if input == nil or input == "" then
return true
end
end
local function replace(str, what, with)
if is_empty(str) then return "" end
if is_empty(what) then return str end
if with == nil then with = "" end
what = string.gsub(what, "[%(%)%.%+%-%*%?%[%]%^%$%%]", "%%%1")
with = string.gsub(with, "[%%]", "%%%%")
return string.gsub(str, what, with)
end
local function comparison(str1, str2)
if not str1 and not str2 then
return true
end
if str1 and str2 then
return str1:lower() == str2:lower()
end
return false
end
function contains(track, words, attr)
local function contains(track, words, attr)
if not track[attr] then return false end
local i = 0
if track.external then
i = 1
end
for word in string.gmatch(words:lower(), "([^/]+)") do
local w = word:match("^%s*(.-)%s*$")
i = i - 1
if string.find(tostring(track[attr] or ""):lower(), word) then
if w ~= "" and string.find(tostring(track[attr] or ""):lower(), w) then
return i
end
end
return false
end
function preferred(track, words, attr, title)
local function preferred(track, words, attr, title)
local score = contains(track, words, attr)
if not score then
if tracks[track.type][title] == nil then
tracks[track.type][title] = -math.huge
return true
return false
end
return false
end
@@ -117,10 +159,11 @@ function preferred(track, words, attr, title)
return false
end
function preferred_or_equals(track, words, attr, title)
local function preferred_or_equals(track, words, attr, title)
local score = contains(track, words, attr)
if not score then
if tracks[track.type][title] == nil or tracks[track.type][title] == -math.huge then
if (tracks[track.type][title] == nil or
tracks[track.type][title] == -math.huge) and track.default then
return true
end
return false
@@ -131,14 +174,14 @@ function preferred_or_equals(track, words, attr, title)
return false
end
function copy(obj)
local function copy(obj)
if type(obj) ~= "table" then return obj end
local res = {}
for k, v in pairs(obj) do res[k] = copy(v) end
return res
end
function track_layout_hash(tracklist)
local function track_layout_hash(tracklist)
local t = {}
for _, track in ipairs(tracklist) do
t[#t + 1] = string.format("%s-%d-%s-%s-%s-%s", track.type, track.id, tostring(track.default),
@@ -147,12 +190,51 @@ function track_layout_hash(tracklist)
return table.concat(t, "\n")
end
function trackselect()
local fpath = mp.get_property('path')
if not options.enabled then return end
if need_ignore(options.special_protocols, fpath) then return end
tracks = copy(defaults)
local function sanitize_title(track, path, filename, media_title)
if not track or not track.title then return end
local title = filename
if is_protocol(path) then
title = url_decode(media_title):gsub("%.?([^%.]+)$", "")
track.title = url_decode(track.title)
end
track.title = replace(track.title, title, "")
end
local function selected_tracks()
if not trackselect_ran then
return
end
last = {}
local tracklist = mp.get_property_native("track-list")
local path = mp.get_property("path")
local filename = mp.get_property("filename/no-ext")
local media_title = mp.get_property("media-title", "")
for _, track in ipairs(tracklist) do
if track.selected then
sanitize_title(track, path, filename, media_title)
last[track.type] = track
end
end
end
local function trackselect()
if options.smart_keep then
-- observe specific current-tracks sub-properties
local props = { 'current-tracks/video', 'current-tracks/audio', 'current-tracks/sub' }
for _, p in ipairs(props) do
mp.observe_property(p, 'native', selected_tracks)
end
end
trackselect_ran = true
if not options.enabled then
return
end
tracks = copy(defaults)
local path = mp.get_property("path")
local filename = mp.get_property("filename/no-ext")
local media_title = mp.get_property("media-title", "")
local tracklist = mp.get_property_native("track-list")
local tracklist_changed = false
local found_last = {}
@@ -164,10 +246,14 @@ function trackselect()
fingerprint = new_fingerprint
tracklist_changed = true
end
for _, track in ipairs(tracklist) do
if options.smart_keep and last[track.type] ~= nil and last[track.type].lang == track.lang and
last[track.type].codec == track.codec and last[track.type].external == track.external and
last[track.type].title == track.title then
sanitize_title(track, path, filename, media_title)
if options.smart_keep and last[track.type] ~= nil and
comparison(last[track.type].lang, track.lang) and
track.codec ~= "null" and last[track.type].codec == track.codec and
last[track.type].external == track.external and
comparison(last[track.type].title, track.title) then
tracks[track.type].best = track
options["preferred_" .. track.type .. "_lang"] = ""
options["excluded_" .. track.type .. "_words"] = ""
@@ -185,40 +271,64 @@ function trackselect()
options["preferred_" .. track.type .. "_channels"] = ""
end
end
if options["preferred_" .. track.type .. "_lang"] ~= "" or options["excluded_" .. track.type .. "_words"] ~= ""
or options["expected_" .. track.type .. "_words"] ~= "" or
if track.codec ~= "null" and options["preferred_" .. track.type .. "_lang"] ~= "" or
options["excluded_" .. track.type .. "_words"] ~= "" or
options["expected_" .. track.type .. "_words"] ~= "" or
(options["preferred_" .. track.type .. "_channels"] or "") ~= "" then
local preferred_lang = options["preferred_" .. track.type .. "_lang"]
local excluded_words = options["excluded_" .. track.type .. "_words"]
local expected_words = options["expected_" .. track.type .. "_words"]
local preferred_channels = options["preferred_" .. track.type .. "_channels"]
if preferred_lang == "" then
if track.type == "video" then
preferred_lang = mp.get_property("vlang", ""):gsub(",", "/")
elseif track.type == "audio" then
preferred_lang = mp.get_property("alang", ""):gsub(",", "/")
elseif track.type == "sub" then
preferred_lang = mp.get_property("slang", ""):gsub(",", "/")
end
end
if track.selected then
tracks[track.type].selected = track.id
if options.smart_keep then
last[track.type] = track
end
end
if track.title then
track.title = string.gsub(string.gsub(track.title, "[%(%)%.%+%-%*%?%[%]%^%$%%]", "%%%1"), filename, "")
end
if next(tracks[track.type].best) == nil or not (tracks[track.type].best.external
and tracks[track.type].best.lang ~= nil and not track.external) then
if options["excluded_" .. track.type .. "_words"] == "" or
not contains(track, options["excluded_" .. track.type .. "_words"], "title") then
if options["expected_" .. track.type .. "_words"] == "" or
contains(track, options["expected_" .. track.type .. "_words"], "title") then
local pass = true
local channels = false
local lang = false
if (options["preferred_" .. track.type .. "_channels"] or "") ~= "" and
preferred_or_equals(track, options["preferred_" .. track.type .. "_lang"], "lang",
"lang_score") then
channels = preferred(track, options["preferred_" .. track.type .. "_channels"],
"demux-channel-count", "channels_score")
pass = channels
if (next(tracks[track.type].best) == nil or not (tracks[track.type].best.external
and tracks[track.type].best.lang ~= nil and not track.external)) then
if excluded_words == "" or not contains(track, excluded_words, "title") then
local channels = false
local lang = false
if (preferred_channels or "") ~= "" and
preferred_or_equals(track, preferred_lang, "lang", "lang_score") then
channels = preferred(track, preferred_channels, "demux-channel-count", "channels_score")
end
if preferred_lang ~= "" then
lang = preferred(track, preferred_lang, "lang", "lang_score")
end
local exp_score = nil
if expected_words then
exp_score = contains(track, expected_words, "title")
end
if exp_score then
local lang_score = nil
local existing_lang = nil
local existing_exp = tracks[track.type].expected_score
if next(tracks[track.type].best) ~= nil and preferred_lang ~= "" then
existing_lang = contains(tracks[track.type].best, preferred_lang, "lang")
lang_score = contains(track, preferred_lang, "lang")
end
if options["preferred_" .. track.type .. "_lang"] ~= "" then
lang = preferred(track, options["preferred_" .. track.type .. "_lang"], "lang", "lang_score")
if existing_exp == nil or exp_score > existing_exp or (exp_score == existing_exp and
((lang_score or -math.huge) > (existing_lang or -math.huge) or channels)) then
tracks[track.type].expected_score = exp_score
tracks[track.type].best = track
end
if (options["preferred_" .. track.type .. "_lang"] == "" and pass) or channels or lang or
(track.external and track.lang == nil and
(not tracks[track.type].best.external or tracks[track.type].best.lang == nil)) then
elseif tracks[track.type].expected_score == nil then
if (preferred_lang == "" and track.default) or channels or lang or
(track.external and track.lang == nil and next(tracks[track.type].best) == nil) then
tracks[track.type].best = track
end
end
@@ -226,9 +336,10 @@ function trackselect()
end
end
end
for _type, track in pairs(tracks) do
if next(track.best) ~= nil and track.best.id ~= track.selected then
mp.set_property(_type:sub(1, 1) .. "id", track.best.id)
mp.set_property('file-local-options/'.. _type:sub(1, 1) .. "id", track.best.id)
if options.smart_keep and found_last[track.best.type] then
last[track.best.type] = track.best
end
@@ -236,22 +347,13 @@ function trackselect()
end
end
function selected_tracks()
local tracklist = mp.get_property_native("track-list")
last = {}
for _, track in ipairs(tracklist) do
if track.selected then
last[track.type] = track
end
end
end
if options.hook then
mp.add_hook("on_preloaded", 50, trackselect)
else
mp.register_event("file-loaded", trackselect)
end
if options.smart_keep then
mp.register_event("track-switched", selected_tracks)
end
mp.add_hook("on_unload", 50, function()
trackselect_ran = false
mp.unobserve_property(selected_tracks)
end)