Compare commits
13 Commits
Author | SHA1 | Date |
---|---|---|
|
34e39eb3c1 | |
|
bce8b7ca8f | |
|
62565c10f2 | |
|
f340ef11af | |
|
7a27725d6d | |
|
f81cadd6d1 | |
|
8b6793c6a7 | |
|
eb59a03590 | |
|
3a497b8630 | |
|
42484060ae | |
|
705f7b63a2 | |
|
0e1d10fc0c | |
|
b15dc2be32 |
11
README.md
11
README.md
|
@ -1,4 +1,4 @@
|
||||||
# vlc-mediaklikk
|
# vlc-mediaklikk-video
|
||||||
VLC playlist parser for MédiaKlikk videos and video streams
|
VLC playlist parser for MédiaKlikk videos and video streams
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
@ -22,11 +22,14 @@ Copy [mediaklikk-video.lua](mediaklikk-video.lua) to [VLC Lua playlist scripts](
|
||||||
* [mediaklikk.hu](https://www.mediaklikk.hu/)
|
* [mediaklikk.hu](https://www.mediaklikk.hu/)
|
||||||
|
|
||||||
### Video streams
|
### Video streams
|
||||||
* [M1](https://www.mediaklikk.hu/m1-elo)
|
* [M1](https://hirado.hu/elo/m1)
|
||||||
* [M2](https://www.mediaklikk.hu/m2-elo)
|
* [M2](https://www.mediaklikk.hu/m2-elo)
|
||||||
* [M4](https://www.mediaklikk.hu/m4-elo)
|
* [M4 Sport](https://m4sport.hu/elo/mtv4live)
|
||||||
|
* [M4 Sport +](https://m4sport.hu/elo/mtv4plus)
|
||||||
* [M5](https://www.mediaklikk.hu/m5-elo)
|
* [M5](https://www.mediaklikk.hu/m5-elo)
|
||||||
* [Duna](https://www.mediaklikk.hu/duna-elo)
|
* [Duna](https://www.mediaklikk.hu/duna-elo)
|
||||||
* [Duna World](https://www.mediaklikk.hu/duna-world-elo)
|
* [Duna World](https://www.mediaklikk.hu/duna-world-elo)
|
||||||
|
|
||||||
Video streams require VLC 3.0+ (currently available from [nightly builds](https://nightlies.videolan.org/)).
|
## On addons.videolan.org
|
||||||
|
|
||||||
|
This playlist parser is also [available on addons.videolan.org](https://addons.videolan.org/p/1190582/).
|
||||||
|
|
|
@ -1,202 +1,155 @@
|
||||||
local tables
|
local dkjson = require('dkjson')
|
||||||
local streams
|
local log = {}
|
||||||
local urls
|
local openGraph = {}
|
||||||
local log
|
local streams = {}
|
||||||
|
local tables = {}
|
||||||
|
|
||||||
local function noPlaylist(reason)
|
local urlPatterns = {
|
||||||
log.err('Failed to create playlist:', reason)
|
'hirado%.hu',
|
||||||
return {}
|
'm4sport%.hu',
|
||||||
end
|
'mediaklikk%.hu'
|
||||||
|
|
||||||
local Object = {}
|
|
||||||
|
|
||||||
function Object:new(overrides)
|
|
||||||
return setmetatable(overrides or {}, {__index = self})
|
|
||||||
end
|
|
||||||
|
|
||||||
local Parser = Object:new()
|
|
||||||
local VideoParser = Parser:new()
|
|
||||||
local LiveStreamParser = Parser:new()
|
|
||||||
|
|
||||||
local parsers = {
|
|
||||||
LiveStreamParser:new{urlPattern = 'mediaklikk%.hu/m1%-elo'},
|
|
||||||
LiveStreamParser:new{urlPattern = 'mediaklikk%.hu/m2%-elo'},
|
|
||||||
LiveStreamParser:new{urlPattern = 'mediaklikk%.hu/m4%-elo'},
|
|
||||||
LiveStreamParser:new{urlPattern = 'mediaklikk%.hu/m5%-elo'},
|
|
||||||
LiveStreamParser:new{urlPattern = 'mediaklikk%.hu/duna%-elo'},
|
|
||||||
LiveStreamParser:new{urlPattern = 'mediaklikk%.hu/duna%-world%-elo'},
|
|
||||||
VideoParser:new{urlPattern = 'hirado%.hu'},
|
|
||||||
VideoParser:new{urlPattern = 'm4sport%.hu'},
|
|
||||||
VideoParser:new{urlPattern = 'mediaklikk%.hu'}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function probe()
|
function probe()
|
||||||
return tables.some(Parser.probe, parsers)
|
return vlc.access:match('https?') and tables.find(urlPatterns, function(pattern)
|
||||||
|
return vlc.path:match(pattern)
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function parse()
|
function parse()
|
||||||
local parser = tables.find(Parser.probe, parsers)
|
|
||||||
if not parser then
|
|
||||||
return noPlaylist('could not find Parser')
|
|
||||||
end
|
|
||||||
|
|
||||||
return parser:parse()
|
|
||||||
end
|
|
||||||
|
|
||||||
local protocol = vlc.access .. '://'
|
|
||||||
|
|
||||||
function Parser:probe()
|
|
||||||
return vlc.access:match('https?') and vlc.path:match(self.urlPattern)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Parser:parse()
|
|
||||||
local pageSource = streams.readAll(vlc)
|
local pageSource = streams.readAll(vlc)
|
||||||
|
|
||||||
log.dbg('Extracting Player URLs...')
|
if vlc.path:match('player%.mediaklikk%.hu') then
|
||||||
|
log.dbg('Player loaded, finding player options json')
|
||||||
|
|
||||||
local playerUrls = self:playerUrls(pageSource)
|
local playerOptionsJson = pageSource:match('pl.setup%( (%b{}) %);')
|
||||||
if #playerUrls == 0 then
|
if not playerOptionsJson then
|
||||||
return noPlaylist('could not find any Player URL')
|
log.warn('Cannot find player options json')
|
||||||
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
log.dbg('Player URLs:', unpack(playerUrls))
|
log.dbg('Finding playlist items of type hls')
|
||||||
log.dbg('Extracting Paths...')
|
|
||||||
|
|
||||||
local function findPath(playerUrl)
|
local playerOptions = dkjson.decode(playerOptionsJson)
|
||||||
local path = self:path(playerUrl)
|
local playlistItems = tables.filter(playerOptions.playlist, function(playlistItem)
|
||||||
if not path then
|
return playlistItem.type == 'hls'
|
||||||
log.warn('could not extract Path from', playerUrl)
|
end)
|
||||||
|
|
||||||
|
log.dbg('Number of playlist items:', #playlistItems)
|
||||||
|
|
||||||
|
local params = tables.map(tables.toMap(vlc.path:gmatch('[?&]([^=]+)=([^&]*)')), function(param)
|
||||||
|
return vlc.strings.decode_uri(param)
|
||||||
|
end)
|
||||||
|
|
||||||
|
return tables.map(playlistItems, function(playlistItem)
|
||||||
|
return {
|
||||||
|
path = playlistItem.file:gsub('^//', vlc.access .. '://'),
|
||||||
|
title = params.title,
|
||||||
|
arturl = params.bgimage
|
||||||
|
}
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return path
|
log.dbg('Finding embedded players');
|
||||||
|
|
||||||
|
local playerSetupJsons = tables.toArray(pageSource:gmatch('mtva_player_manager%.player%(document%.getElementById%("player_%d+_%d+"%), (%b{})%);'));
|
||||||
|
|
||||||
|
log.dbg('Number of players:', #playerSetupJsons)
|
||||||
|
|
||||||
|
return tables.map(playerSetupJsons, function(playerSetupJson)
|
||||||
|
local playerSetup = dkjson.decode(playerSetupJson)
|
||||||
|
local video = playerSetup.streamId or playerSetup.token
|
||||||
|
|
||||||
|
if not video then
|
||||||
|
log.warn('Cannot find either streamId or token in player setup json:', playerSetupJson)
|
||||||
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local paths = tables.map(findPath, playerUrls)
|
local title = playerSetup.title or openGraph.property(pageSource, 'title')
|
||||||
if #paths == 0 then
|
local arturl = (playerSetup.bgImage and vlc.access .. ':' .. playerSetup.bgImage) or openGraph.property(pageSource, 'image')
|
||||||
return noPlaylist('could not find any Path')
|
local playerUrl = vlc.access .. '://player.mediaklikk.hu/playernew/player.php?video=' .. video ..
|
||||||
end
|
((title and '&title=' .. vlc.strings.encode_uri_component(title)) or '') ..
|
||||||
|
((arturl and '&bgimage=' .. vlc.strings.encode_uri_component(arturl)) or '')
|
||||||
|
|
||||||
log.dbg('Paths:', unpack(paths))
|
log.dbg('Loading player:', playerUrl)
|
||||||
|
|
||||||
local function playListItem(path)
|
|
||||||
return self:playListItem(path, pageSource)
|
|
||||||
end
|
|
||||||
|
|
||||||
return tables.map(playListItem, paths)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Parser:path(playerUrl)
|
|
||||||
local playerPageSource = streams.readAll(vlc.stream(playerUrl))
|
|
||||||
local path = playerPageSource:match('"file": *"([^"]+)"')
|
|
||||||
if path then
|
|
||||||
return urls.normalize(path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function VideoParser:playerUrls(pageSource)
|
|
||||||
local function playerUrl(token)
|
|
||||||
return protocol .. 'player.mediaklikk.hu/player/player-external-vod-full.php?hls=1&token=' .. token
|
|
||||||
end
|
|
||||||
|
|
||||||
local tokens = tables.collect(pageSource:gmatch('"token":"([^"]+)"'))
|
|
||||||
return tables.map(playerUrl, tokens)
|
|
||||||
end
|
|
||||||
|
|
||||||
function VideoParser:playListItem(path, pageSource)
|
|
||||||
local function findProperty(property)
|
|
||||||
return pageSource:match('<meta property="og:' .. property .. '" content="([^"]+)"/>')
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
path = path,
|
path = playerUrl,
|
||||||
title = findProperty('title'),
|
title = title,
|
||||||
description = findProperty('description'),
|
arturl = arturl,
|
||||||
arturl = findProperty('image'),
|
options = {
|
||||||
url = findProperty('url')
|
'http-referrer=' .. vlc.access .. '://' .. vlc.path
|
||||||
}
|
}
|
||||||
end
|
|
||||||
|
|
||||||
function LiveStreamParser:playerUrls(pageSource)
|
|
||||||
local streamId = pageSource:match('"streamId":"([^"]+)"')
|
|
||||||
if not streamId then
|
|
||||||
return {}
|
|
||||||
end
|
|
||||||
|
|
||||||
return {protocol .. 'player.mediaklikk.hu/playernew/player.php?noflash=yes&video=' .. streamId}
|
|
||||||
end
|
|
||||||
|
|
||||||
function LiveStreamParser:playListItem(path, pageSource)
|
|
||||||
return {
|
|
||||||
path = path,
|
|
||||||
title = pageSource:match('<title>(.+)</title>'),
|
|
||||||
url = protocol .. vlc.path
|
|
||||||
}
|
}
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
tables = {
|
|
||||||
find = function(predicate, values)
|
|
||||||
for key, value in ipairs(values) do
|
|
||||||
if predicate(value, key, values) then
|
|
||||||
return value, key
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
some = function(predicate, values)
|
|
||||||
local value, key = tables.find(predicate, values)
|
|
||||||
return key
|
|
||||||
end,
|
|
||||||
|
|
||||||
collect = function(iterator, state, initialValue)
|
|
||||||
local result = {}
|
|
||||||
for value in iterator, state, initialValue do
|
|
||||||
table.insert(result, value)
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
end,
|
|
||||||
|
|
||||||
map = function(transform, values)
|
|
||||||
local result = {}
|
|
||||||
for key, value in ipairs(values) do
|
|
||||||
result[key] = transform(value, i, values)
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
streams = {
|
|
||||||
lines = function(s)
|
|
||||||
return s.readline, s, nil
|
|
||||||
end,
|
|
||||||
|
|
||||||
readAll = function(s)
|
|
||||||
return table.concat(tables.collect(streams.lines(s)), '\n')
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
urls = {
|
|
||||||
normalizations = {
|
|
||||||
{pattern = '\\(.)', replacement = '%1'},
|
|
||||||
{pattern = '^//', replacement = protocol}
|
|
||||||
},
|
|
||||||
|
|
||||||
normalize = function(url)
|
|
||||||
for i, normalization in ipairs(urls.normalizations) do
|
|
||||||
url = url:gsub(normalization.pattern, normalization.replacement)
|
|
||||||
end
|
|
||||||
return url
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
local function logger(vlcLog)
|
local function logger(vlcLog)
|
||||||
return function(...)
|
return function(...)
|
||||||
vlcLog(table.concat({'mediaklikk-video:', ...}, ' '))
|
vlcLog(table.concat({'mediaklikk-video:', ...}, ' '))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
log = {
|
log.dbg = logger(vlc.msg.dbg)
|
||||||
dbg = logger(vlc.msg.dbg),
|
log.warn = logger(vlc.msg.warn)
|
||||||
warn = logger(vlc.msg.warn),
|
log.err = logger(vlc.msg.err)
|
||||||
err = logger(vlc.msg.err),
|
log.info = logger(vlc.msg.info)
|
||||||
info = logger(vlc.msg.info)
|
|
||||||
}
|
function openGraph.property(source, property)
|
||||||
|
return source:match('<meta property="og:' .. property .. '" content="(.-)"/>')
|
||||||
|
end
|
||||||
|
|
||||||
|
function streams.readAll(s)
|
||||||
|
local function iterator(size)
|
||||||
|
if s == vlc then
|
||||||
|
return s.read(size)
|
||||||
|
else
|
||||||
|
return s:read(size)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return table.concat(tables.toArray(iterator, 1024, nil));
|
||||||
|
end
|
||||||
|
|
||||||
|
function tables.find(values, predicate)
|
||||||
|
for key, value in pairs(values) do
|
||||||
|
if predicate(value, key, values) then
|
||||||
|
return value, key
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function tables.toArray(iterator, state, initialValue)
|
||||||
|
local result = {}
|
||||||
|
for value in iterator, state, initialValue do
|
||||||
|
table.insert(result, value)
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function tables.toMap(iterator, state, initialValue)
|
||||||
|
local result = {}
|
||||||
|
for key, value in iterator, state, initialValue do
|
||||||
|
result[key] = value
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function tables.map(values, transform)
|
||||||
|
local result = {}
|
||||||
|
for key, value in pairs(values) do
|
||||||
|
result[key] = transform(value, key, values)
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function tables.filter(values, predicate)
|
||||||
|
local result = {}
|
||||||
|
for key, value in pairs(values) do
|
||||||
|
if predicate(value, key, values) then
|
||||||
|
result[key] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
Loading…
Reference in New Issue