(:~ : -------------------------------- : Xivid function module : -------------------------------- : : Copyright (C) 2023 Reino Wijnsma : : This program is free software: you can redistribute it and/or modify : it under the terms of the GNU General Public License as published by : the Free Software Foundation, either version 3 of the License, or : (at your option) any later version. : : This program is distributed in the hope that it will be useful, : but WITHOUT ANY WARRANTY; without even the implied warranty of : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the : GNU General Public License for more details. : : You should have received a copy of the GNU General Public License : along with this program. If not, see . : : @author Reino Wijnsma (rwijnsma@xs4all.nl) : @see https://github.com/Reino17/xivid :) xquery version "4.0-xidel"; module namespace xivid = "https://github.com/Reino17/xivid/"; (:~ : -------------------------------- : Helper functions : -------------------------------- :) declare function xivid:m3u8-to-json($url as string?) as array()? { let $src:=unparsed-text($url)[$url], $media:=extract($src,"#EXT-X-MEDIA.*URI=.+",0,"*"), $streams:=extract($src,"#EXT-X-STREAM-INF.+\r?\n.+",0,"*") return if (not(exists($streams))) then array{ {"id":"hls-1","format":"m3u8[h264+aac]","url":$url}[url] }[exists(.())] else array{ $media[contains(.,"TYPE=SUBTITLES")] ! { "id":"sub-1", "format":"m3u8[vtt]", "language":extract(.,"LANGUAGE="(.+?)"",1), "label":extract(.,"NAME="(.+?)"",1), "url":resolve-uri(extract(.,"URI="(.+?)"",1),$url) }, for $x at $i in $streams[contains(.,"PROGRESSIVE-URI")] let $br:=extract($x,"BANDWIDTH=(\d+)",1) order by $br count $i return { "id":"pg-"||$i, "format":"mp4[h264+aac]", "resolution":extract($x,"RESOLUTION=([\dx]+)",1), "bitrate":round($br div 1000)||"kbps", "url":extract($x,"URI="(.+?)"",1) }, {"id":"hls-0","format":"m3u8[manifest]","url":$url}, $media[contains(.,"TYPE=AUDIO")] ! { "id":"hls-"||position(), "format":"m3u8[aac]", "bitrate"?:extract(.,"URI=".+?(\d+)_K.+?"",1)[.] ! `{.}kbps`, "url":resolve-uri(extract(.,"URI="(.+?)"",1),$url) }, for $x at $i in $streams where if (exists($media[contains(.,"TYPE=AUDIO")])) then contains($x,"RESOLUTION") else $x group by $file:=x:lines($x)[last()] let $br:=extract($x[last()],"BANDWIDTH=(\d+)",1), $br2:=extract($file,"=(\d+)",1,"*") order by $br count $i return { "id":"hls-"||$i + count($media[contains(.,"TYPE=AUDIO")]), "format":if (contains($x[last()],"RESOLUTION")) then if (exists($media[contains(.,"TYPE=AUDIO")])) then "m3u8[h264]" else "m3u8[h264+aac]" else "m3u8[aac]", "resolution"?:concat( extract($x[last()],"RESOLUTION=([\dx]+)",1)[.], extract($x[last()],"FRAME-RATE=([\d\.]+)",1)[.] ! `@{round-half-to-even(.,3)}fps` )[.], "bitrate"?:if ($br2[1]) then join((round($br2[2] div 1000),round($br2[1] div 1000)),"|")||"kbps" else round($br div 1000)||"kbps", "url":resolve-uri($file,$url) } } }; declare function xivid:mpd-to-json($mpd) as array() { array{ { "id":"dash-0", "format":"mpd[manifest]", "url":$mpd[. instance of string] }[url], for $x at $i in ( if ($mpd instance of node()) then $mpd else doc($mpd) )//Representation order by boolean($x/@width),$x/@bandwidth count $i return { "id":"dash-"||$i, "format":`{substring-after($x/(.,..)/@mimeType,"/")}[{ tokenize($x/@codecs,"\.")[1] ! ( if (.="mp4a") then "aac" else if (.="avc1") then "h264" else . ) }]`, "resolution"?:$x/@width ! `{.}x{$x/@height}@{ round-half-to-even( eval(replace($x/@frameRate,"/"," div ")),3 ) }fps`, "samplerate"?:$x/@audioSamplingRate ! `{. div 1000}kHz`, "bitrate":`{round($x/@bandwidth div 1000)}kbps`, "url":if ($mpd instance of string) then resolve-uri($x/BaseUrl,$mpd) else $x/BaseUrl } } }; declare function xivid:adjust-dateTime-to-dst($arg as anyAtomicType) as dateTime { let $is-summertime:=function($arg as dateTime) as boolean { let $dst:= for $month in ("03","10") let $day:=(25 to 31)[ days-from-duration( date(`{year-from-dateTime($arg)}-{$month}-{.}`) - date("0000-01-01") ) mod 7 eq 0 ] return dateTime(`{year-from-dateTime($arg)}-{$month}-{$day}T01:00:00Z`) return $dst[1] le dateTime($arg) and dateTime($arg) lt $dst[2] } return adjust-dateTime-to-timezone( dateTime($arg), if ($is-summertime(current-dateTime())) then if ($is-summertime(dateTime($arg))) then implicit-timezone() else implicit-timezone() - duration("PT1H") else if ($is-summertime(dateTime($arg))) then implicit-timezone() + duration("PT1H") else implicit-timezone() ) }; declare function xivid:string-to-utc-dateTime($arg as string) as dateTime { let $month:={ "jan":"01","feb":"02","mrt":"03","maart":"03","apr":"04", "mei":"05","jun":"06","jul":"07","aug":"08", "sep":"09","okt":"10","nov":"11","dec":"12" }, $dt:=if ($arg castable as dateTime) then $arg else let $i:=tokenize($arg,"[\s-]") return concat( join( ( $i[3], if ($i[2] castable as integer) then $i[2] else ($month($i[2]),$month(substring($i[2],1,3))), format-integer($i[1],"00") ),"-" ), "T", substring( join( tokenize($i[4],":") ! format-integer(.,"00"),":" )||":00", 1,8 ) ) return adjust-dateTime-to-timezone( xivid:adjust-dateTime-to-dst($dt), duration("PT0S") ) }; declare function xivid:bin-xor($a as integer,$b as integer) as integer { let $bin:=($a,$b) ! x:integer-to-base(.,2), $len:=max($bin ! string-length()), $val:=$bin ! format-integer(.,string-join((1 to $len) ! 0)) return string-join( (1 to $len) ! ( if (substring($val[1],.,1) eq substring($val[2],.,1)) then 0 else 1 ) ) ! x:integer(.,2) }; declare function xivid:info($json as object()) as string* { let $lbl:={ "name":"Naam:","date":"Datum:","duration":"Tijdsduur:", "start":"Begin:","end":"Einde:","expdate":"Gratis tot:", "formats":"Formaten:" }, $len:=$json() ! string-length($lbl(.)), $fmts:=array{ { "id":"id","format":"formaat", "language":"taal","resolution":"resolutie", "samplerate":"frequentie","bitrate":"bitrate" }, $json/(formats)() }, $f_lbl:=$fmts(1)() ! distinct-values( for $x in $fmts()[position() gt 1] return .[$x(.)] ), $f_len:=$f_lbl[position() lt last()] ! max( $fmts()(.) ! string-length() ), $dur:=$json[start]/(start,duration) ! ( dayTimeDuration(.) div dayTimeDuration("PT1S") ), $ss:=($dur[1] - $dur[1] mod 30,$dur[1] mod 30) return ( for $x at $i in $json() return concat( $lbl($x), string-join((1 to max($len) - $len[$i] + 1) ! " "), if ($x="formats") then join( $fmts() ! string-join( for $x at $i in $f_lbl return ( if (position() eq count($fmts()) and $i eq count($f_lbl)) then .($x)||" (best)" else .($x), (1 to $f_len[$i] - string-length(.($x)) + 2) ! " " ) )," "||(1 to max($len) + 1) ! " " ) else if ($x=("date","expdate")) then format-dateTime( xivid:adjust-dateTime-to-dst($json($x)), "[D01]-[M01]-[Y] [H01]:[m01]:[s01]" ) else if ($x=("duration","start","end")) then duration($json($x)) + time("00:00:00") else $json($x) ), concat( `Download:{string-join((1 to max($len) - 9 + 1) ! " ")}ffmpeg `, $ss[1][. gt 0] ! `-ss {.} `, "-i ", $ss[2][. gt 0] ! `-ss {.} `, `-t {$dur[2]} -c copy ` )[exists($dur)] ) }; declare function xivid:bbvms( $url as string?,$publ as string?,$title as string? ) as object()? { json-doc($url)/( if (clipData/sourcetype="live") then { "name":`{if ($publ) then $publ else publicationData/label}: Livestream`, "date":adjust-dateTime-to-timezone( current-dateTime() + duration("PT0.5S"), duration("PT0S") ) ! substring(.,1,19)||"Z", "formats":xivid:m3u8-to-json(clipData/(assets)()/src) } else { "name":join( ( if ($publ) then $publ else publicationData/label, if ($title) then $title else clipData/title ),": " ), "date":adjust-dateTime-to-timezone( dateTime(clipData/publisheddate) + duration("PT0.5S"), duration("PT0S") ) ! substring(.,1,19)||"Z", "duration"?:clipData/length * duration("PT1S"), "formats"?:let $host:=publicationData return clipData/array{ (subtitles)()/{ "id":"sub-1", "format":"srt", "language":isocode, "label":languagename, "url":`{$host/baseurl}/subtitle/{id}.srt` }, for $x at $i in (assets)()[not(ends-with(src,"m3u8"))] order by exists($x/isSource),$x/bandwidth count $i return { "id":"pg-"||$i, "format":"mp4[h264+aac]", "resolution":`{$x/width}x{$x/height}`, "bitrate":$x/bandwidth||"kbps", "url":resolve-uri($x/src,$host/defaultMediaAssetPath) }, xivid:m3u8-to-json( resolve-uri( (assets)()[ends-with(src,"m3u8")]/src, $host/defaultMediaAssetPath ) )(), let $org:=parse-json(s3Info) return { "id":"pg-"||count((assets)()[not(ends-with(src,"m3u8"))]) + 1, "format":($org/format/filename,src)[1] ! ( if (ends-with(.,"mkv")) then "mkv[h264+opus]" else if (ends-with(.,"mxf")) then "mxf[mpeg2+pcm]" else "mp4[h264+aac]" ), "resolution":`{originalWidth}x{originalHeight}`, "bitrate"?:$org/format/`{round(tokenize(bit_rate)[1] * 1024)}kbps`, "url":( $org/format/filename, resolve-uri( .[not(src = (assets)()/src)]/src[.], $host/defaultMediaAssetPath ) )[1] }[url] }[exists(.())] } ) }; (:~ : -------------------------------- : Extractors : -------------------------------- :) declare function xivid:npo($url as string) as object()? { let $prid:=if (matches($url,"https://www.npostart.nl/live/")) then doc($url)//npo-player/@media-id else if (matches($url,"https://radioplayer.npo.nl/")) then parse-json( doc($url)//script/substring-after(.,"window.config=") )/request-decode(embedVideoUrl)/params/mid else extract($url,"[A-Z\d_]+$"), $cookies:=x:request({ "headers":"X-Requested-With: XMLHttpRequest", "url":"https://www.npostart.nl/api/token" })/extract(headers,"Set-Cookie: (.+?);",1,"*"), $token:=x:request({ "method":"POST", "headers":( "X-Requested-With: XMLHttpRequest", replace(uri-decode($cookies[1]),"XSRF-TOKEN=","X-XSRF-TOKEN: "), "Cookie: "||$cookies[2] ), "url":"https://www.npostart.nl/player/"||$prid })/json return parse-json( extract(doc($token/embedUrl)[.//video]//script,"var video =(.+);",1) )/{ "name":if (type="liveradio") then `NPO: {title} Livestream` else concat( "NPO: ",franchiseTitle, if (exists(episodeNumber)) then concat( " S",format-integer(seasonNumber,"00"), "E",format-integer(episodeNumber,"00") ) else ": "||title ), "date":broadcastDate, "duration"?:duration * duration("PT1S"), "start"?:startAt * duration("PT1S"), "end"?:(startAt + duration) * duration("PT1S"), "formats":array{ ( if (not(exists((subtitles)())) and parentId) then x:request({ "method":"POST", "headers":( "X-Requested-With: XMLHttpRequest", replace(uri-decode($cookies[1]),"XSRF-TOKEN=","X-XSRF-TOKEN: "), "Cookie: "||$cookies[2] ), "url":"https://www.npostart.nl/player/"||parentId })/parse-json( extract(doc(json/embedUrl)[.//video]//script,"var video =(.+);",1) ) else . )/(subtitles)()/{ "id":"sub-1", "format":"vtt", "language":language, "label":label, "url":src }, xivid:m3u8-to-json( x:request({ "method":"POST", "url":request-combine( `https://start-player.npo.nl/video/{ if (not(exists((subtitles)())) and parentId) then parentId else $prid }/streams`, {"profile":"hls","quality":"npo","tokenId":$token/token} )/url })/json/stream[not(exists(protection))]/src )() } }[exists((formats)())] }; declare function xivid:nos($url as string) as object()? { let $src:=parse-json(doc($url)//script[@type="application/json"])//data return $src/(video,(items)()[type="video"][1])/{ "name":`{$src/supplyChannelName}: {title}`, "date":adjust-dateTime-to-timezone( dateTime(replace($src/publishedAt,"(.{22})(.{2})","$1:$2")), duration("PT0S") ), "duration":time( substring("00:00:00",1,8 - string-length(duration))||duration ) - time("00:00:00"), "formats":xivid:m3u8-to-json( x:request({"method":"HEAD","url":source/url})/url ) } }; declare function xivid:rtl($url as string) as object()? { json-doc( "https://api.rtl.nl/rtlxl/metadata/api/metadata/"||( if (contains($url,"rtlnieuws.nl")) then doc($url)//@data-uuid else extract($url,"[0-9a-z-]+$") ) )/{ "name":`RTL: {series/title} - {title}`, "date":broadcastDateTime, "duration":duration * duration("PT1S"), "formats":xivid:m3u8-to-json( x:request({ "headers":"authorization: Bearer "||json-doc( "https://api.rtl.nl/rtlxl/token/api/2/token" )/accessToken, "url":(assets)()[type="Video"]/request-combine( url,{"device":"web","format":"hls"} )/url })/json/manifest ) } }; declare function xivid:kijk($url as string) as object()? { x:request({ "headers":"Accept: application/json", "url":request-combine( "https://graph.kijk.nl/graphql", { "query":concat( "query{programs(guid:"", extract($url,"\w+$"), ""){items{__typename,type,guid,title,duration,", "tvSeasonEpisodeNumber,seriesEpisodeNumber,seasonNumber,", "media{mediaContent{assetTypes,sourceUrl,type},availableDate,", "expirationDate,availabilityState},series{guid,title}}}}" ) } )/url })/(json//items)()/{ "name":concat( "Kijk: ", join((series/title,title)," - "), .[exists(seasonNumber)]/concat( " S", format-integer(seasonNumber,"00"), "E", format-integer(tvSeasonEpisodeNumber,"00") ) ), "date":.//availableDate div 1000 * duration("PT1S") + dateTime("1970-01-01T00:00:00Z"), "duration":round(duration) * duration("PT1S"), "expdate":.//expirationDate div 1000 * duration("PT1S") + dateTime("1970-01-01T00:00:00Z"), "formats":array{ for $x at $i in (.//mediaContent)()[type="webvtt"] order by $x/sourceUrl count $i return $x/{ "id":"sub-"||$i, "format":"vtt", "language":"nl", "label":substring((assetTypes)(),17), "url":sourceUrl }, xivid:m3u8-to-json( (.//mediaContent)()[ type="m3u8" and ends-with((assetTypes)(),"public") ]/sourceUrl )() } } }; declare function xivid:tvblik($url as string) as object()? { let $host:=extract( doc($url)//( div[@id="embed-player"]/(@data-episode,a/@href), div[@class="video_thumb"]//@onclick, iframe[@class="sbsEmbed"]/@src ), "(npo|rtl|kijk).+(?:/|video=)([\w-]+)",(1,2) ) return if ($host[1]="npo") then xivid:npo("https://www.npostart.nl/"||$host[2]) else if ($host[1]="rtl") then xivid:rtl("https://www.rtlxl.nl/video/"||$host[2]) else xivid:kijk("https://kijk.nl/video/"||$host[2]) }; declare function xivid:regiogroei($url as string) as object()? { let $omroep:={ "omropfryslan":{ "livestreamId":3748627, "headerId":"friesland.fryslan", "bbvms":"https://omropfryslan.bbvms.com/p/regiogroei_fryslan_web_videoplayer/c" }, "rtvnoord":{ "livestreamId":3977713, "headerId":"groningen", "bbvms":"https://rtvnoord.bbvms.com/p/regiogroei_web_videoplayer/c" }, "rtvdrenthe":{ "livestreamId":3230738, "headerId":"drenthe", "bbvms":"https://rtvdrenthe.bbvms.com/p/regiogroei_drenthe_web_videoplayer/c" }, "rtvoost":{ "livestreamId":4198331, "headerId":"overijssel", "bbvms":"https://rtvoost.bbvms.com/p/regiogroei_oost_web_videoplayer/c" }, "omroepwest":{ "livestreamId":2054317, "headerId":"zh-west", "bbvms":"https://omroepwest.bbvms.com/p/regiogroei_west_web_videoplayer/c" }, "rijnmond":{ "livestreamId":1227908, "headerId":"zh-rijnmond", "bbvms":"https://rijnmond.bbvms.com/p/regiogroei_rijnmond_web_videoplayer/c" }, "rtvutrecht":{ "livestreamId":3742011, "headerId":"utrecht", "bbvms":"https://rtvutrecht.bbvms.com/p/regiogroei_utrecht_web_videoplayer/c" }, "gld":{ "livestreamId":3651657, "headerId":"gelderland", "bbvms":"https://omroepgelderland.bbvms.com/p/regiogroei_gelderland_web_videoplayer/c" }, "omroepzeeland":{ "livestreamId":3745936, "headerId":"zeeland", "bbvms":"https://omroepzeeland.bbvms.com/p/regiogroei_zeeland_web_videoplayer/c" } }, $host:=extract($url,"www\.(.+)\.nl",1), $path:=tokenize($url,"/") return map:get($omroep,$host)/xivid:bbvms( `{bbvms}/{ if (ends-with($url,"live")) then livestreamId else x:request( map:merge(( { "headers":( `Accept: application/vnd.groei.{headerId}+json;v=3.0`, "X-Groei-Platform: web","X-Groei-Layout: wide" ) }, request-combine( "https://api.regiogroei.cloud/page/episode/"||$path[last()], {"slug":$path[last() - 1],"origin":$path[last()]} ) )) )/json/(components)(1)/sourceId }.json`, if ($host = "omropfryslan") then "Omrop Fryslân" else (), if (ends-with($url,"live")) then () else normalize-space(doc($url)//h1[@class="program-header_title"]) ) }; declare function xivid:obr($url as string) as object()? { if ($url = "https://www.omroepbrabant.nl/tv") then xivid:bbvms("https://omroepbrabant.bbvms.com/p/default/c/1080520.json",(),()) else xivid:bbvms( `https://omroepbrabant.bbvms.com/p/default/c/sourceid_string:{ extract("'$url'","\d+",0,"*")[last()] }.json`, (), doc($url)//meta[@property="og:title"]/normalize-space(@content) ) }; declare function xivid:l1($url as string) as object()? { doc($url)/( if ($url = "https://l1.nl/live-l1-tv") then xivid:bbvms(//div[@class="bbwLive-player"]/script/@src||"on",(),()) else xivid:bbvms( `https://limburg.bbvms.com/p/L1_video/c/{ //div[@class="bbw bbwVideo"]/@data-id }.json`, (), //meta[@property="og:title"]/@content ) ) }; declare function xivid:nhnieuws($url as string) as object()? { doc($url)/( if (//article) then parse-json( //script/substring-after(.,"INITIAL_PROPS__ = ") )/pageData/{ "name":if (contains($url,"nhnieuws.nl")) then "NH Nieuws: "||title else "AT5: "||title, "date":created * duration("PT1S") + dateTime("1970-01-01T00:00:00Z"), "formats":xivid:m3u8-to-json( (blocks)()[type=("video","headerVideo")]/video/stream/url ) } else { "name":substring-after(//head/title,"Media - ")||": Livestream", "date":adjust-dateTime-to-timezone( current-dateTime() + duration("PT0.5S"), duration("PT0S") ) ! substring(.,1,19)||"Z", "formats":xivid:m3u8-to-json( parse-json( //script/substring-after(.,"INIT_DATA__ = ") )/videoStream ) } ) }; declare function xivid:ofl($url as string) as object()? { let $src:=doc($url), $info:=$src//div[@class="fn-jw-player fn-videoplayer"] return if ($info/@data-page-type="home") then { "name":"Omroep Flevoland: Livestream", "date":adjust-dateTime-to-timezone( current-dateTime() + duration("PT0.5S"), duration("PT0S") ) ! substring(.,1,19)||"Z", "formats":xivid:m3u8-to-json($info/@data-file) } else { "name":"Omroep Flevoland: "||normalize-space($src//h2), "date":xivid:string-to-utc-dateTime( $src//header[@class="header"]/div/join( if (span/span[@class="d--block--sm"]) then extract( span/span[@class="d--block--sm"], "(\d+ \w+ \d+) \| ([\d:]+)",(1,2) ) else extract(span,"[\d:-]+",0,"*") ) ), "formats":array{ { "id":"pg-1", "format":"mp4[h264+aac]", "resolution":"960x540", "url":$info/@data-file } } } }; declare function xivid:dumpert($url as string) as object()? { let $json:=parse-json(parse-json( doc($url)//script/extract(.,"JSON\.parse\((.+)\)",1)[.] ))/items/item/item[media_type="VIDEO"], $fmts:=$json//variants, $host:={ "youtube":"https://youtu.be/", "twitter":"https://twitter.com/i/status/" } return if ($fmts()/version="embed" and not($fmts()/version="stream")) then let $uri:=tokenize($fmts()/uri,":") return eval(`xivid:{$uri[1]}("{$host($uri[1])}{$uri[2]}")`) else $json/{ "name":"Dumpert: "||title, "date":adjust-dateTime-to-timezone(dateTime(date),duration("PT0S")), "duration":(media)()/duration * duration("PT1S"), "formats":array{ for $x at $i in ("mobile","tablet","720p","original") return { "id":"pg-"||$i, "format":"mp4[h264+aac]", "url":$fmts()[version=$x and ends-with(uri,"mp4")]/uri }[url], xivid:m3u8-to-json($fmts()[version="stream"]/uri)() } } }; declare function xivid:autojunk($url as string) as object()? { doc($url)//div[@id="playerWrapper"]/( .[iframe]/xivid:youtube(iframe/@src), .[script]/{ "name":"Autojunk: "||//meta[@property="og:title"]/@content, "date":xivid:string-to-utc-dateTime( join(extract(//span[@class="posted"]/text(),"[\d:-]+",0,"*")) ), "formats"?:array{ let $id:=extract(//meta[@property="og:image"]/@content,"\d{4}/\d{4}/\d+") for $x at $i in (".mp4","_hq.mp4","_720p.mp4") ! `https://static.autojunk.nl/flv/{$id}{.}` where x:request({ "method":"HEAD", "error-handling":"xxx=accept", "url":$x })/headers[1] = "HTTP/1.1 200 OK" return { "id":"pg-"||$i, "format":"mp4[h264+aac]", "resolution":("640x360","852x480","1280x720")[$i], "bitrate":(600,1200,2000)[$i]||"kbps", "url":$x } } } ) }; declare function xivid:abhd($url as string) as object()? { doc($url)//div[@id="playerObject"]/map:merge(( { "name":"ABHD: "||h1/a }, parse-json( replace( extract(script,"clipData.assets = (.+\]);",1,"s")," //.+","" ), {"liberal":true()} )/{ "date":dateTime( join( extract(.(1)/src,"(\d{4})/?(\d{2})(\d{2})",1 to 3),"-" )||"T00:00:00Z" ), "formats":array{ for $x at $i in .()/src return { "id":"pg-"||$i, "format":"mp4[h264+aac]", "resolution":if (ends-with($x,"hq.mp4")) then "852x480" else "1280x720", "bitrate":if (ends-with($x,"hq.mp4")) then "1200kbps" else "2000kbps", "url":if (matches($x,"\d{14}")) then replace($x,"(.+?\d{4})(\d{4})(.+)","$1/$2/$3") else $x } } } )) }; declare function xivid:autoblog($url as string) as object()? { doc($url)/( //iframe[contains(@data-lazy-src,"bbvms")]/xivid:bbvms( resolve-uri(replace(@data-lazy-src,"html.+","json"),$url),(),() ), //iframe[contains(@data-lazy-src,"youtube")]/xivid:youtube( substring-before(@data-lazy-src,"?") ) ) }; declare function xivid:telegraaf($url as string) as object()? { json-doc( `https://content.tmgvideo.nl/playlist/item={ parse-json( doc($url)//script/extract(.,"APOLLO_STATE__=(.+);",1)[.] )/(.//videoId)[1] }/playlist.json` )/(items)()/{ "name":"Telegraaf: "||title, "date":xivid:string-to-utc-dateTime( replace(publishedstart,"\s","T") ), "duration":duration * duration("PT1S"), "expdate":publishedend ! xivid:string-to-utc-dateTime( replace(.,"\s","T") ), "formats":array{ locations/reverse((progressive)())/{ "id":"pg-"||position(), "format":"mp4[h264+aac]", "resolution":`{width}x{height}`, "url":.//src }, xivid:m3u8-to-json( locations/(adaptive)()[type="application/x-mpegURL"]/extract(src,".+m3u8") )() } } }; declare function xivid:ad($url as string) as object()? { let $id:=extract($url,"~p(\d+)",1), $json:=json-doc( `https://embed.mychannels.video/sdk/production/{ if ($id) then $id else x:request({ "headers":"Cookie: authId=8ac8ac9f-3782-4ba2-a449-9dc1fcdacbd5", "url":$url })/doc/( if (//*[@class="mc-embed"]) then //*[@class="mc-embed"]/extract(@src,"\d+") else //div[@data-mychannels-type="video"]/@data-mychannels-id ) }?options=FUTFU_default` ) return $json/map:merge(( { "name":`AD: {(shows)()/title} - {(productions)()/title}` }, (productions)()/{ "date":xivid:string-to-utc-dateTime( replace(publicationDate,"\s","T") ), "duration":duration * duration("PT1S"), "formats":array{ for $x at $i in reverse((sources)()[type="video/mp4"]) return { "id":"pg-"||$i, "format":"mp4[h264+aac]", "resolution":("640x360","1280x720")[$i], "url":$x/src }, xivid:m3u8-to-json((sources)(1)/src)() } } )) }; declare function xivid:lc($url as string) as object()? { doc($url)/map:merge(( parse-json(//script[@type="application/ld+json"])/{ "name":`{.(3)/name}: {.(2)/name}`, "date":replace(.(1)/datePublished,"\+0000","Z") }, json-doc( `https://content.tmgvideo.nl/playlist/item={ //div[@class="video-player"]/substring-after(@id,"videoplayer-") }/playlist.json` )/(items)()/{ "duration":duration * duration("PT1S"), "formats":locations/array{ reverse((progressive)())/{ "id":"pg-"||position(), "format":"mp4[h264+aac]", "resolution":`{width}x{height}`, "url":(sources)()/src }, xivid:m3u8-to-json( (adaptive)()[type="application/x-mpegURL"]/src )() } } )) }; declare function xivid:youtube($url as string) as object()? { x:request({ "headers":( "Content-Type: application/json", "User-Agent: com.google.android.youtube/18.16.34" ), "post":serialize( { "context":{ "client":{ "clientName":"ANDROID", "clientVersion":"18.16.34", "androidSdkVersion":33 } }, "videoId":extract($url,"[A-Za-z0-9_-]+$") }, {"method":"json"} ), "url":"https://www.youtube.com/youtubei/v1/player" })/json/{ "name":videoDetails/title, "duration"?:videoDetails[not(isLive)]/lengthSeconds * duration("PT1S"), "formats":array{ (.//captionTracks)()[languageCode=("nl","en")]/{ "id":"sub-"||position(), "format":"ttml", "language":languageCode, "label":name//text, "url":baseUrl }, for $x at $i in streamingData/(formats)() order by $x/width count $i return { "id":"pg-"||$i, "format":extract($x/mimeType,"video/(.+?);",1) ! ( if (.="3gpp") then "3gpp[mp4v+aac]" else "mp4[h264+aac]" ), "resolution":`{$x/width}x{$x/height}@{$x/fps}fps`, "bitrate":round($x/bitrate div 1000)||"kbps", "url":$x/url }, for $x at $i in streamingData[not(hlsManifestUrl)]/(adaptiveFormats)() order by boolean($x/width),$x/bitrate count $i return { "id":"dash-"||$i, "format":let $mt:=extract($x/mimeType,"/(.+?);.+"(\w+)",(1,2)) return `{$mt[1]}[{ if ($mt[2]="avc1") then "h264" else if ($mt[2]="mp4a") then "aac" else $mt[2] }]`, "resolution"?:$x/width ! `{.}x{$x/height}@{$x/fps}fps`, "samplerate"?:$x/audioSampleRate ! `{. div 1000}kHz`, "bitrate":round($x/bitrate div 1000)||"kbps", "url":$x/url }, xivid:m3u8-to-json(streamingData/hlsManifestUrl)() } } }; declare function xivid:vimeo($url as string) as object()? { json-doc(`https://player.vimeo.com/video/{extract($url,"\d+")}/config`)/{ "name":video/`{owner/name}: {title}`, "date":adjust-dateTime-to-timezone( dateTime(replace(seo/upload_date,"\s","T")||"-05:00"), duration("PT0S") ), "duration":video/duration * duration("PT1S"), "formats":request/files/array{ for $x at $i in (progressive)() order by $x/width count $i return $x/{ "id":"pg-"||$i, "format":"mp4[h264+aac]", "resolution":`{width}x{height}@{fps}fps`, "url":url }, xivid:m3u8-to-json((hls//url)[1])() } } }; declare function xivid:dailymotion($url as string) as object()? { json-doc( "https://www.dailymotion.com/player/metadata/video/"|| extract($url,"video/(x[0-9a-z]+)",1) )/{ "name":"Dailymotion: "||title, "date":created_time * duration("PT1S") + dateTime("1970-01-01T00:00:00Z"), "duration":duration * duration("PT1S"), "formats":xivid:m3u8-to-json(qualities//url) } }; declare function xivid:rumble($url as string) as object()? { request-combine( "https://rumble.com/embedJS/u3/", { "request":"video", "ver":"2", "v":if (matches($url,"^v[0-9a-z]+")) then $url else ( if (contains($url,"/embed/")) then $url else parse-json( doc($url)//script[@type="application/ld+json"] )(1)/embedUrl ) ! extract(.,"embed/(v[0-9a-z]+)",1) } )/json-doc(url)/{ "name":`{author/name}: {parse-html(title)}`, "date":dateTime(pubDate), "duration"?:.[live=0]/duration * duration("PT1S"), "formats":array{ cc/nl/{ "id":"sub-1", "format":"vtt", "language":"nl", "label":language, "url":path }, for $x at $i in ua/(mp4,webm)/* order by $x/meta/bitrate count $i return $x/{ "id":"pg-"||$i, "format":if (ends-with(url,"webm")) then "webm[vp8+vorbis]" else "mp4[h264+aac]", "resolution":meta/`{w}x{h}`, "bitrate":meta/bitrate||"kbps", "url":url }, xivid:m3u8-to-json(ua/hls/auto/url)() } } }; declare function xivid:odysee($url as string) as object()? { parse-json( doc($url)//script[@type="application/ld+json"] )/{ "name":`{substring-after(author/name,"@")}: {name}`, "date":dateTime(uploadDate), "formats":array{ { "id":"pg-1", "format":"mp4[h264+aac]", "resolution":`{width}x{height}`, "url":contentUrl } } } }; declare function xivid:reddit($url as string) as object()? { json-doc( if (request-decode($url)/host = "v.redd.it") then x:request({"method":"HEAD","url":$url})/url||".json" else $url||".json" )(1)/data/(children)()/data/( if (is_reddit_media_domain) then { "name":`{subreddit}: {title}`, "date":created * duration("PT1S") + dateTime("1970-01-01T00:00:00Z"), "duration":media/reddit_video/duration * duration("PT1S"), "formats":media/reddit_video/array{ xivid:m3u8-to-json(string(parse-html(hls_url)))(), xivid:mpd-to-json(string(parse-html(dash_url)))() } } else xivid:youtube(request-decode(parse-html(url))/params/v) ) }; declare function xivid:twitch($url as string) as object()? { let $call_api:=function($query as object()) as object() { x:request({ "headers":"Client-ID: kimne78kx3ncx6brgo4mv6wki5h1ko", "post":serialize($query,{"method":"json"}), "url":"https://gql.twitch.tv/gql" })/json/data/* }, $path:=tokenize( request-decode($url)/substring-after(url,host),"/" )[.] return $call_api( { "operationName":"ComscoreStreamingQuery", "variables":{ "channel":if ($path=("video","videos","clip")) then "" else $path[last()], "isLive":if ($path=("video","videos","clip")) then false() else true(), "isVodOrCollection":if ($path=("video","videos")) then true() else false(), "vodID":if ($path=("video","videos")) then $path[last()] else "", "isClip":if ($path="clip") then true() else false(), "clipSlug":if ($path="clip") then $path[last()] else "" }, "extensions":{ "persistedQuery":{ "version":1, "sha256Hash":"e1edae8122517d013405f237ffcc124515dc6ded82480a88daef69c83b53ac01" } } } )/{ "name":"Twitch: "||( .[__typename="Video"]/`{owner/displayName} - {title}`, .[__typename="Clip"]/`{broadcaster/displayName} - {title}`, .[__typename="User"]/`{displayName}: {broadcastSettings/title}` ), "date":.//createdAt, "duration"?:(lengthSeconds,durationSeconds) * duration("PT1S"), "formats":if ($path="clip") then $call_api({ "operationName":"VideoAccessToken_Clip", "variables":{"slug":$path[last()]}, "extensions":{ "persistedQuery":{ "version":1, "sha256Hash":"36b89d2507fce29e5ca551df756d27c1cfe079e2609642b4390aa4c35796eb11" } } })/array{ for $x at $i in (videoQualities)() order by $x/quality count $i return { "id":"pg-"||$i, "format":"mp4[h264+aac]", "resolution":("640x320","854x480","1280x720","1920x1080")[$i], "url":request-combine( $x/sourceURL, playbackAccessToken/{"sig":signature,"token":value} )/url } } else request-combine( concat( "https://usher.ttvnw.net/", if ($path=("video","videos")) then "vod/" else "api/channel/hls/", $path[last()], ".m3u8" ), $call_api({ "query":concat( if ($path=("video","videos")) then "{videoPlaybackAccessToken(id:"" else "{streamPlaybackAccessToken(channelName:"", $path[last()], "",params:{platform:"web",playerBackend:"", "mediaplayer",playerType:"site"}){value,signature}}" ) })/{ "allow_source":true(), "allow_audio_only":true(), "allow_spectre":true(), "fast_bread"?:if ($path=("video","videos")) then () else true(), "p":(random-seed(),100000 + random(9900000)), "player_backend":"mediaplayer", "playlist_include_framerate":true(), "sig":signature, "token":value } )/xivid:m3u8-to-json(url) } }; declare function xivid:mixcloud($url as string) as object()? { let $decrypt:=function($arg as string) as string { let $key:=x:cps( "IFYOUWANTTHEARTISTSTOGETPAIDDONOTDOWNLOADFROMMIXCLOUD" ) return string-join( x:cps( for $x at $i in x:cps( binary-to-string(base64Binary($arg)) ) return xivid:bin-xor($x,$key[($i - 1) mod count($key) + 1]) ) ) }, $csrf:=x:request({"method":"HEAD","url":$url})/substring-before( substring-after(headers[contains(.,"csrftoken")],"="),";" ), $us:=tokenize(substring-after($url,"mixcloud.com/"),"/") return x:request({ "headers":( "Content-Type: application/json", "Referer: "||$url, "X-CSRFToken: "||$csrf, "Cookie: csrftoken="||$csrf ), "post":serialize( { "query":concat( "{cloudcastLookup(lookup:{username:"", $us[1],"",slug:"",$us[2], ""}){name,owner{displayName,url,username},", "publishDate,audioLength,streamInfo{hlsUrl,url}}}" ) }, {"method":"json"} ), "url":"https://www.mixcloud.com/graphql" })/json//cloudcastLookup/{ "name":`{owner/displayName} - {name}`, "date":dateTime(publishDate), "duration":audioLength * duration("PT1S"), "formats":array{ { "id":"pg-1", "format":"m4a[aac]", "url":$decrypt(streamInfo/url) }, xivid:m3u8-to-json($decrypt(streamInfo/hlsUrl))() } } }; declare function xivid:soundcloud($url as string) as object()? { parse-json( doc($url)//script/extract(.,"__sc_hydration = (.+);",1) )()[hydratable="sound"]/data/{ "name":`{user/(full_name,username)[.][1]} - {title}`, "date":created_at, "duration":round(duration div 1000) * duration("PT1S"), "formats":media/transcodings/array{ .()[format/protocol="progressive"]/map:merge(( { "id":"pg-1", "format":substring-before(preset,"_") }, request-combine( url,{"client_id":"RCfT93M4biAV6sjNiab6pMV1eYEgatjk"} )/json-doc(url)/{ "bitrate":extract(url,"\.(\d+)\.",1)||"kbps", "url":url } )), for $x at $i in .()[format/protocol="hls"] let $a_url:=request-combine( $x/url,{"client_id":"RCfT93M4biAV6sjNiab6pMV1eYEgatjk"} )/json-doc(url)/url, $br:=extract($a_url,"\.(\d+)\.",1) order by $br count $i return { "id":"hls-"||$i, "format":`m3u8[{substring-before($x/preset,"_")}]`, "bitrate":$br||"kbps", "url":$a_url } } } }; declare function xivid:facebook($url as string) as object()? { doc($url)/map:merge(( { "name":"Facebook: "||//meta[@property="og:title"]/@content, "date":adjust-dateTime-to-timezone( dateTime( parse-json( //script[@type="application/ld+json"] )/substring(dateCreated,1,22)||":00" ), duration("PT0S") ) }, //script/extract(.,"ScheduledApplyEach,(.+?)\);",1)[.] ! parse-json(.)[.//playable_url]//media/{ "duration":round(playable_duration_in_ms div 1000) * duration("PT1S"), "formats":array{ (video_available_captions_locales)()/{ "id":"sub-1", "format":"srt", "language":locale, "label":localized_language, "url":captions_url }[url], (playable_url,playable_url_quality_hd) ! { "id":"pg-"||position(), "format":"mp4[h264+aac]", "url":. }, xivid:mpd-to-json(parse-xml(dash_manifest))() } } )) }; declare function xivid:twitter($url as string) as object()? { let $bearer_token:="Authorization: Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejR"|| "COuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA", $guest_token:=x:request({ "method":"POST", "headers":$bearer_token, "url":"https://api.twitter.com/1.1/guest/activate.json" })/json/guest_token return x:request({ "headers":($bearer_token,"x-guest-token: "||$guest_token), "url":request-combine( "https://twitter.com/i/api/graphql/0hWvDhmW8YQ-S_ib3azIrw/TweetResultByRestId", { "variables":serialize( { "tweetId":extract($url,"\d+$"), "withCommunity":false, "includePromotedContent":false, "withVoice":false }, {"method":"json"} ), "features":serialize( { "creator_subscriptions_tweet_preview_api_enabled":true, "tweetypie_unmention_optimization_enabled":true, "responsive_web_edit_tweet_api_enabled":true, "graphql_is_translatable_rweb_tweet_is_translatable_enabled":true, "view_counts_everywhere_api_enabled":true, "longform_notetweets_consumption_enabled":true, "responsive_web_twitter_article_tweet_consumption_enabled":false, "tweet_awards_web_tipping_enabled":false, "freedom_of_speech_not_reach_fetch_enabled":true, "standardized_nudges_misinfo":true, "tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled":true, "longform_notetweets_rich_text_read_enabled":true, "longform_notetweets_inline_media_enabled":true, "responsive_web_graphql_exclude_directive_enabled":true, "verified_phone_label_enabled":false, "responsive_web_media_download_video_enabled":false, "responsive_web_graphql_skip_user_profile_image_extensions_enabled":false, "responsive_web_graphql_timeline_navigation_enabled":true, "responsive_web_enhance_cards_enabled":false }, {"method":"json"} ) } )/url })/json/data/tweetResult/result/{ "name":`Twitter: {core/user_results/result/legacy/screen_name} - {legacy/full_text}`, "date":parse-ietf-date(legacy/created_at), "duration":round( legacy/extended_entities/(media)()/video_info/duration_millis div 1000 ) * duration("PT1S"), "formats":legacy/extended_entities/(media)()/video_info/array{ for $x at $i in (variants)()[content_type="video/mp4"] order by $x/bitrate count $i return { "id":"pg-"||$i, "format":"mp4[h264+aac]", "resolution":extract($x/url,"\d+x\d+"), "bitrate":$x/bitrate div 1000||"kbps", "url":$x/url }, xivid:m3u8-to-json((variants)()[content_type="application/x-mpegURL"]/url)() } } }; declare function xivid:instagram($url as string) as object()? { parse-json( doc($url)//script/extract(.,"_sharedData = (.+);",1)[.] )//shortcode_media/{ "name":"Instagram: "||edge_media_to_caption//text, "date":taken_at_timestamp * duration("PT1S") + dateTime("1970-01-01T00:00:00Z"), "duration":round(video_duration) * duration("PT1S"), "formats":array{ { "id":"pg-1", "format":"mp4[h264+aac]", "resolution":dimensions/`{height}x{width}`, "url":video_url } } } }; declare function xivid:pornhub($url as string) as object()? { doc( if (contains($url,"/embed/")) then replace($url,"/embed/","/view_video.php?viewkey=") else $url )/map:put( if (exists(//script[@type="application/ld+json"])) then parse-json(//script[@type="application/ld+json"])/{ "name":"Pornhub: "||parse-html(name), "date":dateTime(uploadDate), "duration":duration(duration) } else parse-json( extract(//div[@id="player"]/script[1],"flashvars_\d+ = (.+);",1) )/{ "name":"Pornhub: "||video_title, "date":dateTime( date(replace(image_url,".+(\d{4})(\d{2})/(\d{2}).+","$1-$2-$3")), time("00:00:00Z") ), "duration":duration(video_duration * duration("PT1S")) }, "formats", let $fmts:=tokenize( replace(//div[@id="player"]/script[1],"" \+ "|"",""), "flashvars.+?;" )[contains(.,"var media_")][position() ge last() - 1] ! string-join( for $var in extract(.,"\*/(\w+)",1,"*") return substring-before(substring-after(.,$var||"="),";") ) return array{ { "id":"sub-1", "format":"srt", "url":parse-json( extract(//div[@id="player"]/script[1],"flashvars_\d+ = (.+);",1) )/resolve-uri(closedCaptionsFile,$url) }[url], for $x at $i in json-doc($fmts[2])() return { "id":"pg-"||$i, "format":"mp4[h264+aac]", "resolution":("426x240","854x480","1280x720","1920x1080")[$i], "url":$x/videoUrl }, xivid:m3u8-to-json($fmts[1])() } ) }; declare function xivid:xhamster($url as string) as object()? { parse-json( extract(doc($url)//script[@id="initials-script"],"\{.+\}") )/{ "name":"xHamster: "||videoModel/title, "date":videoModel/created * duration("PT1S") + dateTime("1970-01-01T00:00:00Z"), "duration":videoModel/duration * duration("PT1S"), "formats":xplayerSettings/sources/standard/mp4/array{ .()[label != "auto"]/{ "id":"pg-"||position(), "format":"mp4[h264+aac]", "resolution":{ "144p":"256x144","240p":"426x240","480p":"854x480", "720p":"1280x720","1080p":"1920x1080","2160p":"3840x2160" }(quality), "url":url }, xivid:m3u8-to-json(.()[label = "auto"]/url)() } } }; declare function xivid:youporn($url as string) as object()? { doc($url)/map:put( parse-json((//script[@type="application/ld+json"])[1])/{ "name":"YouPorn: "||name, "date":dateTime(uploadDate||"T00:00:00Z"), "duration":duration(duration) }, "formats", array{ for $x at $i in parse-json( extract(//script,"mediaDefinition: (.+),",1) )(1)/json-doc(resolve-uri(videoUrl,$url))()[format="mp4"] order by $x/quality count $i return { "id":"pg-"||$i, "format":"mp4[h264+aac]", "resolution":("426x240","854x480","1280x720","1920x1080")[$i], "url":$x/videoUrl } } ) };