Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 91 additions & 82 deletions src/thumbnailer_shared.lua
Original file line number Diff line number Diff line change
Expand Up @@ -73,94 +73,103 @@ end

function Thumbnailer:on_video_change(params)
-- Gather a new state when we get proper video-dec-params and our state is empty
if params then
if not self.state.ready then
self:update_state()
self:check_storyboard_async(function()
local duration = mp.get_property_native("duration")
local max_duration
if self.state.is_remote then
max_duration = thumbnailer_options.autogenerate_max_duration_remote
else
max_duration = thumbnailer_options.autogenerate_max_duration
end
local max_duration = thumbnailer_options.autogenerate_max_duration

if duration and self.state.available and thumbnailer_options.autogenerate then
-- Notify if autogenerate is on and video is not too long
if duration < max_duration or max_duration == 0 then
self:start_worker_jobs()
end
end
end)
if not params or self.state.ready then
return
end

self:update_state()
self:check_storyboard()

local duration = mp.get_property_native("duration")
local max_duration
if self.state.is_remote then
max_duration = thumbnailer_options.autogenerate_max_duration_remote
else
max_duration = thumbnailer_options.autogenerate_max_duration
end
local max_duration = thumbnailer_options.autogenerate_max_duration

if duration and self.state.available and thumbnailer_options.autogenerate then
-- Notify if autogenerate is on and video is not too long
if duration < max_duration or max_duration == 0 then
self:start_worker_jobs()
end
end
end

-- Check for storyboards existance with yt-dlp and call back (may take a long time)
function Thumbnailer:check_storyboard_async(callback)
if thumbnailer_options.storyboard_enable and self.state.is_remote then
msg.info("Trying to get storyboard info...")
local sb_cmd = {"yt-dlp", "--format", "sb0", "--dump-json", "--no-playlist",
"--extractor-args", "youtube:skip=hls,dash,translated_subs", -- yt speedup
"--", mp.get_property_native("path")}

mp.command_native_async({name="subprocess", args=sb_cmd, capture_stdout=true}, function(success, sb_json)
if success and sb_json.status == 0 then
local sb = utils.parse_json(sb_json.stdout)
if sb and sb.duration and sb.width and sb.height and sb.fragments and #sb.fragments > 0 then
self.state.storyboard = {}
self.state.storyboard.fragments = sb.fragments
self.state.storyboard.fragment_base_url = sb.fragment_base_url
self.state.storyboard.rows = sb.rows or 5
self.state.storyboard.cols = sb.columns or 5

if sb.fps then
self.state.thumbnail_count = math.floor(sb.fps * sb.duration + 0.5) -- round
-- hack: youtube always adds 1 black frame at the end...
if sb.extractor == "youtube" then
self.state.thumbnail_count = self.state.thumbnail_count - 1
end
else
-- estimate the count of thumbnails
-- assume first atlas is always full
self.state.thumbnail_delta = sb.fragments[1].duration / (self.state.storyboard.rows*self.state.storyboard.cols)
self.state.thumbnail_count = math.floor(sb.duration / self.state.thumbnail_delta)
end

-- Storyboard upscaling factor
local scale = 1
if thumbnailer_options.storyboard_upscale then
-- BUG: sometimes mpv crashes when asked for non-integer scaling and BGRA format (something related to zimg?)
-- use integer scaling for now
scale = math.max(1, math.floor(thumbnailer_options.thumbnail_height / sb.height))
end
self.state.thumbnail_size = {w=sb.width*scale, h=sb.height*scale}
self.state.storyboard.scale = scale

local divisor = 1 -- only save every n-th thumbnail
if thumbnailer_options.storyboard_max_thumbnail_count then
divisor = math.ceil(self.state.thumbnail_count / thumbnailer_options.storyboard_max_thumbnail_count)
end
self.state.storyboard.divisor = divisor
self.state.thumbnail_count = math.floor(self.state.thumbnail_count / divisor)
self.state.thumbnail_delta = sb.duration / self.state.thumbnail_count


-- Prefill individual thumbnail states
self.state.thumbnails = {}
for i = 1, self.state.thumbnail_count do
self.state.thumbnails[i] = -1
end
msg.info("Storyboard info acquired! " .. self.state.thumbnail_count)
self.state.available = true
end
end
callback()
end)
-- Check for storyboards in user-data
function Thumbnailer:check_storyboard()
if not thumbnailer_options.storyboard_enable or not self.state.is_remote then
return
end

local json = mp.get_property_native("user-data/mpv/ytdl/json-subprocess-result/stdout")
if not json then
return
end

json = utils.parse_json(json)
if not json then
return
end

local sb
for _, format in pairs(json.formats) do
if format.format_id == "sb0" then
sb = format
break
end
end
local duration = json.duration

if not (sb and duration and sb.width and sb.height and sb.fragments and #sb.fragments > 0) then
return
end

self.state.storyboard = {}
self.state.storyboard.fragments = sb.fragments
self.state.storyboard.fragment_base_url = sb.fragment_base_url
self.state.storyboard.rows = sb.rows or 5
self.state.storyboard.cols = sb.columns or 5

if sb.fps then
self.state.thumbnail_count = math.floor(sb.fps * duration + 0.5) -- round
-- hack: youtube always adds 1 black frame at the end...
if sb.extractor == "youtube" then
self.state.thumbnail_count = self.state.thumbnail_count - 1
end
else
callback()
-- estimate the count of thumbnails
-- assume first atlas is always full
self.state.thumbnail_delta = sb.fragments[1].duration / (self.state.storyboard.rows*self.state.storyboard.cols)
self.state.thumbnail_count = math.floor(duration / self.state.thumbnail_delta)
end

-- Storyboard upscaling factor
local scale = 1
if thumbnailer_options.storyboard_upscale then
-- BUG: sometimes mpv crashes when asked for non-integer scaling and BGRA format (something related to zimg?)
-- use integer scaling for now
scale = math.max(1, math.floor(thumbnailer_options.thumbnail_height / sb.height))
end
self.state.thumbnail_size = {w=sb.width*scale, h=sb.height*scale}
self.state.storyboard.scale = scale

local divisor = 1 -- only save every n-th thumbnail
if thumbnailer_options.storyboard_max_thumbnail_count then
divisor = math.ceil(self.state.thumbnail_count / thumbnailer_options.storyboard_max_thumbnail_count)
end
self.state.storyboard.divisor = divisor
self.state.thumbnail_count = math.floor(self.state.thumbnail_count / divisor)
self.state.thumbnail_delta = duration / self.state.thumbnail_count

-- Prefill individual thumbnail states
self.state.thumbnails = {}
for i = 1, self.state.thumbnail_count do
self.state.thumbnails[i] = -1
end
msg.info("Storyboard info acquired! " .. self.state.thumbnail_count)
self.state.available = true
end


Expand Down