summaryrefslogtreecommitdiffstats
path: root/player/lua/ytdl_hook.lua
blob: f757e5a28b1f8d0e5f0b231b11c771271acb14ed (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
local utils = require 'mp.utils'
local msg = require 'mp.msg'

local ytdl = {
    path = "youtube-dl",
    minver = "2015.02.23.1",
    vercheck = nil,
}

local function exec(args)
    local ret = utils.subprocess({args = args})
    return ret.status, ret.stdout
end

-- return if it was explicitly set on the command line
local function option_was_set(name)
    return mp.get_property_bool("option-info/" .. name .. "/set-from-commandline",
                                false)
end

-- youtube-dl may set special http headers for some sites (user-agent, cookies)
local function set_http_headers(http_headers)
    if not http_headers then
        return
    end
    local headers = {}
    local useragent = http_headers["User-Agent"]
    if useragent and not option_was_set("user-agent") then
        mp.set_property("file-local-options/user-agent", useragent)
    end
    local cookies = http_headers["Cookie"]
    if cookies then
        headers[#headers + 1] = "Cookie: " .. cookies
    end
    if #headers > 0 and not option_was_set("http-header-fields") then
        mp.set_property_native("file-local-options/http-header-fields", headers)
    end
end

mp.add_hook("on_load", 10, function ()
    local url = mp.get_property("stream-open-filename")

    if (url:find("http://") == 1) or (url:find("https://") == 1)
        or (url:find("ytdl://") == 1) then

       -- check version of youtube-dl if not done yet
        if (ytdl.vercheck == nil) then

             -- check for youtube-dl in mpv's config dir
            local ytdl_mcd = mp.find_config_file("youtube-dl")
            if not (ytdl_mcd == nil) then
                msg.verbose("found youtube-dl at: " .. ytdl_mcd)
                ytdl.path = ytdl_mcd
            end

            msg.debug("checking ytdl version ...")
            local es, version = exec({ytdl.path, "--version"})
            if (es < 0) then
                msg.warn("youtube-dl not found, not executable, or broken.")
                ytdl.vercheck = false
            elseif (version < ytdl.minver) then
                msg.verbose("found youtube-dl version: " .. version)
                msg.warn("Your version of youtube-dl is too old! "
                    .. "You need at least version '"..ytdl.minver
                    .. "', try running `youtube-dl -U`.")
                ytdl.vercheck = false
            else
                msg.verbose("found youtube-dl version: " .. version)
                ytdl.vercheck = true
            end
        end

        if not (ytdl.vercheck) then
            return
        end

        -- strip ytdl://
        if (url:find("ytdl://") == 1) then
            url = url:sub(8)
        end

        local format = mp.get_property("options/ytdl-format")

        -- subformat workaround
        local subformat = "ass/srt/best"

        local command = {
            ytdl.path, "--no-warnings", "-J", "--flat-playlist", "--all-subs",
            "--sub-format", subformat, "--no-playlist"
        }
        if (format ~= "") then
            table.insert(command, "--format")
            table.insert(command, format)
        end
        table.insert(command, "--")
        table.insert(command, url)
        local es, json = exec(command)

        if (es < 0) or (json == nil) or (json == "") then
            msg.warn("youtube-dl failed, trying to play URL directly ...")
            return
        end

        local json, err = utils.parse_json(json)

        if (json == nil) then
            msg.error("failed to parse JSON data: " .. err)
            return
        end

        msg.verbose("youtube-dl succeeded!")

        -- what did we get?
        if not (json["direct"] == nil) and (json["direct"] == true) then
            -- direct URL, nothing to do
            msg.verbose("Got direct URL")
            return
        elseif not (json["_type"] == nil) and (json["_type"] == "playlist") then
            -- a playlist

            if (#json.entries == 0) then
                msg.warn("Got empty playlist, nothing to play.")
                return
            end


            -- some funky guessing to detect multi-arc videos
            if  not (json.entries[1]["webpage_url"] == nil)
                and (json.entries[1]["webpage_url"] == json["webpage_url"]) then
                msg.verbose("multi-arc video detected, building EDL")


                local playlist = "edl://"
                for i, entry in pairs(json.entries) do

                    playlist = playlist .. entry.url .. ";"
                end

                msg.debug("EDL: " .. playlist)

                -- can't change the http headers for each entry, so use the 1st
                if json.entries[1] then
                    set_http_headers(json.entries[1].http_headers)
                end

                mp.set_property("stream-open-filename", playlist)
                if not (json.title == nil) then
                    mp.set_property("file-local-options/media-title", json.title)
                end

            else

                local playlist = "#EXTM3U\n"
                for i, entry in pairs(json.entries) do
                    local site = entry.url

                    -- some extractors will still return the full info for
                    -- all clips in the playlist and the URL will point
                    -- directly to the file in that case, which we don't
                    -- want so get the webpage URL instead, which is what
                    -- we want
                    if not (entry["webpage_url"] == nil) then
                        site = entry["webpage_url"]
                    end

                    playlist = playlist .. "ytdl://" .. site .. "\n"
                end

                mp.set_property("stream-open-filename", "memory://" .. playlist)
            end

        else -- probably a video
            local streamurl = ""

            -- DASH?
            if not (json["requested_formats"] == nil) then
                msg.info("Using DASH, expect inaccurate duration.")
                if not (json.duration == nil) then
                    msg.info("Actual duration: " .. mp.format_time(json.duration))
                end

                -- video url
                streamurl = json["requested_formats"][1].url

                -- audio url
                mp.set_property("file-local-options/audio-file",
                    json["requested_formats"][2].url)

                -- workaround for slow startup (causes inaccurate duration)
                mp.set_property("file-local-options/demuxer-lavf-o",
                    "fflags=+ignidx")

            elseif not (json.url == nil) then
                -- normal video
                streamurl = json.url
                set_http_headers(json.http_headers)
            else
                msg.error("No URL found in JSON data.")
                return
            end

            msg.debug("streamurl: " .. streamurl)

            mp.set_property("stream-open-filename", streamurl)

            mp.set_property("file-local-options/media-title", json.title)

            -- add subtitles
            if not (json.requested_subtitles == nil) then
                for lang, sub_info in pairs(json.requested_subtitles) do
                    msg.verbose("adding subtitle ["..lang.."]")

                    local slang = lang
                    if (lang:len() > 3) then
                        slang = lang:sub(1,2)
                    end

                    if not (sub_info.data == nil) then
                        sub = "memory://"..sub_info.data
                    else
                        sub = sub_info.url
                    end
                    mp.commandv("sub_add", sub,
                        "auto", lang.." "..sub_info.ext, slang)
                end
            end

            -- for rtmp
            if not (json.play_path == nil) then
                mp.set_property("file-local-options/stream-lavf-o",
                    "rtmp_tcurl=\""..streamurl..
                    "\",rtmp_playpath=\""..json.play_path.."\"")
            end
        end
    end
end)