updated & added README
13 files changed, 986 insertions(+), 19 deletions(-) | |||
---|---|---|---|
A | README.md | +11 | -0 |
M | alacritty/alacritty.yml | +1 | -1 |
M | bspwm/bspwmrc | +2 | -1 |
A | celluloid/scripts/ytdl_hook.lua | +952 | -0 |
A | coc/extensions/db.json | +1 | -0 |
A | coc/extensions/package.json | +1 | -0 |
A | coc/memos.json | +1 | -0 |
M | gtk-3.0/bookmarks | +7 | -2 |
A | gtk-3.0/gtk.css | +0 | -0 |
M | gtk-3.0/settings.ini | +2 | -1 |
A | gtk-4.0/settings.ini | +2 | -0 |
M | qt5ct/qt5ct.conf | +6 | -6 |
D | starship.toml | +0 | -8 |
1@@ -0,0 +1,11 @@
2+# Arjuns gobbled together nix dotfiles
3+
4+Mainly bspwm, polybar, sxhkd & picom. Has some older and unnecessary gnome & xfce stuff.
5+
6+
7+## Acknowledgements
8+
9+ - [BSPWM](https://github.com/baskerville/bspwm)
10+ - [Polybar](https://github.com/polybar/polybar)
11+ - [Picom (ibhagwan fork)](https://github.com/ibhagwan/picom)
12+ - [SXHKD](https://github.com/baskerville/sxhkd)
M · alacritty/alacritty.yml
+1, -11@@ -20,7 +20,7 @@ window:
2 font:
3 # The size to use.
4 size: 13
5- # The normal (roman) font face to use.
6+ # The n3ormal (roman) font face to use.
7 normal:
8 family: JetBrains Mono
9 # Style can be specified to pick a specific face.
M · bspwm/bspwmrc
+2, -11@@ -31,4 +31,5 @@ xset -dpms s off
2 nitrogen --restore &
3 ~/.config/polybar/polybar.sh &
4 sxhkd &
5-#/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 &
6+/usr/bin/gnome-keyring-daemon --start --components=pkcs11,secrets,ssh
7+/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 &
A · celluloid/scripts/ytdl_hook.lua
+952, -0 1@@ -0,0 +1,952 @@
2+local utils = require 'mp.utils'
3+local msg = require 'mp.msg'
4+local options = require 'mp.options'
5+
6+local o = {
7+ exclude = "",
8+ try_ytdl_first = false,
9+ use_manifests = false,
10+ all_formats = false,
11+ force_all_formats = true,
12+ ytdl_path = "youtube-dl",
13+}
14+
15+local ytdl = {
16+ path = nil,
17+ searched = false,
18+ blacklisted = {}
19+}
20+
21+options.read_options(o, nil, function()
22+ ytdl.blacklisted = {} -- reparse o.exclude next time
23+ ytdl.searched = false
24+end)
25+
26+local chapter_list = {}
27+
28+function Set (t)
29+ local set = {}
30+ for _, v in pairs(t) do set[v] = true end
31+ return set
32+end
33+
34+-- ?: surrogate (keep in mind that there is no lazy evaluation)
35+function iif(cond, if_true, if_false)
36+ if cond then
37+ return if_true
38+ end
39+ return if_false
40+end
41+
42+local safe_protos = Set {
43+ "http", "https", "ftp", "ftps",
44+ "rtmp", "rtmps", "rtmpe", "rtmpt", "rtmpts", "rtmpte",
45+ "data"
46+}
47+
48+-- For some sites, youtube-dl returns the audio codec (?) only in the "ext" field.
49+local ext_map = {
50+ ["mp3"] = "mp3",
51+ ["opus"] = "opus",
52+}
53+
54+local codec_map = {
55+ -- src pattern = mpv codec
56+ ["vtt"] = "webvtt",
57+ ["opus"] = "opus",
58+ ["vp9"] = "vp9",
59+ ["avc1%..*"] = "h264",
60+ ["av01%..*"] = "av1",
61+ ["mp4a%..*"] = "aac",
62+}
63+
64+-- Codec name as reported by youtube-dl mapped to mpv internal codec names.
65+-- Fun fact: mpv will not really use the codec, but will still try to initialize
66+-- the codec on track selection (just to scrap it), meaning it's only a hint,
67+-- but one that may make initialization fail. On the other hand, if the codec
68+-- is valid but completely different from the actual media, nothing bad happens.
69+local function map_codec_to_mpv(codec)
70+ if codec == nil then
71+ return nil
72+ end
73+ for k, v in pairs(codec_map) do
74+ local s, e = codec:find(k)
75+ if s == 1 and e == #codec then
76+ return v
77+ end
78+ end
79+ return nil
80+end
81+
82+local function exec(args)
83+ local ret = mp.command_native({name = "subprocess",
84+ args = args,
85+ capture_stdout = true,
86+ capture_stderr = true})
87+ return ret.status, ret.stdout, ret, ret.killed_by_us
88+end
89+
90+-- return true if it was explicitly set on the command line
91+local function option_was_set(name)
92+ return mp.get_property_bool("option-info/" ..name.. "/set-from-commandline",
93+ false)
94+end
95+
96+-- return true if the option was set locally
97+local function option_was_set_locally(name)
98+ return mp.get_property_bool("option-info/" ..name.. "/set-locally", false)
99+end
100+
101+-- youtube-dl may set special http headers for some sites (user-agent, cookies)
102+local function set_http_headers(http_headers)
103+ if not http_headers then
104+ return
105+ end
106+ local headers = {}
107+ local useragent = http_headers["User-Agent"]
108+ if useragent and not option_was_set("user-agent") then
109+ mp.set_property("file-local-options/user-agent", useragent)
110+ end
111+ local additional_fields = {"Cookie", "Referer", "X-Forwarded-For"}
112+ for idx, item in pairs(additional_fields) do
113+ local field_value = http_headers[item]
114+ if field_value then
115+ headers[#headers + 1] = item .. ": " .. field_value
116+ end
117+ end
118+ if #headers > 0 and not option_was_set("http-header-fields") then
119+ mp.set_property_native("file-local-options/http-header-fields", headers)
120+ end
121+end
122+
123+local function append_libav_opt(props, name, value)
124+ if not props then
125+ props = {}
126+ end
127+
128+ if name and value and not props[name] then
129+ props[name] = value
130+ end
131+
132+ return props
133+end
134+
135+local function edl_escape(url)
136+ return "%" .. string.len(url) .. "%" .. url
137+end
138+
139+local function url_is_safe(url)
140+ local proto = type(url) == "string" and url:match("^(.+)://") or nil
141+ local safe = proto and safe_protos[proto]
142+ if not safe then
143+ msg.error(("Ignoring potentially unsafe url: '%s'"):format(url))
144+ end
145+ return safe
146+end
147+
148+local function time_to_secs(time_string)
149+ local ret
150+
151+ local a, b, c = time_string:match("(%d+):(%d%d?):(%d%d)")
152+ if a ~= nil then
153+ ret = (a*3600 + b*60 + c)
154+ else
155+ a, b = time_string:match("(%d%d?):(%d%d)")
156+ if a ~= nil then
157+ ret = (a*60 + b)
158+ end
159+ end
160+
161+ return ret
162+end
163+
164+local function extract_chapters(data, video_length)
165+ local ret = {}
166+
167+ for line in data:gmatch("[^\r\n]+") do
168+ local time = time_to_secs(line)
169+ if time and (time < video_length) then
170+ table.insert(ret, {time = time, title = line})
171+ end
172+ end
173+ table.sort(ret, function(a, b) return a.time < b.time end)
174+ return ret
175+end
176+
177+local function is_blacklisted(url)
178+ if o.exclude == "" then return false end
179+ if #ytdl.blacklisted == 0 then
180+ local joined = o.exclude
181+ while joined:match('%|?[^|]+') do
182+ local _, e, substring = joined:find('%|?([^|]+)')
183+ table.insert(ytdl.blacklisted, substring)
184+ joined = joined:sub(e+1)
185+ end
186+ end
187+ if #ytdl.blacklisted > 0 then
188+ url = url:match('https?://(.+)')
189+ for _, exclude in ipairs(ytdl.blacklisted) do
190+ if url:match(exclude) then
191+ msg.verbose('URL matches excluded substring. Skipping.')
192+ return true
193+ end
194+ end
195+ end
196+ return false
197+end
198+
199+local function parse_yt_playlist(url, json)
200+ -- return 0-based index to use with --playlist-start
201+
202+ if not json.extractor or json.extractor ~= "youtube:playlist" then
203+ return nil
204+ end
205+
206+ local query = url:match("%?.+")
207+ if not query then return nil end
208+
209+ local args = {}
210+ for arg, param in query:gmatch("(%a+)=([^&?]+)") do
211+ if arg and param then
212+ args[arg] = param
213+ end
214+ end
215+
216+ local maybe_idx = tonumber(args["index"])
217+
218+ -- if index matches v param it's probably the requested item
219+ if maybe_idx and #json.entries >= maybe_idx and
220+ json.entries[maybe_idx].id == args["v"] then
221+ msg.debug("index matches requested video")
222+ return maybe_idx - 1
223+ end
224+
225+ -- if there's no index or it doesn't match, look for video
226+ for i = 1, #json.entries do
227+ if json.entries[i] == args["v"] then
228+ msg.debug("found requested video in index " .. (i - 1))
229+ return i - 1
230+ end
231+ end
232+
233+ msg.debug("requested video not found in playlist")
234+ -- if item isn't on the playlist, give up
235+ return nil
236+end
237+
238+local function make_absolute_url(base_url, url)
239+ if url:find("https?://") == 1 then return url end
240+
241+ local proto, domain, rest =
242+ base_url:match("(https?://)([^/]+/)(.*)/?")
243+ local segs = {}
244+ rest:gsub("([^/]+)", function(c) table.insert(segs, c) end)
245+ url:gsub("([^/]+)", function(c) table.insert(segs, c) end)
246+ local resolved_url = {}
247+ for i, v in ipairs(segs) do
248+ if v == ".." then
249+ table.remove(resolved_url)
250+ elseif v ~= "." then
251+ table.insert(resolved_url, v)
252+ end
253+ end
254+ return proto .. domain ..
255+ table.concat(resolved_url, "/")
256+end
257+
258+local function join_url(base_url, fragment)
259+ local res = ""
260+ if base_url and fragment.path then
261+ res = make_absolute_url(base_url, fragment.path)
262+ elseif fragment.url then
263+ res = fragment.url
264+ end
265+ return res
266+end
267+
268+local function edl_track_joined(fragments, protocol, is_live, base)
269+ if not (type(fragments) == "table") or not fragments[1] then
270+ msg.debug("No fragments to join into EDL")
271+ return nil
272+ end
273+
274+ local edl = "edl://"
275+ local offset = 1
276+ local parts = {}
277+
278+ if (protocol == "http_dash_segments") and not is_live then
279+ msg.debug("Using dash")
280+ local args = ""
281+
282+ -- assume MP4 DASH initialization segment
283+ if not fragments[1].duration then
284+ msg.debug("Using init segment")
285+ args = args .. ",init=" .. edl_escape(join_url(base, fragments[1]))
286+ offset = 2
287+ end
288+
289+ table.insert(parts, "!mp4_dash" .. args)
290+
291+ -- Check remaining fragments for duration;
292+ -- if not available in all, give up.
293+ for i = offset, #fragments do
294+ if not fragments[i].duration then
295+ msg.error("EDL doesn't support fragments" ..
296+ "without duration with MP4 DASH")
297+ return nil
298+ end
299+ end
300+ end
301+
302+ for i = offset, #fragments do
303+ local fragment = fragments[i]
304+ if not url_is_safe(join_url(base, fragment)) then
305+ return nil
306+ end
307+ table.insert(parts, edl_escape(join_url(base, fragment)))
308+ if fragment.duration then
309+ parts[#parts] =
310+ parts[#parts] .. ",length="..fragment.duration
311+ end
312+ end
313+ return edl .. table.concat(parts, ";") .. ";"
314+end
315+
316+local function has_native_dash_demuxer()
317+ local demuxers = mp.get_property_native("demuxer-lavf-list", {})
318+ for _, v in ipairs(demuxers) do
319+ if v == "dash" then
320+ return true
321+ end
322+ end
323+ return false
324+end
325+
326+local function valid_manifest(json)
327+ local reqfmt = json["requested_formats"] and json["requested_formats"][1] or {}
328+ if not reqfmt["manifest_url"] and not json["manifest_url"] then
329+ return false
330+ end
331+ local proto = reqfmt["protocol"] or json["protocol"] or ""
332+ return (proto == "http_dash_segments" and has_native_dash_demuxer()) or
333+ proto:find("^m3u8")
334+end
335+
336+local function as_integer(v, def)
337+ def = def or 0
338+ local num = math.floor(tonumber(v) or def)
339+ if num > -math.huge and num < math.huge then
340+ return num
341+ end
342+ return def
343+end
344+
345+-- Convert a format list from youtube-dl to an EDL URL, or plain URL.
346+-- json: full json blob by youtube-dl
347+-- formats: format list by youtube-dl
348+-- use_all_formats: if=true, then formats is the full format list, and the
349+-- function will attempt to return them as delay-loaded tracks
350+-- See res table initialization in the function for result type.
351+local function formats_to_edl(json, formats, use_all_formats)
352+ local res = {
353+ -- the media URL, which may be EDL
354+ url = nil,
355+ -- for use_all_formats=true: whether any muxed formats are present, and
356+ -- at the same time the separate EDL parts don't have both audio/video
357+ muxed_needed = false,
358+ }
359+
360+ local default_formats = {}
361+ local requested_formats = json["requested_formats"]
362+ if use_all_formats and requested_formats then
363+ for _, track in ipairs(requested_formats) do
364+ local id = track["format_id"]
365+ if id then
366+ default_formats[id] = true
367+ end
368+ end
369+ end
370+
371+ local duration = as_integer(json["duration"])
372+ local single_url = nil
373+ local streams = {}
374+
375+ local tbr_only = true
376+ for index, track in ipairs(formats) do
377+ tbr_only = tbr_only and track["tbr"] and
378+ (not track["abr"]) and (not track["vbr"])
379+ end
380+
381+ for index, track in ipairs(formats) do
382+ local edl_track = nil
383+ edl_track = edl_track_joined(track.fragments,
384+ track.protocol, json.is_live,
385+ track.fragment_base_url)
386+ if not edl_track and not url_is_safe(track.url) then
387+ return nil
388+ end
389+
390+ local tracks = {}
391+ if track.vcodec and track.vcodec ~= "none" then
392+ tracks[#tracks + 1] = {
393+ media_type = "video",
394+ codec = map_codec_to_mpv(track.vcodec),
395+ }
396+ end
397+ -- Tries to follow the strange logic that vcodec unset means it's
398+ -- an audio stream, even if acodec is sometimes unset.
399+ if (#tracks == 0) or (track.acodec and track.acodec ~= "none") then
400+ tracks[#tracks + 1] = {
401+ media_type = "audio",
402+ codec = map_codec_to_mpv(track.acodec) or
403+ ext_map[track.ext],
404+ }
405+ end
406+ if #tracks == 0 then
407+ return nil
408+ end
409+
410+ local url = edl_track or track.url
411+ local hdr = {"!new_stream", "!no_clip", "!no_chapters"}
412+ local skip = false
413+ local params = ""
414+
415+ if use_all_formats then
416+ for _, sub in ipairs(tracks) do
417+ -- A single track that is either audio or video. Delay load it.
418+ local props = ""
419+ if sub.media_type == "video" then
420+ props = props .. ",w=" .. as_integer(track.width)
421+ .. ",h=" .. as_integer(track.height)
422+ .. ",fps=" .. as_integer(track.fps)
423+ elseif sub.media_type == "audio" then
424+ props = props .. ",samplerate=" .. as_integer(track.asr)
425+ end
426+ hdr[#hdr + 1] = "!delay_open,media_type=" .. sub.media_type ..
427+ ",codec=" .. (sub.codec or "null") .. props
428+
429+ -- Add bitrate information etc. for better user selection.
430+ local byterate = 0
431+ local rates = {"tbr", "vbr", "abr"}
432+ if #tracks > 1 then
433+ rates = {({video = "vbr", audio = "abr"})[sub.media_type]}
434+ end
435+ if tbr_only then
436+ rates = {"tbr"}
437+ end
438+ for _, f in ipairs(rates) do
439+ local br = as_integer(track[f])
440+ if br > 0 then
441+ byterate = math.floor(br * 1000 / 8)
442+ break
443+ end
444+ end
445+ local title = track.format or track.format_note or ""
446+ if #tracks > 1 then
447+ if #title > 0 then
448+ title = title .. " "
449+ end
450+ title = title .. "muxed-" .. index
451+ end
452+ local flags = {}
453+ if default_formats[track["format_id"]] then
454+ flags[#flags + 1] = "default"
455+ end
456+ hdr[#hdr + 1] = "!track_meta,title=" ..
457+ edl_escape(title) .. ",byterate=" .. byterate ..
458+ iif(#flags > 0, ",flags=" .. table.concat(flags, "+"), "")
459+ end
460+
461+ if duration > 0 then
462+ params = params .. ",length=" .. duration
463+ end
464+ end
465+
466+ hdr[#hdr + 1] = edl_escape(url) .. params
467+
468+ streams[#streams + 1] = table.concat(hdr, ";")
469+ -- In case there is only 1 of these streams.
470+ -- Note: assumes it has no important EDL headers
471+ single_url = url
472+ end
473+
474+ -- Merge all tracks into a single virtual file, but avoid EDL if it's
475+ -- only a single track (i.e. redundant).
476+ if #streams == 1 and single_url then
477+ res.url = single_url
478+ elseif #streams > 0 then
479+ res.url = "edl://" .. table.concat(streams, ";")
480+ else
481+ return nil
482+ end
483+
484+ return res
485+end
486+
487+local function add_single_video(json)
488+ local streamurl = ""
489+ local format_info = ""
490+ local max_bitrate = 0
491+ local requested_formats = json["requested_formats"]
492+ local all_formats = json["formats"]
493+
494+ if o.use_manifests and valid_manifest(json) then
495+ -- prefer manifest_url if present
496+ format_info = "manifest"
497+
498+ local mpd_url = requested_formats and
499+ requested_formats[1]["manifest_url"] or json["manifest_url"]
500+ if not mpd_url then
501+ msg.error("No manifest URL found in JSON data.")
502+ return
503+ elseif not url_is_safe(mpd_url) then
504+ return
505+ end
506+
507+ streamurl = mpd_url
508+
509+ if requested_formats then
510+ for _, track in pairs(requested_formats) do
511+ max_bitrate = (track.tbr and track.tbr > max_bitrate) and
512+ track.tbr or max_bitrate
513+ end
514+ elseif json.tbr then
515+ max_bitrate = json.tbr > max_bitrate and json.tbr or max_bitrate
516+ end
517+ end
518+
519+ if streamurl == "" then
520+ -- possibly DASH/split tracks
521+ local res = nil
522+ local has_requested_formats = requested_formats and #requested_formats > 0
523+
524+ -- Not having requested_formats usually hints to HLS master playlist
525+ -- usage, which we don't want to split off, at least not yet.
526+ if (all_formats and o.all_formats) and
527+ (has_requested_formats or o.force_all_formats)
528+ then
529+ format_info = "all_formats (separate)"
530+ res = formats_to_edl(json, all_formats, true)
531+ -- Note: since we don't delay-load muxed streams, use normal stream
532+ -- selection if we have to use muxed streams.
533+ if res and res.muxed_needed then
534+ res = nil
535+ end
536+ end
537+
538+ if (not res) and has_requested_formats then
539+ format_info = "youtube-dl (separate)"
540+ res = formats_to_edl(json, requested_formats, false)
541+ end
542+
543+ if res then
544+ streamurl = res.url
545+ end
546+ end
547+
548+ if streamurl == "" and json.url then
549+ format_info = "youtube-dl (single)"
550+ local edl_track = nil
551+ edl_track = edl_track_joined(json.fragments, json.protocol,
552+ json.is_live, json.fragment_base_url)
553+
554+ if not edl_track and not url_is_safe(json.url) then
555+ return
556+ end
557+ -- normal video or single track
558+ streamurl = edl_track or json.url
559+ set_http_headers(json.http_headers)
560+ end
561+
562+ if streamurl == "" then
563+ msg.error("No URL found in JSON data.")
564+ return
565+ end
566+
567+ msg.verbose("format selection: " .. format_info)
568+ msg.debug("streamurl: " .. streamurl)
569+
570+ mp.set_property("stream-open-filename", streamurl:gsub("^data:", "data://", 1))
571+
572+ mp.set_property("file-local-options/force-media-title", json.title)
573+
574+ -- set hls-bitrate for dash track selection
575+ if max_bitrate > 0 and
576+ not option_was_set("hls-bitrate") and
577+ not option_was_set_locally("hls-bitrate") then
578+ mp.set_property_native('file-local-options/hls-bitrate', max_bitrate*1000)
579+ end
580+
581+ -- add subtitles
582+ if not (json.requested_subtitles == nil) then
583+ local subs = {}
584+ for lang, info in pairs(json.requested_subtitles) do
585+ subs[#subs + 1] = {lang = lang or "-", info = info}
586+ end
587+ table.sort(subs, function(a, b) return a.lang < b.lang end)
588+ for _, e in ipairs(subs) do
589+ local lang, sub_info = e.lang, e.info
590+ msg.verbose("adding subtitle ["..lang.."]")
591+
592+ local sub = nil
593+
594+ if not (sub_info.data == nil) then
595+ sub = "memory://"..sub_info.data
596+ elseif not (sub_info.url == nil) and
597+ url_is_safe(sub_info.url) then
598+ sub = sub_info.url
599+ end
600+
601+ if not (sub == nil) then
602+ local edl = "edl://!no_clip;!delay_open,media_type=sub"
603+ local codec = map_codec_to_mpv(sub_info.ext)
604+ if codec then
605+ edl = edl .. ",codec=" .. codec
606+ end
607+ edl = edl .. ";" .. edl_escape(sub)
608+ mp.commandv("sub-add", edl, "auto", sub_info.ext, lang)
609+ else
610+ msg.verbose("No subtitle data/url for ["..lang.."]")
611+ end
612+ end
613+ end
614+
615+ -- add chapters
616+ if json.chapters then
617+ msg.debug("Adding pre-parsed chapters")
618+ for i = 1, #json.chapters do
619+ local chapter = json.chapters[i]
620+ local title = chapter.title or ""
621+ if title == "" then
622+ title = string.format('Chapter %02d', i)
623+ end
624+ table.insert(chapter_list, {time=chapter.start_time, title=title})
625+ end
626+ elseif not (json.description == nil) and not (json.duration == nil) then
627+ chapter_list = extract_chapters(json.description, json.duration)
628+ end
629+
630+ -- set start time
631+ if not (json.start_time == nil) and
632+ not option_was_set("start") and
633+ not option_was_set_locally("start") then
634+ msg.debug("Setting start to: " .. json.start_time .. " secs")
635+ mp.set_property("file-local-options/start", json.start_time)
636+ end
637+
638+ -- set aspect ratio for anamorphic video
639+ if not (json.stretched_ratio == nil) and
640+ not option_was_set("video-aspect-override") then
641+ mp.set_property('file-local-options/video-aspect-override', json.stretched_ratio)
642+ end
643+
644+ local stream_opts = mp.get_property_native("file-local-options/stream-lavf-o", {})
645+
646+ -- for rtmp
647+ if (json.protocol == "rtmp") then
648+ stream_opts = append_libav_opt(stream_opts,
649+ "rtmp_tcurl", streamurl)
650+ stream_opts = append_libav_opt(stream_opts,
651+ "rtmp_pageurl", json.page_url)
652+ stream_opts = append_libav_opt(stream_opts,
653+ "rtmp_playpath", json.play_path)
654+ stream_opts = append_libav_opt(stream_opts,
655+ "rtmp_swfverify", json.player_url)
656+ stream_opts = append_libav_opt(stream_opts,
657+ "rtmp_swfurl", json.player_url)
658+ stream_opts = append_libav_opt(stream_opts,
659+ "rtmp_app", json.app)
660+ end
661+
662+ if json.proxy and json.proxy ~= "" then
663+ stream_opts = append_libav_opt(stream_opts,
664+ "http_proxy", json.proxy)
665+ end
666+
667+ mp.set_property_native("file-local-options/stream-lavf-o", stream_opts)
668+end
669+
670+local function check_version(ytdl_path)
671+ local command = {
672+ name = "subprocess",
673+ capture_stdout = true,
674+ args = {ytdl_path, "--version"}
675+ }
676+ local version_string = mp.command_native(command).stdout
677+ local year, month, day = string.match(version_string, "(%d+).(%d+).(%d+)")
678+
679+ -- sanity check
680+ if (tonumber(year) < 2000) or (tonumber(month) > 12) or
681+ (tonumber(day) > 31) then
682+ return
683+ end
684+ local version_ts = os.time{year=year, month=month, day=day}
685+ if (os.difftime(os.time(), version_ts) > 60*60*24*90) then
686+ msg.warn("It appears that your youtube-dl version is severely out of date.")
687+ end
688+end
689+
690+function run_ytdl_hook(url)
691+ local start_time = os.clock()
692+
693+ -- check for youtube-dl in mpv's config dir
694+ if not (ytdl.searched) then
695+ local exesuf = (package.config:sub(1,1) == '\\') and '.exe' or ''
696+ local ytdl_mcd = mp.find_config_file(o.ytdl_path .. exesuf)
697+ if ytdl_mcd == nil then
698+ msg.verbose("No youtube-dl found with path "..o.ytdl_path..exesuf.." in config directories")
699+ ytdl.path = o.ytdl_path
700+ else
701+ msg.verbose("found youtube-dl at: " .. ytdl_mcd)
702+ ytdl.path = ytdl_mcd
703+ end
704+ ytdl.searched = true
705+ end
706+
707+ -- strip ytdl://
708+ if (url:find("ytdl://") == 1) then
709+ url = url:sub(8)
710+ end
711+
712+ local format = mp.get_property("options/ytdl-format")
713+ local raw_options = mp.get_property_native("options/ytdl-raw-options")
714+ local allsubs = true
715+ local proxy = nil
716+ local use_playlist = false
717+
718+ local command = {
719+ ytdl.path, "--no-warnings", "-J", "--flat-playlist",
720+ "--sub-format", "ass/srt/best"
721+ }
722+
723+ -- Checks if video option is "no", change format accordingly,
724+ -- but only if user didn't explicitly set one
725+ if (mp.get_property("options/vid") == "no") and (#format == 0) then
726+ format = "bestaudio/best"
727+ msg.verbose("Video disabled. Only using audio")
728+ end
729+
730+ if (format == "") then
731+ format = "bestvideo+bestaudio/best"
732+ end
733+
734+ if format ~= "ytdl" then
735+ table.insert(command, "--format")
736+ table.insert(command, format)
737+ end
738+
739+ for param, arg in pairs(raw_options) do
740+ table.insert(command, "--" .. param)
741+ if (arg ~= "") then
742+ table.insert(command, arg)
743+ end
744+ if (param == "sub-lang") and (arg ~= "") then
745+ allsubs = false
746+ elseif (param == "proxy") and (arg ~= "") then
747+ proxy = arg
748+ elseif (param == "yes-playlist") then
749+ use_playlist = true
750+ end
751+ end
752+
753+ if (allsubs == true) then
754+ table.insert(command, "--all-subs")
755+ end
756+ if not use_playlist then
757+ table.insert(command, "--no-playlist")
758+ end
759+ table.insert(command, "--")
760+ table.insert(command, url)
761+ msg.debug("Running: " .. table.concat(command,' '))
762+ local es, json, result, aborted = exec(command)
763+
764+ if aborted then
765+ return
766+ end
767+
768+ if (es < 0) or (json == nil) or (json == "") then
769+ -- trim our stderr to avoid spurious newlines
770+ ytdl_err = result.stderr:gsub("^%s*(.-)%s*$", "%1")
771+ msg.error(ytdl_err)
772+ local err = "youtube-dl failed: "
773+ if result.error_string and result.error_string == "init" then
774+ err = err .. "not found or not enough permissions"
775+ elseif not result.killed_by_us then
776+ err = err .. "unexpected error occurred"
777+ else
778+ err = string.format("%s returned '%d'", err, es)
779+ end
780+ msg.error(err)
781+ if string.find(ytdl_err, "yt%-dl%.org/bug") then
782+ check_version(ytdl.path)
783+ end
784+ return
785+ end
786+
787+ local json, err = utils.parse_json(json)
788+
789+ if (json == nil) then
790+ msg.error("failed to parse JSON data: " .. err)
791+ check_version(ytdl.path)
792+ return
793+ end
794+
795+ msg.verbose("youtube-dl succeeded!")
796+ msg.debug('ytdl parsing took '..os.clock()-start_time..' seconds')
797+
798+ json["proxy"] = json["proxy"] or proxy
799+
800+ -- what did we get?
801+ if json["direct"] then
802+ -- direct URL, nothing to do
803+ msg.verbose("Got direct URL")
804+ return
805+ elseif (json["_type"] == "playlist")
806+ or (json["_type"] == "multi_video") then
807+ -- a playlist
808+
809+ if (#json.entries == 0) then
810+ msg.warn("Got empty playlist, nothing to play.")
811+ return
812+ end
813+
814+ local self_redirecting_url =
815+ json.entries[1]["_type"] ~= "url_transparent" and
816+ json.entries[1]["webpage_url"] and
817+ json.entries[1]["webpage_url"] == json["webpage_url"]
818+
819+
820+ -- some funky guessing to detect multi-arc videos
821+ if self_redirecting_url and #json.entries > 1
822+ and json.entries[1].protocol == "m3u8_native"
823+ and json.entries[1].url then
824+ msg.verbose("multi-arc video detected, building EDL")
825+
826+ local playlist = edl_track_joined(json.entries)
827+
828+ msg.debug("EDL: " .. playlist)
829+
830+ if not playlist then
831+ return
832+ end
833+
834+ -- can't change the http headers for each entry, so use the 1st
835+ set_http_headers(json.entries[1].http_headers)
836+
837+ mp.set_property("stream-open-filename", playlist)
838+ if not (json.title == nil) then
839+ mp.set_property("file-local-options/force-media-title",
840+ json.title)
841+ end
842+
843+ -- there might not be subs for the first segment
844+ local entry_wsubs = nil
845+ for i, entry in pairs(json.entries) do
846+ if not (entry.requested_subtitles == nil) then
847+ entry_wsubs = i
848+ break
849+ end
850+ end
851+
852+ if not (entry_wsubs == nil) and
853+ not (json.entries[entry_wsubs].duration == nil) then
854+ for j, req in pairs(json.entries[entry_wsubs].requested_subtitles) do
855+ local subfile = "edl://"
856+ for i, entry in pairs(json.entries) do
857+ if not (entry.requested_subtitles == nil) and
858+ not (entry.requested_subtitles[j] == nil) and
859+ url_is_safe(entry.requested_subtitles[j].url) then
860+ subfile = subfile..edl_escape(entry.requested_subtitles[j].url)
861+ else
862+ subfile = subfile..edl_escape("memory://WEBVTT")
863+ end
864+ subfile = subfile..",length="..entry.duration..";"
865+ end
866+ msg.debug(j.." sub EDL: "..subfile)
867+ mp.commandv("sub-add", subfile, "auto", req.ext, j)
868+ end
869+ end
870+
871+ elseif self_redirecting_url and #json.entries == 1 then
872+ msg.verbose("Playlist with single entry detected.")
873+ add_single_video(json.entries[1])
874+ else
875+ local playlist_index = parse_yt_playlist(url, json)
876+ local playlist = {"#EXTM3U"}
877+ for i, entry in pairs(json.entries) do
878+ local site = entry.url
879+ local title = entry.title
880+
881+ if not (title == nil) then
882+ title = string.gsub(title, '%s+', ' ')
883+ table.insert(playlist, "#EXTINF:0," .. title)
884+ end
885+
886+ --[[ some extractors will still return the full info for
887+ all clips in the playlist and the URL will point
888+ directly to the file in that case, which we don't
889+ want so get the webpage URL instead, which is what
890+ we want, but only if we aren't going to trigger an
891+ infinite loop
892+ --]]
893+ if entry["webpage_url"] and not self_redirecting_url then
894+ site = entry["webpage_url"]
895+ end
896+
897+ -- links without protocol as returned by --flat-playlist
898+ if not site:find("://") then
899+ -- youtube extractor provides only IDs,
900+ -- others come prefixed with the extractor name and ":"
901+ local prefix = site:find(":") and "ytdl://" or
902+ "https://youtu.be/"
903+ table.insert(playlist, prefix .. site)
904+ elseif url_is_safe(site) then
905+ table.insert(playlist, site)
906+ end
907+
908+ end
909+
910+ if use_playlist and
911+ not option_was_set("playlist-start") and playlist_index then
912+ mp.set_property_number("playlist-start", playlist_index)
913+ end
914+
915+ mp.set_property("stream-open-filename", "memory://" .. table.concat(playlist, "\n"))
916+ end
917+
918+ else -- probably a video
919+ add_single_video(json)
920+ end
921+ msg.debug('script running time: '..os.clock()-start_time..' seconds')
922+end
923+
924+if (not o.try_ytdl_first) then
925+ mp.add_hook("on_load", 10, function ()
926+ msg.verbose('ytdl:// hook')
927+ local url = mp.get_property("stream-open-filename", "")
928+ if not (url:find("ytdl://") == 1) then
929+ msg.verbose('not a ytdl:// url')
930+ return
931+ end
932+ run_ytdl_hook(url)
933+ end)
934+end
935+
936+mp.add_hook(o.try_ytdl_first and "on_load" or "on_load_fail", 10, function()
937+ msg.verbose('full hook')
938+ local url = mp.get_property("stream-open-filename", "")
939+ if not (url:find("ytdl://") == 1) and
940+ not ((url:find("https?://") == 1) and not is_blacklisted(url)) then
941+ return
942+ end
943+ run_ytdl_hook(url)
944+end)
945+
946+mp.add_hook("on_preloaded", 10, function ()
947+ if next(chapter_list) ~= nil then
948+ msg.verbose("Setting chapters")
949+
950+ mp.set_property_native("chapter-list", chapter_list)
951+ chapter_list = {}
952+ end
953+end)
A · coc/extensions/db.json
+1, -01@@ -0,0 +1 @@
2+{}
A · coc/extensions/package.json
+1, -01@@ -0,0 +1 @@
2+{"dependencies":{}}
A · coc/memos.json
+1, -01@@ -0,0 +1 @@
2+{}
M · gtk-3.0/bookmarks
+7, -2 1@@ -1,5 +1,10 @@
2 file:///home/arjun/.config
3-file:///home/arjun/Media
4-file:///home/arjun/Notes
5 file:///home/arjun/Projects
6+file:///home/arjun/Notes
7+file:///home/arjun/Music
8 file:///home/arjun/Executables
9+file:///home/arjun/Dots
10+file:///home/arjun/Books
11+file:///home/arjun/Videos
12+file:///home/arjun/Pictures
13+file:///home/arjun/Downloads
A · gtk-3.0/gtk.css
+0, -0
M · gtk-3.0/settings.ini
+2, -1 1@@ -1,7 +1,9 @@
2 [Settings]
3+gtk-application-prefer-dark-theme=0
4 gtk-theme-name=Materia-dark-compact
5 gtk-icon-theme-name=Papirus-Dark
6 gtk-font-name=Inter 11
7+gtk-cursor-theme-name=Adwaita
8 gtk-cursor-theme-size=0
9 gtk-toolbar-style=GTK_TOOLBAR_BOTH
10 gtk-toolbar-icon-size=GTK_ICON_SIZE_LARGE_TOOLBAR
11@@ -12,5 +14,4 @@ gtk-enable-input-feedback-sounds=1
12 gtk-xft-antialias=1
13 gtk-xft-hinting=1
14 gtk-xft-hintstyle=hintslight
15-gtk-cursor-theme-name=xcursor-breeze-snow
16 gtk-xft-rgba=rgb
A · gtk-4.0/settings.ini
+2, -01@@ -0,0 +1,2 @@
2+[Settings]
3+gtk-application-prefer-dark-theme=0
M · qt5ct/qt5ct.conf
+6, -6 1@@ -1,9 +1,9 @@
2 [Appearance]
3-color_scheme_path=/usr/share/qt5ct/colors/darker.conf
4-custom_palette=true
5+color_scheme_path=/usr/share/qt5ct/colors/simple.conf
6+custom_palette=false
7 icon_theme=Papirus-Dark
8-standard_dialogs=gtk2
9-style=gtk2
10+standard_dialogs=default
11+style=kvantum-dark
12
13 [Fonts]
14 fixed=@Variant(\0\0\0@\0\0\0\x1c\0J\0\x65\0t\0\x42\0r\0\x61\0i\0n\0s\0 \0M\0o\0n\0o@&\0\0\0\0\0\0\xff\xff\xff\xff\x5\x1\0\x32\x10)
15@@ -12,7 +12,7 @@ general=@Variant(\0\0\0@\0\0\0\n\0I\0n\0t\0\x65\0r@&\0\0\0\0\0\0\xff\xff\xff\xff
16 [Interface]
17 activate_item_on_single_click=1
18 buttonbox_layout=2
19-cursor_flash_time=1000
20+cursor_flash_time=1200
21 dialog_buttons_have_icons=1
22 double_click_interval=400
23 gui_effects=@Invalid()
24@@ -28,4 +28,4 @@ wheel_scroll_lines=3
25 geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\x2i\0\0\x1/\0\0\x4\xef\0\0\x3%\0\0\x2k\0\0\x1\x31\0\0\x4\xed\0\0\x3#\0\0\0\0\0\0\0\0\a\x80\0\0\x2k\0\0\x1\x31\0\0\x4\xed\0\0\x3#)
26
27 [SettingsWindow]
28-geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\x3\xc5\0\0\0-\0\0\au\0\0\x4-\0\0\x3\xc7\0\0\0/\0\0\as\0\0\x4+\0\0\0\0\0\0\0\0\a\x80\0\0\x3\xc7\0\0\0/\0\0\as\0\0\x4+)
29+geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\x1\xe8\0\0\0\xdb\0\0\x5\x9e\0\0\x4R\0\0\x1\xea\0\0\0\xfb\0\0\x5\x9c\0\0\x4P\0\0\0\0\0\0\0\0\a\x80\0\0\x1\xea\0\0\0\xfb\0\0\x5\x9c\0\0\x4P)
D · starship.toml
+0, -81@@ -1,8 +0,0 @@
2-add_newline = false
3-
4-[character]
5-success_symbol = "[](bold green)"
6-error_symbol = "[](bold red)"
7-[package]
8-disabled = true
9-