Last active
November 3, 2024 00:06
-
-
Save stefansundin/c200324149bb00001fef5a252a120fc2 to your computer and use it in GitHub Desktop.
VLC playlist parser for Twitch.tv - https://addons.videolan.org/p/1167220/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}"' |
@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
, andskipped_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
@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
, andskipped_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.