Skip to content

Commit 03126c5

Browse files
committed
Support for thumbnailing network files/streams
1 parent 7a66d1e commit 03126c5

4 files changed

Lines changed: 98 additions & 7 deletions

File tree

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,22 @@ thumbnail_count=150
9898
min_delta=5
9999
max_delta=90
100100
# 120 seconds aka 2 minutes will add more thumbnails only when the video is over 5 hours long!
101+
102+
# Below are overrides for remote urls (you generally want less thumbnails, because it's slow!)
103+
# Thumbnailing network paths will be done with mpv (leveraging youtube-dl)
104+
105+
# Allow thumbnailing network paths (naive check for "://")
106+
# Defaults to no
107+
thumbnail_network=[yes/no]
108+
# Override thumbnail count, min/max delta, as above
109+
remote_thumbnail_count=60
110+
remote_min_delta=15
111+
remote_max_delta=120
112+
113+
# Try to grab the raw stream and disable ytdl for the mpv subcalls
114+
# Much faster than passing the url to ytdl again, but may cause problems with some sites
115+
Defaults to yes
116+
remote_direct_stream=[yes/no]
101117
```
102118

103119
With `disable_keybind=yes`, you can add your own keybind to [`input.conf`](https://mpv.io/manual/master/#input-conf) with `script-binding generate-thumbnails`, for example:

src/options.lua

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,21 @@ local thumbnailer_options = {
3838
min_delta = 5,
3939
-- 120 seconds aka 2 minutes will add more thumbnails when the video is over 5 hours!
4040
max_delta = 90,
41+
42+
43+
-- Overrides for remote urls (you generally want less thumbnails!)
44+
-- Thumbnailing network paths will be done with mpv
45+
46+
-- Allow thumbnailing network paths (naive check for "://")
47+
thumbnail_network = false,
48+
-- Override thumbnail count, min/max delta
49+
remote_thumbnail_count = 60,
50+
remote_min_delta = 15,
51+
remote_max_delta = 120,
52+
53+
-- Try to grab the raw stream and disable ytdl for the mpv subcalls
54+
-- Much faster than passing the url to ytdl again, but may cause problems with some sites
55+
remote_direct_stream = true,
4156
}
4257

4358
read_options(thumbnailer_options, SCRIPT_NAME)

src/thumbnailer_server.lua

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
1+
function skip_nil(tbl)
2+
local n = {}
3+
for k, v in pairs(tbl) do
4+
table.insert(n, v)
5+
end
6+
return n
7+
end
18

29
function create_thumbnail_mpv(file_path, timestamp, size, output_path)
3-
local mpv_command = {
10+
local ytdl_disabled = mp.get_property_native("ytdl") == false or thumbnailer_options.remote_direct_stream
11+
12+
local mpv_command = skip_nil({
413
"mpv",
514
file_path,
615

16+
-- Disable ytdl
17+
(ytdl_disabled and "--no-ytdl" or nil),
718
-- Disable hardware decoding
819
"--hwdec=no",
920

@@ -17,7 +28,7 @@ function create_thumbnail_mpv(file_path, timestamp, size, output_path)
1728
"--of=rawvideo",
1829
"--ovc=rawvideo",
1930
"--o", output_path
20-
}
31+
})
2132
return utils.subprocess({args=mpv_command})
2233
end
2334

@@ -66,10 +77,16 @@ end
6677

6778

6879
function generate_thumbnails(from_keypress)
80+
msg.debug("Thumbnailer state:")
81+
msg.debug(utils.to_string(Thumbnailer.state))
82+
6983
if not Thumbnailer.state.available then
7084
if from_keypress then
7185
mp.osd_message("Nothing to thumbnail", 2)
7286
end
87+
if Thumbnailer.state.is_remote then
88+
msg.warn("Not thumbnailing remote file")
89+
end
7390
return
7491
end
7592

@@ -99,6 +116,16 @@ function generate_thumbnails(from_keypress)
99116
end
100117
end
101118

119+
if Thumbnailer.state.is_remote then
120+
thumbnail_func = create_thumbnail_mpv
121+
if thumbnailer_options.remote_direct_stream then
122+
-- Use the direct stream (possibly) provided by ytdl
123+
-- This skips ytdl on the sub-calls, making the thumbnailing faster
124+
-- Works well on YouTube, rest not really tested
125+
file_path = mp.get_property_native("stream-path")
126+
end
127+
end
128+
102129
mp.commandv("script-message", "mpv_thumbnail_script-enabled")
103130

104131
local generate_thumbnail_for_index = function(thumbnail_index)

src/thumbnailer_shared.lua

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ function Thumbnailer:update_state()
6060

6161
self.state.ready = true
6262

63+
local file_path = mp.get_property_native("path")
64+
self.state.is_remote = file_path:find("://") ~= nil
65+
6366
self.state.available = false
6467

6568
-- Make sure the file has video (and not just albumart)
@@ -80,7 +83,19 @@ end
8083

8184

8285
function Thumbnailer:get_thubmnail_template()
83-
local file_key = ("%s-%d"):format(mp.get_property_native("filename/no-ext"), mp.get_property_native("file-size"))
86+
local file_path = mp.get_property_native("path")
87+
local is_remote = file_path:find("://") ~= nil
88+
89+
local filename = mp.get_property_native("filename/no-ext")
90+
local filesize = mp.get_property_native("file-size", 0)
91+
92+
if is_remote then
93+
filesize = 0
94+
end
95+
96+
filename = filename:gsub('[^a-zA-Z0-9_.%-\' ]', '')
97+
98+
local file_key = ("%s-%d"):format(filename, filesize)
8499
local file_template = join_paths(self.cache_directory, file_key, "%06d.bgra")
85100
return file_template
86101
end
@@ -111,13 +126,31 @@ function Thumbnailer:get_delta()
111126
local file_duration = mp.get_property_native("duration")
112127
local is_seekable = mp.get_property_native("seekable")
113128

114-
if file_path:find("://") ~= nil or not is_seekable or not file_duration then
115-
-- Not a local path, not seekable or lacks duration
129+
-- Naive url check
130+
local is_remote = file_path:find("://") ~= nil
131+
132+
local remote_and_disallowed = is_remote
133+
if is_remote and thumbnailer_options.thumbnail_network then
134+
remote_and_disallowed = false
135+
end
136+
137+
if remote_and_disallowed or not is_seekable or not file_duration then
138+
-- Not a local path (or remote thumbnails allowed), not seekable or lacks duration
116139
return nil
117140
end
118141

119-
local target_delta = (file_duration / thumbnailer_options.thumbnail_count)
120-
local delta = math.max(thumbnailer_options.min_delta, math.min(thumbnailer_options.max_delta, target_delta))
142+
local thumbnail_count = thumbnailer_options.thumbnail_count
143+
local min_delta = thumbnailer_options.min_delta
144+
local max_delta = thumbnailer_options.max_delta
145+
146+
if is_remote then
147+
thumbnail_count = thumbnailer_options.remote_thumbnail_count
148+
min_delta = thumbnailer_options.remote_min_delta
149+
max_delta = thumbnailer_options.remote_max_delta
150+
end
151+
152+
local target_delta = (file_duration / thumbnail_count)
153+
local delta = math.max(min_delta, math.min(max_delta, target_delta))
121154

122155
return delta
123156
end

0 commit comments

Comments
 (0)