legacy-dots

updated & added README

Arjun Choudhary arjun99c@gmail.com

commit: cfd9846 parent: aa74e38
13 files changed, 986 insertions(+), 19 deletions(-)
AREADME.md+11-0
Malacritty/alacritty.yml+1-1
Mbspwm/bspwmrc+2-1
Acelluloid/scripts/ytdl_hook.lua+952-0
Acoc/extensions/db.json+1-0
Acoc/extensions/package.json+1-0
Acoc/memos.json+1-0
Mgtk-3.0/bookmarks+7-2
Agtk-3.0/gtk.css+0-0
Mgtk-3.0/settings.ini+2-1
Agtk-4.0/settings.ini+2-0
Mqt5ct/qt5ct.conf+6-6
Dstarship.toml+0-8
A · README.md +11, -0
 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, -1
1@@ -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, -1
1@@ -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, -0
1@@ -0,0 +1 @@
2+{}
A · coc/extensions/package.json +1, -0
1@@ -0,0 +1 @@
2+{"dependencies":{}}
A · coc/memos.json +1, -0
1@@ -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, -0
1@@ -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, -8
1@@ -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-