1
0

youtube.php 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723
  1. <?php
  2. //$yt = new youtube();
  3. //header("Content-Type: application/json");
  4. //echo json_encode($yt->video("minecraft", null, "today", "any", "any", "live", "relevance"));
  5. class youtube{
  6. public function __construct(){
  7. include "lib/nextpage.php";
  8. $this->nextpage = new nextpage("yt");
  9. }
  10. public function getfilters($page){
  11. if($page != "videos"){
  12. return [];
  13. }
  14. return [
  15. "date" => [
  16. "display" => "Time posted",
  17. "option" => [
  18. "any" => "Any time",
  19. "hour" => "Last hour",
  20. "today" => "Today",
  21. "week" => "This week",
  22. "month" => "This month",
  23. "year" => "This year"
  24. ]
  25. ],
  26. "type" => [
  27. "display" => "Type",
  28. "option" => [
  29. "video" => "Video",
  30. "channel" => "Channel",
  31. "playlist" => "Playlist",
  32. "Movie" => "Movie"
  33. ]
  34. ],
  35. "duration" => [
  36. "display" => "Duration",
  37. "option" => [
  38. "any" => "Any duration",
  39. "short" => "Short (>4min)",
  40. "medium" => "Medium (4-20min)",
  41. "long" => "Long (<20min)"
  42. ]
  43. ],
  44. "feature" => [
  45. "display" => "Feature",
  46. "option" => [
  47. "any" => "No features",
  48. "live" => "Live",
  49. "4k" => "4K",
  50. "hd" => "HD",
  51. "subtitles" => "Subtitles/CC",
  52. "creativecommons" => "Creative Commons",
  53. "360" => "VR 360°",
  54. "vr180" => "VR 180°",
  55. "3d" => "3D",
  56. "hdr" => "HDR"
  57. ]
  58. ],
  59. "sort" => [
  60. "display" => "Sort by",
  61. "option" => [
  62. "relevance" => "Relevance",
  63. "upload_date" => "Upload date",
  64. "view_count" => "View count",
  65. "rating" => "Rating"
  66. ]
  67. ]
  68. ];
  69. }
  70. private function ytfilter($date, $type, $duration, $feature, $sort){
  71. // ------------
  72. // INCOMPATIBLE FILTERS
  73. // channel,playlist DURATION, FEATURES, SORT BY
  74. // Movie Features=[live, subtitles, creative commons, 3d]
  75. // live, 3D
  76. // Type[channel, playlist, movie]
  77. // UPLOAD DATE, DURATION, 4k, 360, VR180, HDR
  78. // Type[channel, playlist]
  79. // -----------
  80. // MUST BE TOGETHER
  81. // Relevance,upload date Type=Video
  82. switch($type){
  83. case "channel":
  84. case "playlist":
  85. if($duration != "any"){ $duration = "any"; }
  86. if($feature != "any"){ $feature = "any"; }
  87. if($sort != "any"){ $sort = "any"; }
  88. break;
  89. case "movie":
  90. if(
  91. in_array(
  92. $feature,
  93. [
  94. "live",
  95. "subtitles",
  96. "creative_commons",
  97. "3d"
  98. ],
  99. )
  100. ){
  101. $feature = "any";
  102. }
  103. break;
  104. }
  105. switch($feature){
  106. case "live":
  107. case "3d":
  108. if(
  109. in_array(
  110. $type,
  111. [
  112. "channel",
  113. "playlist",
  114. "movie"
  115. ],
  116. )
  117. ){
  118. $type = "video";
  119. }
  120. break;
  121. }
  122. if(
  123. (
  124. $date != "any" ||
  125. $duration != "any" ||
  126. $feature == "4k" ||
  127. $feature == "360" ||
  128. $feature == "vr180" ||
  129. $feature == "hdr"
  130. ) &&
  131. (
  132. $type == "channel" ||
  133. $type == "playlist"
  134. )
  135. ){
  136. $type = "video";
  137. }
  138. if(
  139. $date == "any" &&
  140. $type == "video" &&
  141. $duration == "any" &&
  142. $feature == "any" &&
  143. $sort == "relevance"
  144. ){
  145. return null;
  146. }
  147. //print_r([$date, $type, $duration, $feature, $sort]);
  148. /*
  149. Encode hex data
  150. */
  151. // UPLOAD DATE
  152. // hour EgQIARAB 12 04 08 01 10 01
  153. // today EgQIAhAB 12 04 08 02 10 01
  154. // week EgQIAxAB 12 04 08 03 10 01
  155. // month EgQIBBAB 12 04 08 04 10 01
  156. // year EgQIBRAB 12 04 08 05 10 01
  157. // TYPE
  158. // video EgIQAQ%253D%253D 12 02 10 01
  159. // channel EgIQAg%253D%253D 12 02 10 02
  160. // playlist EgIQAw%253D%253D 12 02 10 03
  161. // movie EgIQBA%253D%253D 12 02 10 04
  162. // DURATION
  163. // -4min EgIYAQ%253D%253D 12 02 18 01
  164. // 4-20min EgIYAw%253D%253D 12 02 18 03
  165. // 20+min EgIYAg%253D%253D 12 02 18 02
  166. // FEATURE
  167. // live EgJAAQ%253D%253D 12 02 40 01
  168. // 4K EgJwAQ%253D%253D 12 02 70 01
  169. // HD EgIgAQ%253D%253D 12 02 20 01
  170. // Subtitles/CC EgIoAQ%253D%253D 12 02 28 01
  171. // Creative Commons EgIwAQ%253D%253D 12 02 30 01
  172. // 360 EgJ4AQ%253D%253D 12 02 78 01
  173. // VR180 EgPQAQE%253D 12 03 d0 01 01
  174. // 3D EgI4AQ%253D%253D 12 02 38 01
  175. // HDR EgPIAQE%253D 12 03 c8 01 01
  176. // (location & purchased unused)
  177. // SORT BY
  178. // Relevance CAASAhAB 08 00 12 02 10 01 (is nothing by default)
  179. // Upload date CAI%253D 08 02
  180. // View count CAM%253D 08 03
  181. // Rating CAE%253D 08 01
  182. // video
  183. // 12 02 10 01
  184. // under 4 minutes
  185. // 12 02 18 01
  186. // video + under 4 minutes
  187. // 12 04 10 01 18 01
  188. // video + under 4 minutes + HD
  189. // 08 00 12 06 10 01 18 01 20 01
  190. // video + under 4 minutes + upload date
  191. // 08 02 12 04 10 01 18 01
  192. // video + under 4 minutes + HD + upload date
  193. // 08 02 12 06 10 01 18 01 20 01
  194. // this year + video + under 4 minutes + HD + upload date
  195. // 08 02 12 08 08 05 10 01 18 01 20 01
  196. // this week + video + over 20 minutes + HD + view count
  197. // 08 03 12 08 08 03 10 01 18 02 20 01
  198. //echo urlencode(urlencode(base64_encode(hex2bin($str))));
  199. //echo bin2hex(base64_decode(urldecode(urldecode("CAI%253D"))));
  200. // week + video + 20min + rating
  201. // 08 01 12 06 08 03 10 01 18 02
  202. // week + video + 20min + live + rating
  203. // 08 01 12 08 08 03 10 01 18 02 40 01
  204. // live 12 02 40 01
  205. $hex = null;
  206. if(
  207. $date == "any" &&
  208. $type == "video" &&
  209. $duration == "any" &&
  210. $feature == "any" &&
  211. $sort == "relevance"
  212. ){
  213. return $hex;
  214. }
  215. $opcode = 0;
  216. if($date != "any"){ $opcode += 2; }
  217. if($type != "any"){ $opcode += 2; }
  218. if($duration != "any"){ $opcode += 2; }
  219. switch($feature){
  220. case "live":
  221. case "4k":
  222. case "hd":
  223. case "subtitles":
  224. case "creativecommons":
  225. case "360":
  226. case "3d":
  227. $opcode += 2;
  228. break;
  229. case "hdr":
  230. case "vr180":
  231. $opcode += 3;
  232. break;
  233. }
  234. switch($sort){
  235. case "relevance": $hex .= "0800"; break;
  236. case "upload_date": $hex .= "0802"; break;
  237. case "view_count": $hex .= "0803"; break;
  238. case "rating": $hex .= "0801"; break;
  239. }
  240. $hex .= "12" . "0".$opcode;
  241. switch($date){
  242. case "hour": $hex .= "0801"; break;
  243. case "today": $hex .= "0802"; break;
  244. case "week": $hex .= "0803"; break;
  245. case "month": $hex .= "0804"; break;
  246. case "year": $hex .= "0805"; break;
  247. }
  248. switch($type){
  249. case "video": $hex .= "1001"; break;
  250. case "channel": $hex .= "1002"; break;
  251. case "playlist": $hex .= "1003"; break;
  252. case "movie": $hex .= "1004"; break;
  253. }
  254. switch($duration){
  255. case "short": $hex .= "1801"; break;
  256. case "medium": $hex .= "1803"; break;
  257. case "long": $hex .= "1802"; break;
  258. }
  259. switch($feature){
  260. case "live": $hex .= "4001"; break;
  261. case "4k": $hex .= "7001"; break;
  262. case "hd": $hex .= "2001"; break;
  263. case "subtitles": $hex .= "2801"; break;
  264. case "creativecommons": $hex .= "3001"; break;
  265. case "360": $hex .= "7801"; break;
  266. case "vr180": $hex .= "d00101"; break;
  267. case "3d": $hex .= "3801"; break;
  268. case "hdr": $hex .= "c80101"; break;
  269. }
  270. //echo $hex . "\n\n";
  271. return urlencode(base64_encode(hex2bin($hex)));
  272. }
  273. // me reading youtube's json
  274. // https://imgur.com/X9hVlFX
  275. const req_web = 0;
  276. const req_xhr = 1;
  277. private function get($url, $get = [], $reqtype = self::req_web, $continuation = null){
  278. $curlproc = curl_init();
  279. if($get !== []){
  280. $get = http_build_query($get);
  281. $url .= "?" . $get;
  282. }
  283. curl_setopt($curlproc, CURLOPT_URL, $url);
  284. switch($reqtype){
  285. case self::req_web:
  286. $headers =
  287. ["User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/110.0",
  288. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
  289. "Accept-Language: en-US,en;q=0.5",
  290. "Accept-Encoding: gzip",
  291. "Cookie: PREF=tz=America.New_York",
  292. "DNT: 1",
  293. "Connection: keep-alive",
  294. "Upgrade-Insecure-Requests: 1",
  295. "Sec-Fetch-Dest: document",
  296. "Sec-Fetch-Mode: navigate",
  297. "Sec-Fetch-Site: none",
  298. "Sec-Fetch-User: ?1"];
  299. break;
  300. case self::req_xhr:
  301. $headers =
  302. ["User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:110.0) Gecko/20100101 Firefox/110.0",
  303. "Accept: */*",
  304. "Accept-Language: en-US,en;q=0.5",
  305. "Accept-Encoding: gzip",
  306. "Cookie: PREF=tz=America.New_York",
  307. "Referer: https://youtube.com.com/",
  308. "Content-Type: application/json",
  309. "Content-Length: " . strlen($continuation),
  310. "DNT: 1",
  311. "Connection: keep-alive",
  312. "Sec-Fetch-Dest: empty",
  313. "Sec-Fetch-Mode: same-origin",
  314. "Sec-Fetch-Site: same-origin"];
  315. curl_setopt($curlproc, CURLOPT_POST, true);
  316. curl_setopt($curlproc, CURLOPT_POSTFIELDS, $continuation);
  317. break;
  318. }
  319. curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
  320. curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers);
  321. curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
  322. curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
  323. curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
  324. curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
  325. curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
  326. $data = curl_exec($curlproc);
  327. if(curl_errno($curlproc)){
  328. throw new Exception(curl_error($curlproc));
  329. }
  330. curl_close($curlproc);
  331. return $data;
  332. }
  333. public function video($get){
  334. $this->out = [
  335. "status" => "ok",
  336. "npt" => null,
  337. "video" => [],
  338. "author" => [],
  339. "livestream" => [],
  340. "playlist" => [],
  341. "reel" => []
  342. ];
  343. if($get["npt"]){
  344. // parse nextPage
  345. // https://www.youtube.com/youtubei/v1/search?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8&prettyPrint=false
  346. /*
  347. $handle = fopen("nextpage.json", "r");
  348. $json = fread($handle, filesize("nextpage.json"));
  349. fclose($handle);*/
  350. $npt =
  351. json_decode(
  352. $this->nextpage->get(
  353. $get["npt"],
  354. "videos"
  355. ),
  356. true
  357. );
  358. try{
  359. $json = $this->get(
  360. "https://www.youtube.com/youtubei/v1/search",
  361. [
  362. "key" => $npt["key"],
  363. "prettyPrint" => "false"
  364. ],
  365. self::req_xhr,
  366. json_encode($npt["post"])
  367. );
  368. }catch(Exception $error){
  369. throw new Exception("Could not fetch results page");
  370. }
  371. $json = json_decode($json);
  372. foreach(
  373. $json
  374. ->onResponseReceivedCommands[0]
  375. ->appendContinuationItemsAction
  376. ->continuationItems[0]
  377. ->itemSectionRenderer
  378. ->contents
  379. as $video
  380. ){
  381. $this->parsevideoobject($video);
  382. }
  383. if(
  384. !isset(
  385. $json
  386. ->onResponseReceivedCommands[0]
  387. ->appendContinuationItemsAction
  388. ->continuationItems[1]
  389. ->continuationItemRenderer
  390. ->continuationEndpoint
  391. ->continuationCommand
  392. ->token
  393. )
  394. ){
  395. $npt = null;
  396. }else{
  397. // prepare nextpage for later..
  398. $npt["post"]["continuation"] =
  399. $json
  400. ->onResponseReceivedCommands[0]
  401. ->appendContinuationItemsAction
  402. ->continuationItems[1]
  403. ->continuationItemRenderer
  404. ->continuationEndpoint
  405. ->continuationCommand
  406. ->token;
  407. }
  408. $this->out["npt"] = $npt;
  409. }else{
  410. $search = $get["s"];
  411. if(strlen($search) === 0){
  412. throw new Exception("Search term is empty!");
  413. }
  414. $date = $get["date"];
  415. $type = $get["type"];
  416. $duration = $get["duration"];
  417. $feature = $get["feature"];
  418. $sort = $get["sort"];
  419. // parse ytInitialData
  420. $get = [
  421. "search_query" => $search
  422. ];
  423. if(
  424. (
  425. $filter =
  426. $this->ytfilter(
  427. $date,
  428. $type,
  429. $duration,
  430. $feature,
  431. $sort
  432. )
  433. ) !== null
  434. ){
  435. $get["sp"] = $filter;
  436. }
  437. try{
  438. $json = $this->get(
  439. "https://www.youtube.com/results",
  440. $get
  441. );
  442. }catch(Exception $error){
  443. throw new Exception("Could not fetch results page");
  444. }
  445. /*
  446. $handle = fopen("test.html", "r");
  447. $json = fread($handle, filesize("test.html"));
  448. fclose($handle);
  449. */
  450. if(
  451. !preg_match(
  452. '/ytcfg\.set\(({".*})\); *window\.ytcfg/',
  453. $json,
  454. $ytconfig
  455. )
  456. ){
  457. throw new Exception("Could not get ytcfg");
  458. }
  459. $ytconfig = json_decode($ytconfig[1]);
  460. if(
  461. !preg_match(
  462. '/ytInitialData *= *({.*});<\/script>/',
  463. $json,
  464. $json
  465. )
  466. ){
  467. throw new Exception("Could not get ytInitialData");
  468. }
  469. $json = json_decode($json[1]);
  470. // generate POST data for nextpage
  471. $ytconfig->INNERTUBE_CONTEXT->client->screenWidthPoints = 1239;
  472. $ytconfig->INNERTUBE_CONTEXT->client->screenHeightPoints = 999;
  473. $ytconfig->INNERTUBE_CONTEXT->client->screenPixelDensity = 1;
  474. $ytconfig->INNERTUBE_CONTEXT->client->screenDensityFloat = 1;
  475. $ytconfig->INNERTUBE_CONTEXT->client->utcOffsetMinutes = -240;
  476. $ytconfig->INNERTUBE_CONTEXT->request->internalExperimentFlags = [];
  477. $ytconfig->INNERTUBE_CONTEXT->request->consistencyTokenJars = [];
  478. $ytconfig->INNERTUBE_CONTEXT->client->mainAppWebInfo = [
  479. "graftUrl" => $ytconfig->INNERTUBE_CONTEXT->client->originalUrl,
  480. "webDisplayMode" => "WEB_DISPLAY_MODE_BROWSER",
  481. "isWebNativeShareAvailable" => false
  482. ];
  483. $ytconfig->INNERTUBE_CONTEXT->adSignalsInfo = [
  484. "params" => [
  485. [
  486. "key" => "dt",
  487. "value" => (string)$ytconfig->TIME_CREATED_MS
  488. ],
  489. [
  490. "key" => "flash",
  491. "value" => "0"
  492. ],
  493. [
  494. "key" => "frm",
  495. "value" => "0"
  496. ],
  497. [
  498. "key" => "u_tz",
  499. "value" => "-240"
  500. ],
  501. [
  502. "key" => "u_his",
  503. "value" => "3"
  504. ],
  505. [
  506. "key" => "u_h",
  507. "value" => "1080"
  508. ],
  509. [
  510. "key" => "u_w",
  511. "value" => "1920"
  512. ],
  513. [
  514. "key" => "u_ah",
  515. "value" => "1080"
  516. ],
  517. [
  518. "key" => "u_cd",
  519. "value" => "24"
  520. ],
  521. [
  522. "key" => "bc",
  523. "value" => "31"
  524. ],
  525. [
  526. "key" => "bih",
  527. "value" => "999"
  528. ],
  529. [
  530. "key" => "biw",
  531. "value" => "1239"
  532. ],
  533. [
  534. "key" => "brdim",
  535. "value" => "0,0,0,0,1920,0,1920,1061,1239,999"
  536. ],
  537. [
  538. "key" => "vis",
  539. "value" => "1"
  540. ],
  541. [
  542. "key" => "wgl",
  543. "value" => "true"
  544. ],
  545. [
  546. "key" => "ca_type",
  547. "value" => "image"
  548. ]
  549. ]
  550. ];
  551. /*
  552. echo json_encode($json);
  553. die();*/
  554. // *inhales*
  555. foreach(
  556. $json
  557. ->contents
  558. ->twoColumnSearchResultsRenderer
  559. ->primaryContents
  560. ->sectionListRenderer
  561. ->contents[0]
  562. ->itemSectionRenderer
  563. ->contents
  564. as $video
  565. ){
  566. $this->parsevideoobject($video);
  567. }
  568. // get additional data from secondaryContents
  569. if(
  570. isset(
  571. $json
  572. ->contents
  573. ->twoColumnSearchResultsRenderer
  574. ->secondaryContents
  575. ->secondarySearchContainerRenderer
  576. ->contents[0]
  577. ->universalWatchCardRenderer
  578. )
  579. ){
  580. $video =
  581. $json
  582. ->contents
  583. ->twoColumnSearchResultsRenderer
  584. ->secondaryContents
  585. ->secondarySearchContainerRenderer
  586. ->contents[0]
  587. ->universalWatchCardRenderer;
  588. /*
  589. echo json_encode($video);
  590. die();*/
  591. $author =
  592. [
  593. "name" =>
  594. $video
  595. ->header
  596. ->watchCardRichHeaderRenderer
  597. ->title
  598. ->simpleText,
  599. "url" =>
  600. "https://www.youtube.com/channel/" .
  601. $video
  602. ->header
  603. ->watchCardRichHeaderRenderer
  604. ->titleNavigationEndpoint
  605. ->browseEndpoint
  606. ->browseId,
  607. "avatar" => null
  608. ];
  609. if(
  610. isset(
  611. $video
  612. ->header
  613. ->watchCardRichHeaderRenderer
  614. ->avatar
  615. ->thumbnails[0]
  616. ->url
  617. )
  618. ){
  619. $author["avatar"] =
  620. $video
  621. ->header
  622. ->watchCardRichHeaderRenderer
  623. ->avatar
  624. ->thumbnails[0]
  625. ->url;
  626. }
  627. // add video in callToAction if present
  628. if(
  629. isset(
  630. $video
  631. ->callToAction
  632. ->watchCardHeroVideoRenderer
  633. ->lengthText
  634. )
  635. ){
  636. array_push(
  637. $this->out["video"],
  638. [
  639. "title" =>
  640. $video
  641. ->callToAction
  642. ->watchCardHeroVideoRenderer
  643. ->title
  644. ->simpleText,
  645. "description" => null,
  646. "author" => $author,
  647. "date" =>
  648. $this->textualdate2unix(
  649. trim(
  650. explode(
  651. "•",
  652. $video
  653. ->callToAction
  654. ->watchCardHeroVideoRenderer
  655. ->subtitle
  656. ->simpleText
  657. )[2]
  658. )
  659. ),
  660. "duration" =>
  661. $this->hms2int(
  662. $video
  663. ->callToAction
  664. ->watchCardHeroVideoRenderer
  665. ->lengthText
  666. ->simpleText
  667. ),
  668. "views" =>
  669. $this->truncatedcount2int(
  670. trim(
  671. explode(
  672. "•",
  673. $video
  674. ->callToAction
  675. ->watchCardHeroVideoRenderer
  676. ->subtitle
  677. ->simpleText,
  678. 2
  679. )[1]
  680. )
  681. ),
  682. "thumb" => [
  683. "url" =>
  684. $video
  685. ->callToAction
  686. ->watchCardHeroVideoRenderer
  687. ->heroImage
  688. ->singleHeroImageRenderer
  689. ->thumbnail
  690. ->thumbnails[0]
  691. ->url,
  692. "ratio" => "16:9"
  693. ],
  694. "url" =>
  695. "https://www.youtube.com/watch?v=" .
  696. $video
  697. ->callToAction
  698. ->watchCardHeroVideoRenderer
  699. ->navigationEndpoint
  700. ->watchEndpoint
  701. ->videoId
  702. ]
  703. );
  704. }
  705. // get all playlists, ignore videos
  706. $out = null;
  707. foreach(
  708. $video
  709. ->sections
  710. as $section
  711. ){
  712. if(
  713. isset(
  714. $section
  715. ->watchCardSectionSequenceRenderer
  716. ->lists[0]
  717. ->horizontalCardListRenderer
  718. ->cards
  719. )
  720. ){
  721. $out =
  722. $section
  723. ->watchCardSectionSequenceRenderer
  724. ->lists[0]
  725. ->horizontalCardListRenderer
  726. ->cards;
  727. break;
  728. }
  729. }
  730. if($out !== null){
  731. foreach(
  732. $out as $video
  733. ){
  734. if(
  735. !isset(
  736. $video
  737. ->searchRefinementCardRenderer
  738. )
  739. ){
  740. continue;
  741. }
  742. $video =
  743. $video
  744. ->searchRefinementCardRenderer;
  745. array_push(
  746. $this->out["playlist"],
  747. [
  748. "title" =>
  749. $video
  750. ->query
  751. ->runs[0]
  752. ->text,
  753. "description" => null,
  754. "author" => $author,
  755. "date" => null,
  756. "duration" => null,
  757. "views" => null,
  758. "thumb" => [
  759. "url" =>
  760. $video
  761. ->thumbnail
  762. ->thumbnails[0]
  763. ->url,
  764. "ratio" => "1:1"
  765. ],
  766. "url" =>
  767. "https://www.youtube.com" .
  768. $video
  769. ->searchEndpoint
  770. ->commandMetadata
  771. ->webCommandMetadata
  772. ->url
  773. ]
  774. );
  775. }
  776. }
  777. }
  778. foreach(
  779. $json
  780. ->contents
  781. ->twoColumnSearchResultsRenderer
  782. ->primaryContents
  783. ->sectionListRenderer
  784. ->contents
  785. as $cont
  786. ){
  787. if(isset($cont->continuationItemRenderer)){
  788. $this->out["npt"] = [
  789. "key" =>
  790. $ytconfig
  791. ->INNERTUBE_API_KEY,
  792. "post" => [
  793. "context" =>
  794. $ytconfig
  795. ->INNERTUBE_CONTEXT,
  796. "continuation" =>
  797. $cont
  798. ->continuationItemRenderer
  799. ->continuationEndpoint
  800. ->continuationCommand
  801. ->token
  802. ]
  803. ];
  804. break;
  805. }
  806. }
  807. }
  808. if($this->out["npt"] !== null){
  809. $this->out["npt"] = $this->nextpage->store(json_encode($this->out["npt"]), "videos");
  810. }
  811. return $this->out;
  812. }
  813. private function parsevideoobject($video){
  814. if(isset($video->videoRenderer)){
  815. $video = $video->videoRenderer;
  816. $description = null;
  817. if(isset($video->detailedMetadataSnippets)){
  818. foreach(
  819. $video
  820. ->detailedMetadataSnippets[0]
  821. ->snippetText
  822. ->runs
  823. as $description_part
  824. ){
  825. $description .= $description_part->text;
  826. }
  827. }
  828. if(
  829. isset(
  830. $video
  831. ->badges[0]
  832. ->metadataBadgeRenderer
  833. ->icon
  834. ->iconType
  835. ) &&
  836. $video
  837. ->badges[0]
  838. ->metadataBadgeRenderer
  839. ->icon
  840. ->iconType
  841. == "LIVE"
  842. ){
  843. $type = "livestream";
  844. $date = null;
  845. $duration = "_LIVE";
  846. if(isset($video->viewCountText->runs[0]->text)){
  847. $views =
  848. $this->views2int(
  849. $video
  850. ->viewCountText
  851. ->runs[0]
  852. ->text
  853. );
  854. }else{
  855. $views = null;
  856. }
  857. }else{
  858. $type = "video";
  859. if(isset($video->publishedTimeText->simpleText)){
  860. $date = $this->textualdate2unix(
  861. $video
  862. ->publishedTimeText
  863. ->simpleText
  864. );
  865. }else{
  866. $date = null;
  867. }
  868. if(isset($video->lengthText->simpleText)){
  869. $duration =
  870. $this->hms2int(
  871. $video
  872. ->lengthText
  873. ->simpleText
  874. );
  875. }else{
  876. $duration = null;
  877. }
  878. if(isset($video->viewCountText->simpleText)){
  879. $views =
  880. $this->views2int(
  881. $video
  882. ->viewCountText
  883. ->simpleText
  884. );
  885. }else{
  886. $views = null;
  887. }
  888. }
  889. if(
  890. $video
  891. ->navigationEndpoint
  892. ->commandMetadata
  893. ->webCommandMetadata
  894. ->webPageType
  895. == "WEB_PAGE_TYPE_SHORTS"
  896. ){
  897. // haha you thought you could get me, youtube
  898. // jokes on you i dont go outside
  899. $type = "reel";
  900. }
  901. array_push(
  902. $this->out[$type],
  903. [
  904. "title" =>
  905. $video
  906. ->title
  907. ->runs[0]
  908. ->text,
  909. "description" =>
  910. $this->titledots($description),
  911. "author" => [
  912. "name" =>
  913. $video
  914. ->longBylineText
  915. ->runs[0]
  916. ->text,
  917. "url" =>
  918. "https://www.youtube.com/channel/" .
  919. $video
  920. ->longBylineText
  921. ->runs[0]
  922. ->navigationEndpoint
  923. ->browseEndpoint
  924. ->browseId,
  925. "avatar" =>
  926. $this->checkhttpspresence(
  927. $video
  928. ->channelThumbnailSupportedRenderers
  929. ->channelThumbnailWithLinkRenderer
  930. ->thumbnail
  931. ->thumbnails[0]
  932. ->url
  933. )
  934. ],
  935. "date" => $date,
  936. "duration" => $duration,
  937. "views" => $views,
  938. "thumb" => [
  939. "url" =>
  940. $video
  941. ->thumbnail
  942. ->thumbnails[0]
  943. ->url,
  944. "ratio" => "16:9"
  945. ],
  946. "url" =>
  947. "https://www.youtube.com/watch?v=" .
  948. $video
  949. ->videoId
  950. ]
  951. );
  952. }elseif(isset($video->watchCardCompactVideoRenderer)){
  953. $video =
  954. $video
  955. ->watchCardCompactVideoRenderer;
  956. array_push(
  957. $this->out["video"],
  958. [
  959. "title" =>
  960. $video
  961. ->title
  962. ->simpleText,
  963. "description" => null,
  964. "author" => [
  965. "name" =>
  966. $video
  967. ->byline
  968. ->runs[0]
  969. ->text,
  970. "url" =>
  971. "https://www.youtube.com/channel/" .
  972. $video
  973. ->byline
  974. ->runs[0]
  975. ->navigationEndpoint
  976. ->browseEndpoint
  977. ->browseId,
  978. "avatar" => null
  979. ],
  980. "date" =>
  981. $this->textualdate2unix(
  982. trim(
  983. explode(
  984. "•",
  985. $video
  986. ->subtitle
  987. ->simpleText,
  988. 2
  989. )[1]
  990. )
  991. ),
  992. "duration" =>
  993. $this->hms2int(
  994. $video
  995. ->lengthText
  996. ->simpleText
  997. ),
  998. "views" =>
  999. $this->truncatedcount2int(
  1000. trim(
  1001. explode(
  1002. "•",
  1003. $video
  1004. ->subtitle
  1005. ->simpleText,
  1006. 2
  1007. )[0]
  1008. )
  1009. ),
  1010. "thumb" => [
  1011. "url" =>
  1012. $video
  1013. ->thumbnail
  1014. ->thumbnails[0]
  1015. ->url,
  1016. "ratio" => "16:9"
  1017. ],
  1018. "url" =>
  1019. "https://www.youtube.com/watch?v=" .
  1020. $video
  1021. ->navigationEndpoint
  1022. ->watchEndpoint
  1023. ->videoId
  1024. ]
  1025. );
  1026. }elseif(isset($video->reelShelfRenderer)){
  1027. foreach(
  1028. $video
  1029. ->reelShelfRenderer
  1030. ->items
  1031. as $reel
  1032. ){
  1033. $reel =
  1034. $reel
  1035. ->reelItemRenderer;
  1036. array_push(
  1037. $this->out["reel"],
  1038. [
  1039. "title" =>
  1040. $reel
  1041. ->headline
  1042. ->simpleText,
  1043. "description" => null,
  1044. "author" => [
  1045. "name" => null,
  1046. "url" => null,
  1047. "avatar" => null
  1048. ],
  1049. "date" => null,
  1050. "duration" =>
  1051. $this->textualtime2int(
  1052. $reel
  1053. ->accessibility
  1054. ->accessibilityData
  1055. ->label
  1056. ),
  1057. "views" =>
  1058. $this->truncatedcount2int(
  1059. $reel
  1060. ->viewCountText
  1061. ->simpleText
  1062. ),
  1063. "thumb" => [
  1064. "url" =>
  1065. $reel
  1066. ->thumbnail
  1067. ->thumbnails[0]
  1068. ->url,
  1069. "ratio" => "9:16"
  1070. ],
  1071. "url" =>
  1072. "https://www.youtube.com/watch?v=" .
  1073. $reel
  1074. ->videoId
  1075. ]
  1076. );
  1077. }
  1078. }
  1079. elseif(isset($video->channelRenderer)){
  1080. $video = $video->channelRenderer;
  1081. $description = null;
  1082. if(isset($video->descriptionSnippet)){
  1083. foreach(
  1084. $video
  1085. ->descriptionSnippet
  1086. ->runs
  1087. as $description_part
  1088. ){
  1089. $description .= $description_part->text;
  1090. }
  1091. }
  1092. array_push(
  1093. $this->out["author"],
  1094. [
  1095. "title" =>
  1096. $video
  1097. ->title
  1098. ->simpleText,
  1099. "followers" =>
  1100. isset(
  1101. $video
  1102. ->videoCountText
  1103. ->simpleText
  1104. ) ?
  1105. $this->truncatedcount2int(
  1106. $video
  1107. ->videoCountText
  1108. ->simpleText
  1109. ) :
  1110. 0,
  1111. "description" => $this->titledots($description),
  1112. "thumb" =>
  1113. [
  1114. "url" =>
  1115. $this->checkhttpspresence(
  1116. $video
  1117. ->thumbnail
  1118. ->thumbnails[
  1119. count(
  1120. $video
  1121. ->thumbnail
  1122. ->thumbnails
  1123. ) - 1
  1124. ]
  1125. ->url
  1126. ),
  1127. "ratio" => "1:1"
  1128. ],
  1129. "url" =>
  1130. "https://www.youtube.com/channel/" .
  1131. $video
  1132. ->channelId
  1133. ]
  1134. );
  1135. }
  1136. elseif(isset($video->shelfRenderer)){
  1137. if(
  1138. !is_object(
  1139. $video
  1140. ->shelfRenderer
  1141. ->content
  1142. ->verticalListRenderer
  1143. )
  1144. ){
  1145. return;
  1146. }
  1147. foreach(
  1148. $video
  1149. ->shelfRenderer
  1150. ->content
  1151. ->verticalListRenderer
  1152. ->items
  1153. as $shelfvideo
  1154. ){
  1155. $this->parsevideoobject($shelfvideo);
  1156. }
  1157. }elseif(isset($video->radioRenderer)){
  1158. $video = $video->radioRenderer;
  1159. $description =
  1160. $video
  1161. ->videoCountText
  1162. ->runs[0]
  1163. ->text
  1164. . ".";
  1165. $tmp = [];
  1166. foreach(
  1167. $video->videos
  1168. as $childvideo
  1169. ){
  1170. $tmp[] =
  1171. $childvideo
  1172. ->childVideoRenderer
  1173. ->title
  1174. ->simpleText;
  1175. }
  1176. if(count($tmp) !== 0){
  1177. $description .=
  1178. " " . implode(", ", $tmp);
  1179. }
  1180. array_push(
  1181. $this->out["playlist"],
  1182. [
  1183. "title" =>
  1184. $video
  1185. ->title
  1186. ->simpleText,
  1187. "description" => $description,
  1188. "author" => [
  1189. "name" =>
  1190. $video
  1191. ->longBylineText
  1192. ->simpleText,
  1193. "url" => null,
  1194. "avatar" => null
  1195. ],
  1196. "date" => null,
  1197. "duration" => null,
  1198. "views" => null,
  1199. "thumb" => [
  1200. "url" =>
  1201. $video
  1202. ->thumbnail
  1203. ->thumbnails[
  1204. count(
  1205. $video
  1206. ->thumbnail
  1207. ->thumbnails
  1208. ) - 1
  1209. ]
  1210. ->url,
  1211. "ratio" => "16:9"
  1212. ],
  1213. "url" =>
  1214. "https://www.youtube.com/watch?v=" .
  1215. $video
  1216. ->videos[0]
  1217. ->childVideoRenderer
  1218. ->videoId .
  1219. "&list=" .
  1220. $video
  1221. ->playlistId .
  1222. "&start_radio=1"
  1223. ]
  1224. );
  1225. }elseif(isset($video->playlistRenderer)){
  1226. $video = $video->playlistRenderer;
  1227. $description = $video->videoCount . " videos.";
  1228. $tmp = [];
  1229. foreach(
  1230. $video
  1231. ->videos
  1232. as $childvideo
  1233. ){
  1234. $tmp[] =
  1235. $childvideo
  1236. ->childVideoRenderer
  1237. ->title
  1238. ->simpleText;
  1239. }
  1240. if(count($tmp) !== 0){
  1241. $description .=
  1242. " " . implode(", ", $tmp);
  1243. }
  1244. array_push(
  1245. $this->out["playlist"],
  1246. [
  1247. "title" =>
  1248. $video
  1249. ->title
  1250. ->simpleText,
  1251. "description" => $description,
  1252. "author" => [
  1253. "name" =>
  1254. $video
  1255. ->longBylineText
  1256. ->runs[0]
  1257. ->text,
  1258. "url" =>
  1259. "https://www.youtube.com/channel/" .
  1260. $video
  1261. ->longBylineText
  1262. ->runs[0]
  1263. ->navigationEndpoint
  1264. ->browseEndpoint
  1265. ->browseId,
  1266. "picture" => null
  1267. ],
  1268. "date" => null,
  1269. "duration" => null,
  1270. "views" => null,
  1271. "thumb" =>
  1272. [
  1273. "url" =>
  1274. $video
  1275. ->thumbnails[0]
  1276. ->thumbnails[
  1277. count(
  1278. $video
  1279. ->thumbnails[0]
  1280. ->thumbnails
  1281. ) - 1
  1282. ]
  1283. ->url,
  1284. "ratio" => "16:9"
  1285. ],
  1286. "url" =>
  1287. "https://www.youtube.com/watch?v=" .
  1288. $video
  1289. ->videos[0]
  1290. ->childVideoRenderer
  1291. ->videoId .
  1292. "&list=" .
  1293. $video
  1294. ->playlistId .
  1295. "&start_radio=1"
  1296. ]
  1297. );
  1298. }/*else{
  1299. if(!isset($video->searchPyvRenderer)){
  1300. echo json_encode($video);
  1301. die();}
  1302. }*/
  1303. }
  1304. private function textualdate2unix($number){
  1305. $number =
  1306. explode(
  1307. " ",
  1308. str_replace(
  1309. [
  1310. " ago",
  1311. "seconds",
  1312. "minutes",
  1313. "hours",
  1314. "days",
  1315. "weeks",
  1316. "months",
  1317. "years"
  1318. ],
  1319. [
  1320. "",
  1321. "second",
  1322. "minute",
  1323. "hour",
  1324. "day",
  1325. "week",
  1326. "month",
  1327. "year"
  1328. ],
  1329. $number
  1330. ),
  1331. 2
  1332. );
  1333. $time = 0;
  1334. switch($number[1]){
  1335. case "second":
  1336. $time = (int)$number[0];
  1337. break;
  1338. case "minute":
  1339. $time = (int)$number[0] * 60;
  1340. break;
  1341. case "hour":
  1342. $time = (int)$number[0] * 3600;
  1343. break;
  1344. case "day":
  1345. $time = (int)$number[0] * 86400;
  1346. break;
  1347. case "week":
  1348. $time = (int)$number[0] * 604800;
  1349. break;
  1350. case "month":
  1351. $time = (int)$number[0] * 2629746;
  1352. break;
  1353. case "year":
  1354. $time = (int)$number[0] * 31556952;
  1355. break;
  1356. }
  1357. return time() - $time;
  1358. }
  1359. private function checkhttpspresence($link){
  1360. if(substr($link, 0, 2) == "//"){
  1361. return "https:" . $link;
  1362. }
  1363. return $link;
  1364. }
  1365. private function textualtime2int($number){
  1366. $number = explode(" - ", $number);
  1367. if(count($number) >= 2){
  1368. $number = $number[count($number) - 2];
  1369. }else{
  1370. $number = $number[0];
  1371. }
  1372. $number =
  1373. str_replace(
  1374. [
  1375. " ",
  1376. "seconds",
  1377. "minutes",
  1378. "hours",
  1379. ],
  1380. [
  1381. "",
  1382. "second",
  1383. "minute",
  1384. "hour"
  1385. ],
  1386. $number
  1387. );
  1388. preg_match_all(
  1389. '/([0-9]+)(second|minute|hour)/',
  1390. $number,
  1391. $number
  1392. );
  1393. $time = 0;
  1394. for($i=0; $i<count($number[0]); $i++){
  1395. switch($number[2][$i]){
  1396. case "second":
  1397. $time = $time + (int)$number[1][$i];
  1398. break;
  1399. case "minute":
  1400. $time = $time + ((int)$number[1][$i] * 60);
  1401. break;
  1402. case "hour":
  1403. $time = $time + ((int)$number[1][$i] * 3600);
  1404. break;
  1405. }
  1406. }
  1407. return $time;
  1408. }
  1409. private function views2int($views){
  1410. return
  1411. (int)str_replace(
  1412. ",", "",
  1413. explode(" ", $views, 2)[0]
  1414. );
  1415. }
  1416. private function hms2int($time){
  1417. $parts = explode(":", $time, 3);
  1418. $time = 0;
  1419. if(count($parts) === 3){
  1420. // hours
  1421. $time = $time + ((int)$parts[0] * 3600);
  1422. array_shift($parts);
  1423. }
  1424. if(count($parts) === 2){
  1425. // minutes
  1426. $time = $time + ((int)$parts[0] * 60);
  1427. array_shift($parts);
  1428. }
  1429. // seconds
  1430. $time = $time + (int)$parts[0];
  1431. return $time;
  1432. }
  1433. private function truncatedcount2int($number){
  1434. // decimal should always be 1 number long
  1435. $number = explode(" ", $number, 2);
  1436. $number = $number[0];
  1437. $unit = strtolower($number[strlen($number) - 1]);
  1438. $tmp = explode(".", $number, 2);
  1439. $number = (int)$number;
  1440. if(count($tmp) === 2){
  1441. $decimal = (int)$tmp[1];
  1442. }else{
  1443. $decimal = 0;
  1444. }
  1445. switch($unit){
  1446. case "k":
  1447. $exponant = 1000;
  1448. break;
  1449. case "m":
  1450. $exponant = 1000000;
  1451. break;
  1452. case "b";
  1453. $exponant = 1000000000;
  1454. break;
  1455. default:
  1456. $exponant = 1;
  1457. break;
  1458. }
  1459. return ($number * $exponant) + ($decimal * ($exponant / 10));
  1460. }
  1461. private function titledots($title){
  1462. $substr = substr($title, -3);
  1463. if(
  1464. $substr == "..." ||
  1465. $substr == "…"
  1466. ){
  1467. return trim(substr($title, 0, -3), " \n\r\t\v\x00\0\x0B\xc2\xa0");
  1468. }
  1469. return trim($title, " \n\r\t\v\x00\0\x0B\xc2\xa0");
  1470. }
  1471. }