refactor: move chapter and chapter ranges stuff around (#201)
This commit is contained in:

committed by
GitHub

parent
36dd0a622d
commit
3deb8dbb52
249
scripts/uosc.lua
249
scripts/uosc.lua
@@ -326,6 +326,43 @@ local config = {
|
||||
}
|
||||
end
|
||||
end)(),
|
||||
chapter_serializers = (function()
|
||||
-- Parse `chapter_ranges` option into workable data structure
|
||||
local chapter_ranges = {}
|
||||
for _, definition in ipairs(split(options.chapter_ranges, ' *,+ *')) do
|
||||
local start_patterns, color, opacity, end_patterns = string.match(
|
||||
definition, '([^<]+)<(%x%x%x%x%x%x):(%d?%.?%d*)>([^>]+)'
|
||||
)
|
||||
|
||||
-- Valid definition
|
||||
if start_patterns then
|
||||
start_patterns = start_patterns:lower()
|
||||
end_patterns = end_patterns:lower()
|
||||
local uses_bof = start_patterns:find('{bof}') ~= nil
|
||||
local uses_eof = end_patterns:find('{eof}') ~= nil
|
||||
local chapter_range = {
|
||||
start_patterns = split(start_patterns, '|'),
|
||||
end_patterns = split(end_patterns, '|'),
|
||||
color = color,
|
||||
opacity = tonumber(opacity),
|
||||
ranges = {},
|
||||
uses_bof = uses_bof,
|
||||
uses_eof = uses_eof,
|
||||
}
|
||||
|
||||
-- Filter out special keywords so we don't use them when matching titles
|
||||
if uses_bof then
|
||||
chapter_range.start_patterns = itable_remove(chapter_range.start_patterns, '{bof}')
|
||||
end
|
||||
if uses_eof and chapter_range.end_patterns then
|
||||
chapter_range.end_patterns = itable_remove(chapter_range.end_patterns, '{eof}')
|
||||
end
|
||||
|
||||
chapter_ranges[#chapter_ranges + 1] = chapter_range
|
||||
end
|
||||
end
|
||||
return chapter_ranges
|
||||
end)(),
|
||||
}
|
||||
-- Adds `{element}_persistency` property with table of flags when the element should be visible (`{paused = true}`)
|
||||
for _, name in ipairs({'timeline', 'controls', 'volume', 'top_bar', 'speed'}) do
|
||||
@@ -367,7 +404,8 @@ local state = {
|
||||
time_human = nil, -- current playback time in human format
|
||||
duration_or_remaining_time_human = nil, -- depends on options.total_time
|
||||
pause = mp.get_property_native('pause'),
|
||||
chapters = nil,
|
||||
chapters = {},
|
||||
chapter_ranges = {},
|
||||
border = mp.get_property_native('border'),
|
||||
fullscreen = mp.get_property_native('fullscreen'),
|
||||
maximized = mp.get_property_native('window-maximized'),
|
||||
@@ -399,102 +437,6 @@ local state = {
|
||||
margin_bottom = 0,
|
||||
}
|
||||
|
||||
-- Parse `chapter_ranges` option into workable data structure
|
||||
local chapter_ranges = nil
|
||||
for _, definition in ipairs(split(options.chapter_ranges, ' *,+ *')) do
|
||||
local start_patterns, color, opacity, end_patterns = string.match(
|
||||
definition, '([^<]+)<(%x%x%x%x%x%x):(%d?%.?%d*)>([^>]+)'
|
||||
)
|
||||
|
||||
-- Valid definition
|
||||
if start_patterns then
|
||||
start_patterns = start_patterns:lower()
|
||||
end_patterns = end_patterns:lower()
|
||||
local uses_bof = start_patterns:find('{bof}') ~= nil
|
||||
local uses_eof = end_patterns:find('{eof}') ~= nil
|
||||
local chapter_range = {
|
||||
start_patterns = split(start_patterns, '|'),
|
||||
end_patterns = split(end_patterns, '|'),
|
||||
color = color,
|
||||
opacity = tonumber(opacity),
|
||||
ranges = {},
|
||||
}
|
||||
|
||||
-- Filter out special keywords so we don't use them when matching titles
|
||||
if uses_bof then
|
||||
chapter_range.start_patterns = itable_remove(chapter_range.start_patterns, '{bof}')
|
||||
end
|
||||
if uses_eof and chapter_range.end_patterns then
|
||||
chapter_range.end_patterns = itable_remove(chapter_range.end_patterns, '{eof}')
|
||||
end
|
||||
|
||||
chapter_range['serialize'] = function(chapters)
|
||||
chapter_range.ranges = {}
|
||||
local current_range = nil
|
||||
-- bof and eof should be used only once per timeline
|
||||
-- eof is only used when last range is missing end
|
||||
local bof_used = false
|
||||
|
||||
local function start_range(chapter)
|
||||
-- If there is already a range started, should we append or overwrite?
|
||||
-- I chose overwrite here.
|
||||
current_range = {['start'] = chapter}
|
||||
end
|
||||
|
||||
local function end_range(chapter)
|
||||
current_range['end'] = chapter
|
||||
chapter_range.ranges[#chapter_range.ranges + 1] = current_range
|
||||
-- Mark both chapter objects
|
||||
current_range['start']._uosc_used_as_range_point = true
|
||||
current_range['end']._uosc_used_as_range_point = true
|
||||
-- Clear for next range
|
||||
current_range = nil
|
||||
end
|
||||
|
||||
for _, chapter in ipairs(chapters) do
|
||||
if type(chapter.title) == 'string' then
|
||||
local lowercase_title = chapter.title:lower()
|
||||
|
||||
-- Is ending check and handling
|
||||
if chapter_range.end_patterns then
|
||||
chapter.is_end_only = false
|
||||
for _, end_pattern in ipairs(chapter_range.end_patterns) do
|
||||
if lowercase_title:find(end_pattern) then
|
||||
if current_range == nil and uses_bof and not bof_used then
|
||||
bof_used = true
|
||||
start_range({time = 0})
|
||||
end
|
||||
if current_range ~= nil then
|
||||
end_range(chapter)
|
||||
end
|
||||
chapter.is_end_only = end_pattern ~= '.*'
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Is start check and handling
|
||||
for _, start_pattern in ipairs(chapter_range.start_patterns) do
|
||||
if lowercase_title:find(start_pattern) then
|
||||
start_range(chapter)
|
||||
chapter.is_end_only = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- If there is an unfinished range and range type accepts eof, use it
|
||||
if current_range ~= nil and uses_eof then
|
||||
end_range({time = state.duration or infinity})
|
||||
end
|
||||
end
|
||||
|
||||
chapter_ranges = chapter_ranges or {}
|
||||
chapter_ranges[#chapter_ranges + 1] = chapter_range
|
||||
end
|
||||
end
|
||||
|
||||
--[[ CLASSES ]]
|
||||
|
||||
---@class Class
|
||||
@@ -883,32 +825,88 @@ function delete_file(path)
|
||||
})
|
||||
end
|
||||
|
||||
function serialize_chapter_ranges(chapters)
|
||||
state.chapter_ranges = {}
|
||||
|
||||
for _, chapter_serializer in ipairs(config.chapter_serializers) do
|
||||
local current_range = nil
|
||||
-- bof and eof should be used only once per timeline
|
||||
-- eof is only used when last range is missing end
|
||||
local bof_used = false
|
||||
|
||||
local function start_range(chapter)
|
||||
-- If there is already a range started, should we append or overwrite?
|
||||
-- I chose overwrite here.
|
||||
current_range = {['start'] = chapter}
|
||||
end
|
||||
|
||||
local function end_range(chapter)
|
||||
current_range['end'] = chapter
|
||||
current_range.color = chapter_serializer.color
|
||||
current_range.opacity = chapter_serializer.opacity
|
||||
state.chapter_ranges[#state.chapter_ranges + 1] = current_range
|
||||
-- Mark both chapter objects
|
||||
current_range['start']._uosc_used_as_range_point = true
|
||||
current_range['end']._uosc_used_as_range_point = true
|
||||
-- Clear for next range
|
||||
current_range = nil
|
||||
end
|
||||
|
||||
for _, chapter in ipairs(chapters) do
|
||||
if type(chapter.title) == 'string' then
|
||||
local lowercase_title = chapter.title:lower()
|
||||
|
||||
-- Is ending check and handling
|
||||
if chapter_serializer.end_patterns then
|
||||
for _, end_pattern in ipairs(chapter_serializer.end_patterns) do
|
||||
if lowercase_title:find(end_pattern) then
|
||||
if current_range == nil and chapter_serializer.uses_bof and not bof_used then
|
||||
bof_used = true
|
||||
start_range({time = 0})
|
||||
end
|
||||
if current_range ~= nil then
|
||||
end_range(chapter)
|
||||
chapter.is_end_only = end_pattern ~= '.*'
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Is start check and handling
|
||||
for _, start_pattern in ipairs(chapter_serializer.start_patterns) do
|
||||
if lowercase_title:find(start_pattern) and current_range == nil then
|
||||
start_range(chapter)
|
||||
chapter.is_end_only = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- If there is an unfinished range and range type accepts eof, use it
|
||||
if current_range ~= nil and chapter_serializer.uses_eof then
|
||||
end_range({time = infinity})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Ensures chapters are in chronological order
|
||||
function get_normalized_chapters()
|
||||
local chapters = mp.get_property_native('chapter-list')
|
||||
|
||||
function normalize_chapters(chapters)
|
||||
if not chapters then return end
|
||||
|
||||
-- Copy table
|
||||
chapters = itable_slice(chapters)
|
||||
|
||||
-- Ensure chronological order of chapters
|
||||
table.sort(chapters, function(a, b) return a.time < b.time end)
|
||||
|
||||
return chapters
|
||||
end
|
||||
|
||||
function serialize_chapters()
|
||||
-- Sometimes state.duration is not initialized yet for some reason
|
||||
state.duration = mp.get_property_native('duration')
|
||||
local chapters = get_normalized_chapters()
|
||||
|
||||
if not chapters or not state.duration then return end
|
||||
function serialize_chapters(chapters)
|
||||
chapters = normalize_chapters(chapters)
|
||||
if not chapters then return end
|
||||
|
||||
-- Reset custom ranges
|
||||
for _, chapter_range in ipairs(state.chapter_ranges or {}) do
|
||||
chapter_range.serialize(chapters)
|
||||
end
|
||||
serialize_chapter_ranges(chapters)
|
||||
|
||||
for _, chapter in ipairs(chapters) do
|
||||
chapter.title_wrapped, chapter.title_wrapped_width = wrap_text(chapter.title, 25)
|
||||
@@ -2771,22 +2769,18 @@ function Timeline:render()
|
||||
end
|
||||
|
||||
-- Custom ranges
|
||||
if state.chapter_ranges ~= nil then
|
||||
for i, chapter_range in ipairs(state.chapter_ranges) do
|
||||
for i, range in ipairs(chapter_range.ranges) do
|
||||
local rax = time_ax + time_width * (range['start'].time / state.duration)
|
||||
local rbx = time_ax + time_width * (range['end'].time / state.duration)
|
||||
-- for 1px chapter size, use the whole size of the bar including padding
|
||||
local ray = size <= 1 and bay or fay
|
||||
local rby = size <= 1 and bby or fby
|
||||
ass:rect(rax, ray, rbx, rby, {color = chapter_range.color, opacity = chapter_range.opacity})
|
||||
end
|
||||
end
|
||||
for _, chapter_range in ipairs(state.chapter_ranges) do
|
||||
local rax = time_ax + time_width * (chapter_range['start'].time / state.duration)
|
||||
local rbx = time_ax + time_width * math.min(chapter_range['end'].time / state.duration, 1)
|
||||
-- for 1px chapter size, use the whole size of the bar including padding
|
||||
local ray = size <= 1 and bay or fay
|
||||
local rby = size <= 1 and bby or fby
|
||||
ass:rect(rax, ray, rbx, rby, {color = chapter_range.color, opacity = chapter_range.opacity})
|
||||
end
|
||||
|
||||
-- Chapters
|
||||
if (options.timeline_chapters_opacity > 0
|
||||
and (state.chapters ~= nil and #state.chapters > 0 or state.ab_loop_a or state.ab_loop_b)
|
||||
and (#state.chapters > 0 or state.ab_loop_a or state.ab_loop_b)
|
||||
) then
|
||||
local diamond_radius = foreground_size < 3 and foreground_size or math.max(foreground_size / 10, 3)
|
||||
local diamond_border = options.timeline_border and math.max(options.timeline_border, 1) or 1
|
||||
@@ -4026,7 +4020,6 @@ function update_title(title_template)
|
||||
set_state('title', mp.command_native({'expand-text', title_template}))
|
||||
end
|
||||
mp.register_event('file-loaded', function()
|
||||
serialize_chapters()
|
||||
update_title(mp.get_property_native('title'))
|
||||
end)
|
||||
mp.register_event('end-file ', function() set_state('title', nil) end)
|
||||
@@ -4062,7 +4055,7 @@ end)
|
||||
mp.observe_property('chapter-list', 'native', function(_, chapters)
|
||||
set_state('has_chapter', #chapters > 0)
|
||||
Elements:trigger('dispositions')
|
||||
serialize_chapters()
|
||||
serialize_chapters(chapters)
|
||||
end)
|
||||
mp.observe_property('border', 'bool', create_state_setter('border'))
|
||||
mp.observe_property('ab-loop-a', 'number', create_state_setter('ab_loop_a'))
|
||||
@@ -4214,9 +4207,9 @@ mp.add_key_binding(nil, 'chapters', create_self_updating_menu_opener({
|
||||
title = 'Chapters',
|
||||
type = 'chapters',
|
||||
list_prop = 'chapter-list',
|
||||
list_serializer = function(_, _)
|
||||
list_serializer = function(_, chapters)
|
||||
local items = {}
|
||||
local chapters = get_normalized_chapters()
|
||||
chapters = normalize_chapters(chapters)
|
||||
local active_found = false
|
||||
|
||||
for index, chapter in ipairs(chapters) do
|
||||
|
Reference in New Issue
Block a user