ケイマン君のブログ

ケイマン君のオフィシャルサイトです。主にPHPのコーディングやゲームなどをプレイしています。

【2024版】PHPで動く簡易youtube Extractor作ってみた!!

youtubelogo
youtubelogo

なぜ作ったのか?

最近、PHPyoutube 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";
}
?>

参考記事

tyrrrz.me

これを参考に作ってみました

さらなる試練

これでできたはいいですが、この記事の下の方に、あるんですよ!試練が....

この試練とは?

署名関連です。

署名を解読すれば....

あまり言えないですが....

年齢制限付きのビデオが取得できるんです⭐️!

やばくないですか?っっ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";
}
?>

マイクラ2024夏の公式イベントに参加!!

暑い夏がやってきた

今年も暑いですね.... そんなときにはマイクラをやろう!!(?) 今回は、2024年度の夏のマイクライベントMC チャンピオンシップについて書いてみる

MCチャンピオンシップとは?

英語から翻訳-MC Championship は、YouTuber の Scott Major と Minecraft 集団 Noxcrew が主催する Minecraft トーナメントです。 4 人からなる 10 チームが、一連の Minecraft ミニゲームで競い合います。 wikipediaより

HIVEとかCUBECRAFTみたいな感じかな?

参加方法

1.Minecraftを起動する

2.今すぐ参加を押す

最後に

一応公式サイトと、写真集作ってみました!!

公式サイト

写真集

ではまた!!

マイクラジャンプブリッジやり方

ジャンプブリッジとは?

minecraftベットロックエディションのみ使える、ブロックを置く方法です。 具体的には、pvpなどで前にブロックを置きたい時などに使います。

それって難しい?

最初は自分も苦戦しました。 しかし慣れてくると全然難しいとは思いませんよ。(逆に楽)

する必要性

  • まず早くブロックがおけます。javaより早く動けるようになるので立ち回りなどが変わってきます。
  • しゃがむ必要がないのでjavaより楽な面もあるかもしれません。

やり方

置くボタンは、押したままで 視点はやや下にします。 サンプルです

youtu.be

【無料】一瞬で文字を敬語にするサイト!!

3min-good-text

何がすごいのか?

3秒敬語というサイトですが、名前の通り3秒でため口などを敬語にしてくれます!!

やり方

  1. まず 3keigo.com

にアクセスします

  1. 修正したい文字をいれて変換を押すと敬語になります。

どのくらいすごいのか?

今日俺疲れたから、夕飯の準備しといてくれない?
本日は私がお疲れのようですので、夕飯の準備をしていただけますか。
明日、遠足行くとき何持ってけばいい?
明日の遠足に参加する際に、どのような物を持って行けばよいでしょうか。

終わりに

これで、上司とかのやり取りで困りませんね!!