なぜ作ったのか?
最近、PHPのyoutube extractorって古かったりするんです。
例えば、今では使用できないhttp://www.youtube.com/get_video_info?video_id=
このURLのものが多かったりします。
代替えの策...
実は、http://www.youtube.com/get_video_info?video_id=このURLは動かないのは分かりましたけど、実は新しく APIが追加されたのです。
それがこちら!!
https://www.youtube.com/youtubei/v1/player?key=
これです!!
これが新しいAPIです。
実際のソースコード
それがこちら
※追記このコードはたまに失敗しますので、下にある追記のコードの方が動く確率は大きいです
<?php // URLパラメータからIDを取得 $id = isset($_GET['id']) ? $_GET['id'] : ''; // YouTubeの動画IDをチェックする正規表現 (11桁の英数字またはアンダースコア、ハイフン) $youtube_id_regex = '/^[a-zA-Z0-9_-]{11}$/'; // YouTubeの動画IDを取得するための正規表現 $youtube_id_from_url_regex = '/[?&]v=([a-zA-Z0-9_-]{11})/'; // 空でない場合に処理を開始 if (!empty($id)) { // URLデコード $iddecode = urldecode($id); // デコードされたIDがYouTubeのURL形式の場合 if (preg_match($youtube_id_from_url_regex, $iddecode, $matches)) { // IDを取得 $youtube_id = $matches[1]; // リダイレクト header('Location: ./?id=' . urlencode($youtube_id)); exit(); } elseif (preg_match($youtube_id_regex, $id)) { // IDが11桁の形式の場合、そのまま使用 $youtube_id = $id; } else { // 不正なIDの場合、リダイレクト header('Location: ./?id=' . urlencode($id)); exit(); } } else { // IDが空の場合、リダイレクト header('Location: ./?id=trnx5XT0cZs'); exit(); } function getVideoUrl($videoId) { $url = "https://music.youtube.com/youtubei/v1/player"; $payload = json_encode([ "context" => [ "client" => [ "hl" => "en", "gl" => "US", "clientName" => "ANDROID_TESTSUITE", "clientVersion" => "1.9", ] ], "videoId" => $videoId, ]); $headers = [ 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', 'Content-Type: application/json', ]; $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); $response = curl_exec($ch); if (curl_errno($ch)) { echo 'cURL Error: ' . curl_error($ch) . "\n"; return null; } curl_close($ch); $responseData = json_decode($response, true); if (isset($responseData['streamingData']['formats']) && is_array($responseData['streamingData']['formats'])) { foreach ($responseData['streamingData']['formats'] as $format) { if (isset($format['url'])) { return $format['url']; // 最初のURLを返す } } } return null; // URLが見つからなかった場合 } function downloadVideo($videoUrl, $chunkSize = 10000000) { $headers = get_headers($videoUrl, 1); $size = isset($headers['Content-Length']) ? (int)$headers['Content-Length'] : 0; if ($size === 0) { echo "Error: Could not determine the size of the stream. Ensure the URL is correct.\n"; return null; } $tempFile = tempnam(sys_get_temp_dir(), 'video_') . '.mp4'; $outputFile = fopen($tempFile, 'wb'); if (!$outputFile) { echo "Error: Could not open output file.\n"; return null; } for ($i = 0; $i < $size; $i += $chunkSize) { $end = min($i + $chunkSize - 1, $size - 1); $range = "$i-$end"; $ch = curl_init($videoUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, false); curl_setopt($ch, CURLOPT_RANGE, $range); curl_setopt($ch, CURLOPT_FILE, $outputFile); curl_setopt($ch, CURLOPT_HEADER, false); if (!curl_exec($ch)) { echo "cURL Error: " . curl_error($ch) . "\n"; break; } curl_close($ch); } fclose($outputFile); return $tempFile; // 一時ファイルのパスを返す } $videoId = isset($_GET['id']) ? $_GET['id'] : "trnx5XT0cZs"; // リクエストする動画ID $videoUrl = getVideoUrl($videoId); if ($videoUrl) { $tempFile = downloadVideo($videoUrl); // 動画をダウンロード if ($tempFile) { $tmpzz = base64_encode(file_get_contents($tempFile)); // HTMLの出力を開始 header('Content-Type: video/mp4'); header('Content-Disposition: inline; filename="video.mp4"'); header('Content-Length: ' . strlen($tmpzz)); // データサイズを設定 header('Cache-Control: no-cache'); header('Accept-Ranges: bytes'); // BASE64エンコードされたデータをデコードして出力 echo base64_decode($tmpzz); // 一時ファイルを削除 unlink($tempFile); } } else { echo "Error: Video URL not found.\n"; } ?>
参考記事
これを参考に作ってみました
さらなる試練
これでできたはいいですが、この記事の下の方に、あるんですよ!試練が....
この試練とは?
署名関連です。
署名を解読すれば....
あまり言えないですが....
年齢制限付きのビデオが取得できるんです⭐️!
やばくないですか?っっw
しかし、これにはさらなるコードが必要です、
まとめ・結論
年齢制限のない動画なら可能!!
次回の記事にご期待あれ!!
追記
高性能版完成
署名関係ではなく、コーディック関係を修正しました!!
<?php // URLパラメータからIDを取得 $id = isset($_GET['id']) ? $_GET['id'] : ''; // YouTubeの動画IDをチェックする正規表現 (11桁の英数字またはアンダースコア、ハイフン) $youtube_id_regex = '/^[a-zA-Z0-9_-]{11}$/'; // YouTubeの動画IDを取得するための正規表現 $youtube_id_from_url_regex = '/[?&]v=([a-zA-Z0-9_-]{11})/'; // 空でない場合に処理を開始 if (!empty($id)) { // URLデコード $iddecode = urldecode($id); // デコードされたIDがYouTubeのURL形式の場合 if (preg_match($youtube_id_from_url_regex, $iddecode, $matches)) { // IDを取得 $youtube_id = $matches[1]; // リダイレクト header('Location: api.php?id=' . urlencode($youtube_id)); exit(); } elseif (preg_match($youtube_id_regex, $id)) { // IDが11桁の形式の場合、そのまま使用 $youtube_id = $id; } else { // 不正なIDの場合、リダイレクト header('Location: api.php?id=' . urlencode($id)); exit(); } } else { // IDが空の場合、リダイレクト header('Location: api.php?id=trnx5XT0cZs'); exit(); } function getVideoUrl($videoId) { $url = "https://music.youtube.com/youtubei/v1/player"; $payload = json_encode([ "context" => [ "client" => [ "hl" => "en", "gl" => "US", "clientName" => "ANDROID_TESTSUITE", "clientVersion" => "1.9", ] ], "videoId" => $videoId, ]); $headers = [ 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', 'Content-Type: application/json', ]; $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); $response = curl_exec($ch); if (curl_errno($ch)) { echo 'cURL Error: ' . curl_error($ch) . "\n"; curl_close($ch); // cURLセッションを閉じる return null; } curl_close($ch); // cURLセッションを閉じる $responseData = json_decode($response, true); if (isset($responseData['streamingData']['formats']) && is_array($responseData['streamingData']['formats'])) { // type パラメーターを指定(例:'video' or 'audio') // ここで$typeを設定 $type = isset($_GET['type']) ? $_GET['type'] : "video"; // 'audio'に変更可能 if($type !== "video" && $type !== "audio"){ echo "不正なtypeです。videoかaudioを指定してください"; exit; } // 動画のitag優先順位 $videoPriority = [299, 137, 298, 136, 135, 134, 22, 18]; // 音声のitag優先順位 $audioPriority = [140, 139, 22, 18]; // 優先順位に基づいてフォーマットを評価 $priorityList = ($type === 'video') ? $videoPriority : $audioPriority; // フォーマットを優先順位でループ foreach ($priorityList as $itag) { foreach ($responseData['streamingData']['formats'] as $format) { if (isset($format['itag']) && $format['itag'] == $itag && isset($format['url'])) { return [$format['url'], $type]; // URLとタイプを返す } } } } // URLが見つからなかった場合はnullを返す return null; } function downloadVideo($videoUrl, $type = "video", $chunkSize = 10000000) { $headers = get_headers($videoUrl, 1); $size = isset($headers['Content-Length']) ? (int)$headers['Content-Length'] : 0; if ($size === 0) { echo "Error: Could not determine the size of the stream. Ensure the URL is correct.\n"; return null; } $extension = ($type === "video") ? ".mp4" : ".mp3"; $tempFile = tempnam(sys_get_temp_dir(), 'api_files_') . $extension; $outputFile = fopen($tempFile, 'wb'); if (!$outputFile) { echo "Error: Could not open output file.\n"; return null; } // チャンクに分けてダウンロード処理 $ch = curl_init($videoUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, false); curl_setopt($ch, CURLOPT_FILE, $outputFile); curl_setopt($ch, CURLOPT_HEADER, false); if (!curl_exec($ch)) { echo "cURL Error: " . curl_error($ch) . "\n"; fclose($outputFile); return null; } fclose($outputFile); return $tempFile; // 一時ファイルのパスを返す } // 動画IDを取得 $videoId = isset($_GET['id']) ? $_GET['id'] : "trnx5XT0cZs"; // デフォルトの動画ID $result = getVideoUrl($videoId); if ($result) { list($videoUrl, $type) = $result; $tempFile = downloadVideo($videoUrl, $type); if ($tempFile) { // コンテンツタイプ設定 header('Content-Type: ' . ($type === "video" ? 'video/mp4' : 'audio/mp3')); header('Content-Disposition: inline; filename="'. ($type === "video" ? 'video.mp4' : 'audio.mp3') .'"'); header('Content-Length: ' . filesize($tempFile)); // 正しいファイルサイズを設定 header('Cache-Control: no-cache'); header('Accept-Ranges: bytes'); // 一時ファイルを出力 readfile($tempFile); // 一時ファイルを削除 unlink($tempFile); } } else { echo "Error: Video URL not found.\n"; } ?>