Skip to content

Instantly share code, notes, and snippets.

@stefansundin
Last active November 3, 2024 00:06
Show Gist options
  • Save stefansundin/c200324149bb00001fef5a252a120fc2 to your computer and use it in GitHub Desktop.
Save stefansundin/c200324149bb00001fef5a252a120fc2 to your computer and use it in GitHub Desktop.
VLC playlist parser for Twitch.tv - https://addons.videolan.org/p/1167220/
--[[
Twitch.tv extension v0.0.2 by Stefan Sundin
https://gist.github.com/stefansundin/c200324149bb00001fef5a252a120fc2
The only thing that this extension does is to act as a helper to seek to the
correct time when you open a twitch.tv url that contains a timestamp.
You must have the playlist parser installed as well!
Usage:
1. Install the playlist parser: https://addons.videolan.org/p/1167220/
2. Install this file in the lua/extensions/ directory:
- On Windows: %APPDATA%/vlc/lua/extensions/
- On Mac: $HOME/Library/Application Support/org.videolan.vlc/lua/extensions/
- On Linux: ~/.local/share/vlc/lua/extensions/
- On Linux (snap package): ~/snap/vlc/current/.local/share/vlc/lua/extensions/
To install the addon for all users, put the file here instead:
- On Windows: C:/Program Files/VideoLAN/VLC/lua/extensions/
- On Mac: /Applications/VLC.app/Contents/MacOS/share/lua/extensions/
- On Linux: /usr/lib/vlc/lua/extensions/
- On Linux (snap package): /snap/vlc/current/usr/lib/vlc/lua/extensions/
3. Open VLC and activate the extension via the menu.
NOTE: You must do this every time you restart VLC, as far as I know there is
no way for the extension to activate itself. :(
There is no feedback indicating that the extension was activated.
4. Open a twitch.tv url with a timestamp using "Open Network Stream..."
Download and install with one command on Mac:
curl -o "$HOME/Library/Application Support/org.videolan.vlc/lua/extensions/twitch-extension.lua" https://gist.githubusercontent.com/stefansundin/c200324149bb00001fef5a252a120fc2/raw/twitch-extension.lua
Download and install with one command on Linux:
curl -o ~/.local/share/vlc/lua/extensions/twitch-extension.lua https://gist.githubusercontent.com/stefansundin/c200324149bb00001fef5a252a120fc2/raw/twitch-extension.lua
Example url:
https://www.twitch.tv/gamesdonequick/video/113837699?t=23m56s
https://www.twitch.tv/videos/113837699?t=23m56s
https://player.twitch.tv/?video=v113837699&time=23m56s
Changelog:
- v0.0.2: Support new go.twitch.tv urls (beta site).
- v0.0.1: First version of extension.
--]]
require 'common'
function descriptor()
return {
title = "Twitch.tv Extension v0.0.2",
shortdesc = "Twitch.tv Extension v0.0.2",
version = "v0.0.2",
author = "Stefan Sundin",
url = "https://gist.github.com/stefansundin/c200324149bb00001fef5a252a120fc2",
description = "This extension is needed to support jumping to a twitch.tv timestamp indicated by ?t= in the URL. VLC extensions must be activated each time VLC is run. This is unfortunate and I have not found any workaround.",
capabilities = { "input-listener" }
}
end
function activate()
check_meta()
end
function input_changed()
check_meta()
end
-- The extension does not work if I name this string.starts, so weird..
function stringstarts(haystack, needle)
return string.sub(haystack, 1, string.len(needle)) == needle
end
function check_meta()
if vlc.input.is_playing() then
local item = vlc.item or vlc.input.item()
if item then
local meta = item:metas()
if meta and meta["url"] then
vlc.msg.info("Trying to parse t from: "..meta["url"])
if (stringstarts(meta["url"], "https://www.twitch.tv/") or stringstarts(meta["url"], "https://go.twitch.tv/") or stringstarts(meta["url"], "https://player.twitch.tv/")) and (string.find(meta["url"], "[?&]t=") or string.find(meta["url"], "[?&]time=")) then
local t = string.match(meta["url"], "[?&]t=([^&#]+)") or string.match(meta["url"], "[?&]time=([^&#]+)")
vlc.msg.info("t="..t)
local start = 0
local h = string.match(t, "(%d+)h")
if h then
-- vlc.msg.info("h: "..h)
start = start + tonumber(h)*3600
end
local m = string.match(t, "(%d+)m")
if m then
-- vlc.msg.info("m: "..m)
start = start + tonumber(m)*60
end
local s = string.match(t, "(%d+)s")
if s then
-- vlc.msg.info("s: "..s)
start = start + tonumber(s)
end
vlc.msg.info("Seeking to: "..start)
common.seek(start)
end
end
end
return true
end
end
--[[
Twitch.tv playlist parser v0.3.1 by Stefan Sundin
https://gist.github.com/stefansundin/c200324149bb00001fef5a252a120fc2
https://www.opencode.net/stefansundin/twitch.lua
https://addons.videolan.org/p/1167220/
Once installed, you can open a twitch.tv url using "Open Network Stream..."
Features:
- Load a channel and watch live: https://www.twitch.tv/speedgaming
- Load an archived video: https://www.twitch.tv/videos/113837699
- Load a clip: https://clips.twitch.tv/AmazonianKnottyLapwingSwiftRage
This playlist parser utilizes a proxy since v0.3.0 since Twitch completely shut down their old API.
VLC can't make POST requests so there isn't another way to work around the problem.
For information about the proxy please go to https://github.com/stefansundin/media-resolver
You can run the proxy on your own computer but you'll have to edit the proxy_host variable below.
Installation:
Put the file in the lua/playlist/ directory:
- On Windows: %APPDATA%/vlc/lua/playlist/
- On Mac: $HOME/Library/Application Support/org.videolan.vlc/lua/playlist/
- On Linux: ~/.local/share/vlc/lua/playlist/
- On Linux (snap package): ~/snap/vlc/current/.local/share/vlc/lua/playlist/
To install the addon for all users, put the file here instead:
- On Windows: C:/Program Files/VideoLAN/VLC/lua/playlist/
- On Mac: /Applications/VLC.app/Contents/MacOS/share/lua/playlist/
- On Linux: /usr/lib/vlc/lua/playlist/
- On Linux (snap package): /snap/vlc/current/usr/lib/vlc/lua/playlist/
On Linux, you can download and install with the following command:
mkdir -p ~/.local/share/vlc/lua/playlist/; curl -o ~/.local/share/vlc/lua/playlist/twitch.lua https://gist.githubusercontent.com/stefansundin/c200324149bb00001fef5a252a120fc2/raw/twitch.lua
On Mac, you can download and install with the following command:
mkdir -p "$HOME/Library/Application Support/org.videolan.vlc/lua/playlist/"; curl -o "$HOME/Library/Application Support/org.videolan.vlc/lua/playlist/twitch.lua" https://gist.githubusercontent.com/stefansundin/c200324149bb00001fef5a252a120fc2/raw/twitch.lua
Changelog:
- v0.3.1: Added back support for clips.
- v0.3.0: Twitch finally completely shut down their old API. Because VLC scripts can't easily make POST requests it currently requires a proxy to work. To simplify things I removed all features besides loading a live channel and archived videos. I could potentially add back some of the features in the future but I am unsure if I have the will to do it.
- v0.2.3: Fix loading the list of a channel's old videos. Remove support for clips as it is broken and not easy to fix.
- v0.2.2: Fix 1080p on archived videos. Add audio only stream.
- v0.2.1: Skip live videos when loading /<channel>/videos.
- v0.2.0: Support new URLs. Most things seem to be working again.
- v0.1.3: Minor fix that prevented me from running this on Ubuntu 18.04 (snap package).
- v0.1.2: Support for /directory/game/<name>/videos/<type>.
- v0.1.1: Support for /<channel>/clips, /directory/game/<name>/clips. Add ability to load the next page.
- v0.1.0: Rewrote almost the whole thing. Support for /communities/<name>, /directory/game/<name>, /<channel>/videos/, collections.
- v0.0.6: Support new go.twitch.tv urls (beta site).
- v0.0.5: Fix a couple of minor issues.
- v0.0.4: Support new twitch.tv/videos/ urls.
- v0.0.3: Support for Twitch Clips.
- v0.0.2: You can now pick the stream quality you want. The twitch URL will expand to multiple playlist items.
Handy references:
https://github.com/videolan/vlc/blob/master/share/lua/README.txt
https://github.com/videolan/vlc/blob/7f6786ab6c8fb624726a63f07d79c23892827dfb/share/lua/playlist/appletrailers.lua#L34
--]]
-- Set this variable to true to only add the first stream:
local only_add_one_stream = false
-- You can change the order of the streams using this variable:
local preferred_streams = {}
-- Examples:
-- local preferred_streams = { "1080p", "720p" }
-- local preferred_streams = { "360p", "160p", "720p60", "720p" }
-- local preferred_streams = { "Audio Only" }
--
-- Technical note:
-- The sort algorithm is not stable. This means that streams not listed here may become jumbled by the sorting (the sorting only happens if this list has entries in it).
-- You can modify this variable to only include certain streams:
local allowed_streams = {}
-- Examples:
-- local allowed_streams = { "1080p" }
-- local allowed_streams = { "360p", "720p" }
-- local allowed_streams = { "360p" }
-- It does a substring match, so you can use "1080p" to allow both "1080p" and "1080p60".
-- You can modify this variable to skip certain streams:
local skipped_streams = {}
-- Examples:
-- local skipped_streams = { "1080p", "Audio Only" }
-- It does a substring match, so you can use "1080p" to block both "1080p" and "1080p60".
function parse_json(str)
vlc.msg.dbg("Parsing JSON: " .. str)
local json = require("dkjson")
return json.decode(str)
end
function get_json(url)
vlc.msg.info("Getting JSON from " .. url)
local stream = vlc.stream(url)
local data = ""
local line = ""
if not stream then return false end
while true do
line = stream:readline()
if not line then break end
data = data .. line
end
return parse_json(data)
end
function get_streams(item)
vlc.msg.info("Getting streams from " .. item.path)
-- #EXTM3U
-- #EXT-X-TWITCH-INFO:NODE="video-edge-c9010c.lax03",MANIFEST-NODE-TYPE="legacy",MANIFEST-NODE="video-edge-c9010c.lax03",SUPPRESS="false",SERVER-TIME="1483827093.91",USER-IP="76.94.205.190",SERVING-ID="4529b3c0570a46c8b3ed902f68b8368f",CLUSTER="lax03",ABS="false",BROADCAST-ID="24170411392",STREAM-TIME="5819.9121151",MANIFEST-CLUSTER="lax03"
-- #EXT-X-MEDIA:TYPE=VIDEO,GROUP-ID="chunked",NAME="Source",AUTOSELECT=YES,DEFAULT=YES
-- #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2838000,RESOLUTION=1280x720,VIDEO="chunked"
local stream = vlc.stream(item.path)
if not stream then
return { { path="", name="Error fetching streams" } }
end
local items = {}
local name = "error"
local add_stream = true
-- local resolution = "error"
while true do
local line = stream:readline()
if not line then break end
if string.find(line, "^#.*NAME=") then
name = string.match(line, "NAME=\"?([a-zA-Z0-9_ \\(\\)]+)\"?")
if name == "audio_only" then
name = "Audio Only"
end
add_stream = true
if #allowed_streams > 0 then
add_stream = false
for _, stream in ipairs(allowed_streams) do
if string.find(name, stream) then
add_stream = true
end
end
end
for _, stream in ipairs(skipped_streams) do
if string.find(name, stream) then
add_stream = false
end
end
-- elseif string.find(line, "^#.*RESOLUTION=") then
-- resolution = string.match(line, "RESOLUTION=\"?([0-9x]+)\"?")
elseif string.find(line, "^http") and add_stream then
-- Make a copy and overwrite some attributes
local new_item = {}
for k, v in pairs(item) do
new_item[k] = v
end
new_item.path = line
new_item.name = item.name.." ["..name.."]"
table.insert(items, new_item)
if only_add_one_stream then
break
end
end
end
if #preferred_streams > 0 then
table.sort(items, sort_streams)
end
return items
end
function url_encode(str)
str = string.gsub(str, "\n", "\r\n")
str = string.gsub(str, "([^%w %-%_%.%~])", function(c) return string.format("%%%02X", string.byte(c)) end)
str = string.gsub(str, " ", "+")
return str
end
function proxy_resolve(url)
-- Don't just copy this URL to your own app without understanding it.
-- Do NOT use the "v=1" query parameter if you use this in another app!
local proxy_host = "https://media-resolver.fly.dev"
-- If you are running your own media resolver then comment out the line above and uncomment the line below:
-- local proxy_host = "http://localhost:8080"
return proxy_host.."/resolve?v=1&output=json&url="..url_encode(url)
end
function string.starts(haystack, needle)
return string.sub(haystack, 1, string.len(needle)) == needle
end
function sort_streams(a, b)
local i = table.index(preferred_streams, a.name)
local j = table.index(preferred_streams, b.name)
if i == nil and j == nil then
return false
elseif i ~= nil and j == nil then
return true
elseif i == nil and j ~= nil then
return false
end
return i < j
end
function table.index(t, x)
for i, v in pairs(t) do
if string.find(x, v) then
return i
end
end
return nil
end
function probe()
return (vlc.access == "http" or vlc.access == "https") and (string.starts(vlc.path, "www.twitch.tv/") or string.starts(vlc.path, "go.twitch.tv/") or string.starts(vlc.path, "player.twitch.tv/") or string.starts(vlc.path, "clips.twitch.tv/"))
end
function parse()
local url = vlc.access.."://"..vlc.path
local data = get_json(proxy_resolve(url))
if data.error then
return { { path="", name="Error: "..data.error } }
end
local items = {}
for _, item in ipairs(data) do
if string.find(item.path, ".m3u8") then
-- We resolve m3u8 files into quality options here because we won't get a chance to do it later
for _, stream in ipairs(get_streams(item)) do
table.insert(items, stream)
end
else
table.insert(items, item)
end
end
return items
end
URL=$(curl -sL https://www.twitch.tv/ | grep -oE 'https://static.twitchcdn.net/assets/core-[a-z0-9]+.js')
curl -sL "$URL" | grep -oE '"[a-z0-9]{30}"'
# old:
# find the client_id that is used on twitch.tv
# curl -sL https://web-cdn.ttvnw.net/global.js | grep -oP '"[a-z0-9]{31}"'
# on mac:
# curl -sL https://web-cdn.ttvnw.net/global.js | grep -oE '"[a-z0-9]{31}"'
@stefansundin
Copy link
Author

@Khomchyk1992 I have pushed some updates to the script to allow the resolution to be picked more easily. I have NOT released this on addons.videolan.org yet, so for now please download the twitch.lua file manually here.

You have to edit a few variables in the code. They are named only_add_one_stream, preferred_streams, allowed_streams, and skipped_streams. Hopefully they should let you do what you want.

Please write a comment here if they solve your problem, and if you have any feedback. Thanks.

@Khomchyk1992
Copy link

@Khomchyk1992 I have pushed some updates to the script to allow the resolution to be picked more easily. I have NOT released this on addons.videolan.org yet, so for now please download the twitch.lua file manually here.

You have to edit a few variables in the code. They are named only_add_one_stream, preferred_streams, allowed_streams, and skipped_streams. Hopefully they should let you do what you want.

Please write a comment here if they solve your problem, and if you have any feedback. Thanks.

Thank you very much, set your value, everything works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment