Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2018年04月16日] [更新 2018年05月01日] Delphi 一般・その他
Youtube の動画の情報とダウンロード
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
一部 Windows 7 U64(SP1) + Delphi 10.2 Starter
[整理番号 - 1030]







Youtube の動画の情報とダウンロード


下に表示しているのは Youtube に掲載されている動画の 1 つです。ここでは、このページに埋め込んで表示していて、このページで動画を再生できます。もちろん全画面での再生もできます。再生中に動画の右下の [Youtube] をクリックすれば Youtube のサイトで再生できます。





Youtube の動画は、その動画の実際のアドレス (URL) が分からなければダウンロードできません。ウェブブラウザのアドレス欄の URL は動画を再生しているウェブページのアドレスであって、動画そのもののアドレスではありません。

本ページは、この Youtube の動画の実際のアドレス (ダイレクト URL) の取得方法について説明しています。また、簡単なダウンロード用のサンプルを掲載しています。

YouTube の動画をダウンロードすることは法的には問題ないと思われますが、Youtube ではダウンロードそのものを禁止しています (利用規約 5-B 参照)。
本ページで説明してテストしている方法は、他にも応用できる内容です。そこで、当サイトで記事として掲載しておくことにしました。

ダウンロードした動画を VCL フォーム上で再生する方法は以下の記事を参考にしてください。



[備考 1]
本ページの記事の内容とサンプルは 2018年04月16日現在での確認結果です。Youtube の仕様は変更されることがあります。これらの方法が将来的に利用できるとは限りません。


[備考 2]
違法コンテンツ (音声及び映像) のダウンロードは違法です。







01_Youtube の動画のダウンロード用 URL の取得手順


Youtube の動画の情報を取得するには、Youtube の HTTPS サーバにリクエストの文字列を送ります。そのためには動画の ID の値が必要です。動画の ID の値は、下図のように該当する動画のマウスの右ボタンクリックから [動画の URL をコピー] で取得できます。
これ以降の説明は上の動画を例にしています。



図1
動画の URL の取得

コピーした文字列は、上の動画の場合は以下のようになっています。/ で区切られた最後の値が動画の ID の値です。


https://youtu.be/Yq1Ip8IzR60


この値を使用して以下の文字列を Youtube の HTTPS サーバにリクエストとして送信します。video= 以降の部分が動画の ID の値です。
リクエストの送信には HTTP の GET メソッドを使用します。


https://www.youtube.com/get_video_info?video_id=Yq1Ip8IzR60


上の文字列は https://www.youtube.com/get_video_info と言うアドレス (URL) にパラメータを追加した形式になっています。

最初のパラメータは ? に続けて書きます。2 番名以降のパラメータは & を追加してその後に書きます。各パラメータは = で指定します。& は各パラメータの区切り文字です。この区切り文字をデリミタと言うことがあります。
HTTP, HTTPS や FTP サーバへのリクエストには、多くの場合この形式が使われています。アプリケーションのコマンドライン (コマンドラインパラメータ、あるいは起動オプション等) には通常半角の空白文字が使われますが、この & 文字を使うこともあります。

下図がリクエストのレスポンスとして取得した文字列です。取得の際に UTF-8 でエンコードの指定をしています。レスポンスの結果として取得することをダウンロードと言うこともあります。
リクエストの結果が常にこの例のような文字列とは限りません。以下のリンクのサンプルのように XML コードの時もあります。



[備考 1]
Youtube API では動画の情報を JSON 形式で取得することができます。


[備考 2]
Youtube が HTTP サーバ時代の http://jp.youtube.com/ 等の動画のアドレスは、リクエストすると自動的に HTTPS サーバにリダイレクトされます。



図2
リクエストのレスポンス例

取得した上の図の文字列に動画の情報があります。この文字列を解析して動画の実際のアドレスを取得することになります。エンコードはされていますが、この文字列も HTTPS サーバへのリクエストの文字列と同じ、以下のような形式となっています。


?最初のパラメータ名=最初のパラメータの値&2番目のパラメータ名=2番目のパラメータの値&.....


まず url_encoded_fmt_stream_map というパラメータの値を取り出します。
つまり、url_encoded_fmt_stream_map と言う文字列と & で囲まれた部分文字列を取得します。
下図がその結果です。



図3
文字列の取り出し結果

取得した文字列をデコードして、今度は url パラメータの値を取り出します。パラメータの区切り文字 (デリミタ) & は %26 です。デコードしないと & という文字になりません。url パラメータは複数存在することがあります。下図は最初の url パラメータの値を取得した結果です。
この文字列が実際の動画のアドレス (URL) です。ダイレクト URL とも言うようです。
この文字列を Youtube の HTTPS サーバにリクエストして結果を取得すれば、その結果が動画のタウンロードということになります。もちろん url= の 4 文字と最後の & は削除しておきます。
下図の文字列は、取得した url パラメータの値をデコードして表示しています。

この文字列もまた複数のパラメータで構成されています。この中に itag というパラメータがあります。この itag は動画の品質です。itag=22 というのは hd720 という HD高画質の動画です。この動画の形式は mp4 です。
したがって、下図の URL でダウンロードした動画は、拡張子を .mp4 にして保存しても再生できます。保存した後に拡張子を .mp4 に変更しても再生できます。



図4
動画の実際のアドレス





02_YouTube の動画をダウンロードするサンプル
DownloadYoutubeMovie.zip [1,766 KB] 2018年04月18日版 (EXE 同梱)


前項で説明した手順で実際に動画のダイレクト URL を取得するサンプルです。
HTTPS サーバにアクセスするので TIdSSLIOHandlerSocketOpenSSL コンポーネントを使用しています。文字列の検索には小宮さん作の TRegEx 互換 ライブラリSkRegularExpressions を使用しています。
保存は FLV 形式で保存しますが、本サンプルで使用している動画の場合、拡張子を .mp4 にして保存しても再生できます。

[動画の URL をコピー] でコピーした動画の URL、または動画を再生している Youtube のウェブページの URL を入力欄に入力してテストしてください。
本サンプルではプレイリスト付きの動画等はダウンロードできません。また、キャンセル機能も進捗状況の表示の機能もありません。




図5
設計時の画面
図6
実行結果

リスト1
YouTube の動画をダウンロードするサンプル
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP,
  ExtCtrls, StdCtrls, IdIOHandler, IdIOHandlerSocket, IdIOHandlerStack, IdSSL,
  IdSSLOpenSSL;
type
  TForm1 = class(TForm)
    Button1: TButton;
    LabeledEdit1: TLabeledEdit;
    Memo1: TMemo;
    Panel1: TPanel;
    IdHTTP1: TIdHTTP;
    IdSSLIOHandlerSocketOpenSSL1: TIdSSLIOHandlerSocketOpenSSL;
    procedure FormShow(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    function DownloadYoutubeMovie2FLV(AVideoID: string; ASavePath: string): Boolean;
  end;

var
  Form1: TForm1;

implementation

uses
  IdURI,
  StrUtils,
  SkRegularExpressions;

{$R *.dfm}

//=============================================================================
//  フォーム表示開始時の処理
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
begin
  LabeledEdit1.Text := 'https://youtu.be/Yq1Ip8IzR60';
end;
//=============================================================================
//  ダウンロード開始用ボタンクリック
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LIdURL   : TIdURI;
  LParams   : string;
  LStrList  : TStringList;
  LLeftText : string;
  LVideoID  : string;
  LLoopCnt  : Integer;
  LResult   : Boolean;
begin
  Memo1.Lines.Clear;
  Memo1.Update;
  Button1.Enabled := False;
  Button1.Update;

  Screen.Cursor := crHourGlass;
  LVideoID := '';
  LIdURL   := TIdURI.Create(Trim(LabeledEdit1.Text));
  try
    LParams := LIdURL.Params;
    if Trim(LParams) <> '' then begin
      LStrList := TStringList.Create;
      try
        LStrList.Delimiter := '&';
        LStrList.DelimitedText := LParams;

        LLeftText := 'v=';
        for LLoopCnt := 0 to LStrList.Count - 1 do begin
          if StartsText(LLeftText, LStrList[LLoopCnt]) then begin
            LVideoID := StrUtils.ReplaceText(LStrList[LLoopCnt], LLeftText, '');
            Break;
          end;
        end;
      finally
        FreeAndNil(LStrList);
      end;
    end else begin
      LVideoID := LIdURL.Document;
    end;


    LResult := False;
    for LLoopCnt := 0 to 2 do begin
      if DownloadYoutubeMovie2FLV(LVideoID, 'Youtube.flv') then begin
        MessageDlg('ダウンロード終了', mtInformation, [mbOK], 0);
        LResult := True;
        break;
      end;
    end;
    if not LResult then begin
      MessageDlg('失敗しました', mtWarning, [mbOK], 0);
    end;
  finally
    FreeAndNil(LIdURL);
    Screen.Cursor := crDefault;
  end;
  Button1.Enabled := True;
  Button1.SetFocus;
end;

//-----------------------------------------------------------------------------
//  ビデオIDを指定してYoutubeの動画をダウンロードする関数
//  失敗することも多いので注意
//-----------------------------------------------------------------------------
function TForm1.DownloadYoutubeMovie2FLV(AVideoID: string;
  ASavePath: string): Boolean;
var
  LTargetUrl   : string;
  LMemStream   : TMemoryStream;
  LStrList     : TStringList;
  LbasePattern : string;
  LBaseText    : string;
  LMatch       : TMatch;
  LDirectUrl   : string;
begin
  Result := False;
  if AVideoID = '' then exit;

  if IdHTTP1.Connected then begin
    IdHTTP1.Disconnect;
  end;

  //送信するリクエスト文字列
  LTargetUrl := 'https://www.youtube.com/get_video_info?video_id=' + AVideoID;

  LMemStream := TMemoryStream.Create;
  LStrList   := TStringList.Create;
  try
    try
      IdHTTP1.Request.UserAgent:='Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)';
      IdHTTP1.Get(LTargetUrl, LMemStream);

      LMemStream.Position := 0;
      LStrList.LoadFromStream(LMemStream, TEncoding.UTF8);


      if (IdHTTP1.ResponseCode >= 100) and (IdHTTP1.ResponseCode <= 399) then begin

        LbasePattern := 'url_encoded_fmt_stream_map=([^&]+)';
        LBaseText := TRegEx.Match(LStrList.Text, LbasePattern).Value;
        //取得した文字列をデコードする
        LBaseText := TIdURI.URLDecode(LBaseText);

        //取得したテキストから最初のurl=の部分を取り出す
        LDirectUrl := '';
        for LMatch in TRegEx.Matches(LBaseText, 'url=([^&]+)') do begin
          LDirectUrl := LMatch.Groups[1].Value;
          LDirectUrl := TIdURI.URLDecode(LDirectUrl);
          break;
        end;
        if Trim(LDirectUrl) = '' then exit;

        //以下の文字を変換しておく(他にも変換が必要な文字があるかも知れない)
        //変換しないと失敗する可能性が高い
        LDirectUrl := ReplaceText  (LDirectUrl, '%2C', ',');
        LDirectUrl := ReplaceText  (LDirectUrl, '%2F', '/');
        LDirectUrl := ReplaceText  (LDirectUrl, '+', ' ');

        //取得したダウンロード用のURLを表示
        Memo1.Lines.Text := LDirectUrl;
        Memo1.Update;

        //ダイレクトURLから動画ファイルをダウンロード
        //時間がかかる
        LMemStream.Clear;
        IdHTTP1.Get(LDirectUrl, LMemStream);
        LMemStream.Position := 0;
        LMemStream.SaveToFile(ASavePath);

        Result := True;
      end;
    except
    end;
  finally
    FreeAndNil(LMemStream);
    FreeAndNil(LStrList);
  end;
end;

end.




03_Youtube の動画の情報


Youtube の動画の情報は Youtube API を利用した方がいいと思いますが、ダウンロード用の URL の文字列からも取得できます。ダウンロード用の URL はパラメータ付きです。このパラメータが動画の情報となっています。以下のコードはそのパラメータを別々に表示する例です。



図7
動画の URL のパラメータのリスト

リスト2
動画のパラメータの値を取得して表示
function TForm1.DownloadYoutubeMovie2FLV(AVideoID: string;
  ASavePath: string): Boolean;
var
  LTargetUrl   : string;
  LMemStream   : TMemoryStream;
  LStrList     : TStringList;
  LbasePattern : string;
  LBaseText    : string;
  LDirectUrl   : string;
begin
  Result := False;

  if IdHTTP1.Connected then begin
    IdHTTP1.Disconnect;
  end;

  //送信するリクエスト文字列
  LTargetUrl := 'https://www.youtube.com/get_video_info?video_id=' + AVideoID;

  LMemStream := TMemoryStream.Create;
  LStrList   := TStringList.Create;
  try
    try
      IdHTTP1.Request.UserAgent:='Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)';
      IdHTTP1.Get(LTargetUrl, LMemStream);

      LMemStream.Position := 0;
      LStrList.LoadFromStream(LMemStream, TEncoding.UTF8);


      if (IdHTTP1.ResponseCode >= 100) and (IdHTTP1.ResponseCode <= 399) then begin

        LbasePattern := 'url_encoded_fmt_stream_map=([^&]+)';
        LBaseText := TRegEx.Match(LStrList.Text, LbasePattern).Value;
        //取得した文字列をデコードする
        LBaseText := TIdURI.URLDecode(LBaseText);

        //取得したテキストからurl=の部分を取り出す
        LDirectUrl := TRegEx.Match(LBaseText, 'url=([^&]+)').Value;

        LDirectUrl := TIdURI.URLDecode(LDirectUrl);
        LDirectUrl := StrUtils.ReplaceText(LDirectUrl, 'url=', '');
        LDirectUrl := StrUtils.ReplaceText(LDirectUrl, '%2C', ',');
        LDirectUrl := StrUtils.ReplaceText(LDirectUrl, '%2F', '/');
        LDirectUrl := StrUtils.ReplaceText(LDirectUrl, '+', ' ');

        //各パラメータに分割
        LStrList.Clear;
        LStrList.Delimiter := '&';
        LStrList.DelimitedText := LDirectUrl;

        //結果を表示
        Memo1.Lines.Text := LStrList.Text;

        Result := True;
      end;
    except
    end;
  finally
    FreeAndNil(LMemStream);
    FreeAndNil(LStrList);
  end;
end;


前項の動画ファイルのダウンロードのサンプルでは、Youttube にリクエストをして取得した文字列からパラメータ url_encoded_fmt_stream_map の値を取得して、その中の url= の最初の値を取得してダウンロードの URL として使用しました。
以下はパラメータ url_encoded_fmt_stream_map 全てを取得した結果です。ダウンロード可能な各動画の品質とコーデックの種類が確認できます。
前項のサンプルでは、このリストの最初の url= の値を使用して動画をダウンロードしています。



図8
動画のダウンロード URL のリスト
  • url= の値をダウンロードに使用する

リスト3
動画のダウンロード URL の リストを取得
function TForm1.DownloadYoutubeMovie2FLV(AVideoID: string;
  ASavePath: string): Boolean;
var
  LTargetUrl   : string;
  LMemStream   : TMemoryStream;
  LStrList     : TStringList;
  LbasePattern : string;
  LBaseText    : string;
  LTempText    : string;
  LLoopCnt     : Integer;
begin
  Result := False;

  if IdHTTP1.Connected then begin
    IdHTTP1.Disconnect;
  end;

  //送信するリクエスト文字列
  LTargetUrl := 'https://www.youtube.com/get_video_info?video_id=' + AVideoID;

  LMemStream := TMemoryStream.Create;
  LStrList   := TStringList.Create;
  try
    try
      IdHTTP1.Request.UserAgent:='Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)';
      IdHTTP1.Get(LTargetUrl, LMemStream);

      LMemStream.Position := 0;
      LStrList.LoadFromStream(LMemStream, TEncoding.UTF8);


      if (IdHTTP1.ResponseCode >= 100) and (IdHTTP1.ResponseCode <= 399) then begin

        LbasePattern := 'url_encoded_fmt_stream_map=([^&]+)';
        LBaseText := TRegEx.Match(LStrList.Text, LbasePattern).Value;

        //取得した文字列をデコードする
        LBaseText := TIdURI.URLDecode(LBaseText);

        LStrList.Clear;
        LStrList.Delimiter := '&';
        LStrList.DelimitedText := LBaseText;

        for LLoopCnt := 0 to LStrList.Count - 1 do begin
          LTempText := TIdURI.URLDecode(LStrList[LLoopCnt]);
          LTempText := ReplaceText  (LTempText, '%2C', ',');
          LTempText := ReplaceText  (LTempText, '%2F', '/');
          LTempText := ReplaceText  (LTempText, '+', ' ');

          LStrList[LLoopCnt] := LTempText;
        end;
        LStrList.SaveToFile('MovieInfo.txt');

        Result := True;
      end;
    except
    end;
  finally
    FreeAndNil(LMemStream);
    FreeAndNil(LStrList);
  end;
end;


以下のコードは Youtube へのリクエストの結果からそのままパラメータを取得するサンプルです。おそらくこれが該当動画の全情報ではないかと思われます。
各々のパラメータの意味が分からないと理解が難しいですが、length_seconds は動画の長さ (再生の時間)、view_count= は再生回数と思われます、





図9
動画に関する情報のリスト

上のサンプルコードで取得したテキストには、動画ファイルのリンクである url= が 5 つしかありませんでした。ところが、下のコードで取得したテキストにリンクの url= がかなり多くあります。

下図は取得したテキストの最初の方の部分です。これは主に WebM と呼ばれる新しい形式の動画ファイルです。この形式は動画と音声が別々になっています。itag の値が 100 以上はこの形式です。





図10
新しい形式の動画ファイルへのリンク
  • 動画 (音声なし) だけのファイル


図11
新しい形式の動画用音声ファイルのリンク
  • 音声 (audio/webm) のみ

下図は、取得したテキストの、上の図に続く部分です。これが url_encoded_fmt_stream_map パラメータの値で、上のサンプルコードで取得した値と同じですが、パラメータの順番は取得する度に異なることがあります。
これらは主に動画と音声が一体となっている従来の形式の動画ファイルです。itag の値が 100 未満が該当しますが、itag の値が 100 未満でも WebM 形式があります。

前項のサンプルでは、この最初の動画ファイルをダウンロードするようにしています。





図12
従来形式の動画ファイルのリンク
  • これが url_encoded_fmt_stream_map パラメータの値
  • 主に動画と音声が一体となっている

リスト4
Youtube へのリクエストで取得したパラメータの値のリスト
function TForm1.DownloadYoutubeMovie2FLV(AVideoID: string;
  ASavePath: string): Boolean;
var
  LTargetUrl : string;
  LMemStream : TMemoryStream;
  LStrList   : TStringList;
  LBaseText  : string;
  LTempText  : string;
  LLoopCnt   : Integer;
begin
  Result := False;

  if IdHTTP1.Connected then begin
    IdHTTP1.Disconnect;
  end;

  //送信するリクエスト文字列
  LTargetUrl := 'https://www.youtube.com/get_video_info?video_id=' + AVideoID;

  LMemStream := TMemoryStream.Create;
  LStrList   := TStringList.Create;
  try
    try
      IdHTTP1.Request.UserAgent:='Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)';
      IdHTTP1.Get(LTargetUrl, LMemStream);

      LMemStream.Position := 0;
      LStrList.LoadFromStream(LMemStream, TEncoding.UTF8);


      if (IdHTTP1.ResponseCode >= 100) and (IdHTTP1.ResponseCode <= 399) then begin

        //取得した文字列をデコードする
        LBaseText := TIdURI.URLDecode(LStrList.Text);

        LStrList.Clear;
        LStrList.Delimiter := '&';
        LStrList.DelimitedText := LBaseText;

        for LLoopCnt := 0 to LStrList.Count - 1 do begin
          LTempText := TIdURI.URLDecode(LStrList[LLoopCnt]);
          LTempText := ReplaceText  (LTempText, '%2C', ',');
          LTempText := ReplaceText  (LTempText, '%2F', '/');
          LTempText := ReplaceText  (LTempText, '+', ' ');

          LStrList[LLoopCnt] := LTempText;
        end;
        //取得した情報をテキストファイルに保存
        LStrList.SaveToFile('VideoInfo.txt');

        Result := True;
      end;
    except
    end;
  finally
    FreeAndNil(LMemStream);
    FreeAndNil(LStrList);
  end;
end;


上のサンプルコードには、Youtube のウェブページの動画の下に表示されているタイトルのパラメータがあります。パラメータ名は title です。以下のコードはこのタイトルだけを取得する例です。
日本語の処理の確認のために、動画のタイトルが日本語であるその下の動画でテストしています。



図13
動画のタイトルを取得




リスト5
YouTube の動画のタイトルを取得するサンプル
      if (IdHTTP1.ResponseCode >= 100) and (IdHTTP1.ResponseCode <= 399) then begin

        LbasePattern := 'title=([^&]+)';
        LResText := TRegEx.Match(LStrList.Text, LbasePattern).Value;
        //取得した文字列をデコードする
        LResText := TIdURI.URLDecode(LResText);
        LResText := StrUtils.ReplaceText(LResText, '+', ' ');

        Memo1.Text := LResText;

        Result := Length(LResText) > 0;
      end;


以下のコードは動画の公開日を取得する例です。
動画の公開日は、Youtube のリクエストで取得したパラメータにはないようです。そこで、Youtube のウェブページの HTM コードから取得しています。
これまでのサンプルと同じように TIdSSLIOHandlerSocketOpenSSL コンポーネントを使用しています。





図14
動画の公開日

リスト6
Youtube の動画のウェブページの HTML コードから動画の公開日を取得
uses
  SkRegularExpressions;

{$R *.dfm}

//=============================================================================
//  Youtubeの動画再生ページのHTMLコードから動画の情報を取得する例
//  公開日を取得する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LMovieUrl : string;
  LHtmlCode : TStringList;
  LPattern  : string;
  LMatch    : TMatch;
  LResText  : string;
begin
  Memo1.Lines.Clear;
  LMovieUrl := 'https://www.youtube.com/watch?v=Yq1Ip8IzR60';

  LHtmlCode := TStringList.Create;
  IdHTTP1.HandleRedirects := True;
  try
    try
      LHtmlCode.Text := IdHTTP1.Get(LMovieUrl);

      LResText := '';
      LPattern := 'class="watch-time-text">(.*?)<';
      for LMatch in TRegEx.Matches(LHtmlCode.Text, LPattern) do begin
        LResText := LMatch.Groups[1].Value;
      end;
      if LResText <> '' then begin
        Memo1.Lines.Add(LResText);
        LResText := TRegEx.Match(LResText, '[0-9 /]*').Value;
        Memo1.Lines.Add(LResText);
      end;
    except
    end;
    IdHTTP1.Disconnect;
  finally
    FreeAndNil(LHtmlCode);
  end;
end;




04_VCL フォーム上で Youtube の動画を再生


Youtube の動画は TWebBrowser を使用すれば VCL のフォーム上でも再生できます。
以前は、ActiveX コントロールの取り込みでインストールされる TShockwaveFlash コンポーネントを使用して Youtube の動画も再生することができましたが、2018年04月16日現在、Youtube の仕様が変わっているため再生できません。

TWebBrowser で Youtube の動画を再生するには、動画上でマウスの右ボタンクリックのメニューから下図の [埋め込みコードをコピー] で取得した動画の URL を使用します。テストで使用している動画の場合は以下の URL となります。


https://www.youtube.com/embed/6tr2__Tv2I4  [埋め込みコードをコピー] で取得した URL
https://www.youtube.com/watch?v=6tr2__Tv2I4  通常の Youtube の動画再生ページの URL



図15
動画の埋め込みコードを取得する

以下のサンプルの Button1 クリックのコードでは TWebBrowser の Navigate メソッドを使用して動画を再生します。
一方、Button2 クリックのコードでは、いわゆる埋め込みコードと呼ばれる HTML コードを作成して、その HTML コードを TWebBrowser で表示します。そのコードを表示すると動画が表示されます。

下図は設計時の画面です。フォームに TPanel を配置して、Align プロパティを alRight にした後に Anchors の akLeft の値を True にしています。TWebBrowser は Align プロパティの値を alClient にしてその TPanel に配置しています。



図16
設計時の画面
図17
動画を再生中

Button2 クリックのコードでは HTML コードを生成していますから、いろいろな設定が可能です。

例えば下図は動画の再生が終了した時の画面ですが、 Navigate メソッドで再生した場合は、お勧め動画のリンクが表示されます。この動画のリンクをクリックすると、Windows 7 では IE が起動して動画のウェブページを表示します。TWebBrowser が IE オブジェクトなので常に IE が起動します。
一方の Button2 クリックでは、動画の再生が終了してもお勧めの動画のリンクは表示されません。



図18
動画の再生終了時の画面
  • [Button1] クリックの場合
  • お勧め動画のリンクが表示される
図19
動画の再生終了時の画面
  • [Button2] クリックの場合

リスト7
YouTube の動画を TWebBrowser を使用して、VCL フォーム上で再生する例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, OleCtrls, SHDocVw;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Panel1: TPanel;
    WebBrowser1: TWebBrowser;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  MSHTML;

{$R *.dfm}

//=============================================================================
//  TWebBrowserでYoutubeの動画を再生
//
//  TWebBrowser.Navigateメソッドを使用する例
//  引数のURLは埋め込みコードのURLを指定する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LWebPage : string;
begin
  LWebPage := 'https://www.youtube.com/embed/6tr2__Tv2I4';
  WebBrowser1.Navigate(LWebPage);
end;

//=============================================================================
//  TWebBrowserでYoutubeの動画を再生
//
//  HTMLコードを作成して,それをTWebBrowserで表示する例
//  usesにMSHTMLが必要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LHtmlCode  : string;
  LIEObject  : InternetExplorer;
  LIHTMLDoc2 : IHTMLDocument2;
begin
  //IEのオブジェクトを取得
  LIEObject := WebBrowser1.Application as InternetExplorer;

  //about:blankは表示のクリアとしても利用できる
  WebBrowser1.Navigate('about:blank');
  //準備完了まで待つ
  while LIEObject.ReadyState < READYSTATE_INTERACTIVE do begin
    Application.ProcessMessages;
  end;

  //HTMLコードを作成
  LHtmlCode := '<html>'
             + '<center>'
             + '<iframe width="100%" height="365" '
             + 'src="https://www.youtube.com/embed/6tr2__Tv2I4'
             + '?rel=0&showinfo=0" frameborder="0" '
             + 'allow="autoplay; encrypted-media" allowfullscreen></iframe>'
             + '</center>'
             + '</html>';

  if Assigned(LIEObject.Document) then begin
    //TWebBrowserに表示(IEオブジェクトに表示)
    LIHTMLDoc2 := LIEObject.Document as IHTMLDocument2;
    LIHTMLDoc2.body.innerHTML := LHtmlCode;
  end;
end;

end.




05_YouTube Video Download Delphi Library


以下のリンクに Youtube の動画ダウンロード用のライブラリがあります。
zip ファイルのダウンロードファイル名は youtube-dl-delphi-master.zip です。
下図は、添付の [Examples] [YouTube-Download] のプロジェクトを Windows 7 U64(SP1) + Delphi 10.2 Stater でコンパイルして実行しているところです。このサンプルは Fire Monkey 用です。
[Bin] フォルダ内の js32.dll をプロジェクトフォルダ階層下の [Win32] [Debug] にコピーしてテストしています。ライブラリのユニット類はプロジェクトに相対パスで追加されているので、ライブラリのパスを設定する必要はありません。

このサンプルプログラムでは、本ページのサンプルコードではダウンロードできないプレイリスト付きの動画もダウンロードできます。



図20
ダウンロード中




06_JavaScript を使用した YouTube の動画のダイレクト URL 取得


ウェブブラウザでウェブページを表示している時は JavaScript が使用できます。これ以降のサンプルでは TEmbeddedWB コンポーネントに Youtube の動画再生のウェブページを表示しています。

IE (InternetExplorer) でウェブページを表示している場合は IE のオブジェクトを取得することができれば同じような方法で操作できます。ただし、IE オブジェクトには次項のサンプルで使用しているような、JavaScript の関数を実行して戻り値を取得する直接的な方法がありません。以下のリンクの記事を参考にしてください。



Youtube の動画をダウンロードするための JavaScript も多くの方がウェブ上に掲載してます。
下に示すリンクの記事はその 1 つです。ここでは、その記事の JavaScript のコードをテストしてみました 。ユニットのコード内に JavaScript のコードを書いた方がいろいろ変更することができて便利なのですが、面倒なので上の記事のコードをそのままコピーして Youtube_JS.txt というテキストファイルに保存して、実行時にそのファイルを読み出しています。



[備考]
JavaScript のファイルには、一般的に .js という拡張子が使われています。



図21
実行結果
  • ポップアップメニューの [対象をファイルに保存 ] でダウンロード

リスト8
JavaScript を使用して動画のダウンロード URL のリストをウェブページに表示
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls, OleCtrls, SHDocVw_EWB, EwbCore, EmbeddedWB;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Panel1: TPanel;
    EmbeddedWB1: TEmbeddedWB;
    Edit1: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Edit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure EmbeddedWB1NavigateComplete2(ASender: TObject;
      const pDisp: IDispatch; var URL: OleVariant);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フェーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit1.Text := 'https://www.youtube.com/watch?v=Yq1Ip8IzR60';
  EmbeddedWB1.Navigate(Trim(Edit1.Text));
end;

//=============================================================================
//  Edit1のOnKeyDownイベント
//  [Enter]キーを押下したらEdit1のURLを表示
//=============================================================================
procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = VK_RETURN then begin
    EmbeddedWB1.Navigate(Trim(Edit1.Text));
  end;
end;

//=============================================================================
//  ファイルに保存しているJavaScriptのコードを読み出して
//  表示中のウェブページに対してJavaScriptのコードをセットする
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList : TStringList;
begin
  LStrList := TStringList.Create;
  try
    LStrList.LoadFromFile(ExpandFileName('.\Youtube_JS.txt'));
    EmbeddedWB1.ExecScript(LStrList.Text, 'javascript');
  finally
    FreeAndNil(LStrList);
  end;
end;

//=============================================================================
//  表示中のウェブページのURLをTEditに表示
//  他に適切なイベントがあるかも知れない
//=============================================================================
procedure TForm1.EmbeddedWB1NavigateComplete2(ASender: TObject;
  const pDisp: IDispatch; var URL: OleVariant);
begin
  Edit1.Text := EmbeddedWB1.Doc2.url;
end;

end.




07_JavaScript の実行結果から動画の URL を取得してダウンロード


Youtube の動画をダウンロードするだけであれば、前項の様にウェブページに表示する必要はありません。そこで、上で使用した JavaScript のコードを変更して次のようにしました。この JavaScript をウェブブラウザのオブジェクトにセットして、get_videoInfo 関数を実行して情報を取得します。

これで取得する値は、ここまでの説明にも出てきた url_encoded_fmt_stream_map パラメータの値です。この値もいくつかのパラメータで構成されています。その中の最初の url= の値を取り出して、その動画をダウンロードするようにしたのが下に示すサンプルコードです。



javascript:

var TempValue;

function conv_args(fmts) {
  TempValue = fmts;
}
function get_videoinfo () {
  return TempValue;
}

conv_args(ytplayer.config.args.url_encoded_fmt_stream_map);


JavaScript のコード内の conv_args 関数の引数を以下のようにすると、動画のタイトルや再生回数等の値を取得することができます。


conv_args(ytplayer.config.args.title);            動画のタイトル
conv_args(ytplayer.config.args.length_seconds);   再生時間(動画の長さ
conv_args(ytplayer.config.args.view_count);       再生回数

[備考]
本サンプルではファイルのダウンロードに TEmbeddedWB.DownloadFile メソッドを使用しています。このメソッドではダウンロードの進捗状態を取得することはできません。



図22
ダウンロード終了

リスト9
JavaScript を使用して取得した値からダイレクト URL を取り出して動画をダウンロード
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls, OleCtrls, SHDocVw_EWB, EwbCore, EmbeddedWB;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Panel1: TPanel;
    EmbeddedWB1: TEmbeddedWB;
    Edit1: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure EmbeddedWB1NavigateComplete2(ASender: TObject;
      const pDisp: IDispatch; var URL: OleVariant);
    procedure FormDestroy(Sender: TObject);
    procedure Edit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  private
    { Private 宣言 }
    FDownloadThread : TThread;
    function DownloadMovie(ADownloadURL, ASavePath: string): Boolean;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  IdURI,
  WinInet;

{$R *.dfm}

//=============================================================================
//  フェーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit1.Text := 'https://www.youtube.com/watch?v=Yq1Ip8IzR60';
  EmbeddedWB1.Navigate(Trim(Edit1.Text));
end;

//=============================================================================
//  フォーム破棄時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  if Assigned(FDownloadThread) then begin
    FDownloadThread.Terminate;
    FreeAndNil(FDownloadThread);
  end;
end;

//=============================================================================
//  Edit1のOnKeyDownイベント
//  [Enter]キーを押下したらEdit1のURLを表示
//=============================================================================
procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = VK_RETURN then begin
    EmbeddedWB1.Navigate(Trim(Edit1.Text));
  end;
end;

//=============================================================================
//  表示中のウェブページに対してJavaScriptをセット
//  動画ダウンロード用のダイレクトURLを取得してダウンロード
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList   : TStringList;
  LText      : string;
  LLoopCnt   : Integer;
  LPos       : Integer;
  LDirectURL : string;
begin
  LStrList := TStringList.Create;
  try
    //JavaScriptのコードを読み込んでウェブブラウザにセット
    LStrList.LoadFromFile(ExpandFileName('.\Youtube_JS.txt'));
    EmbeddedWB1.ExecScript(LStrList.Text, 'javascript');

    //上のJavaScriptの実行結果をJavaScript内の関数で取得
    LText := EmbeddedWB1.ExecScriptEx('get_videoinfo', ['']);

    //JavaScriptから取得した文字列からurl=の部分を取り出す
    LStrList.Clear;
    LStrList.Delimiter := ',';
    LStrList.DelimitedText := TIdURI.URLDecode(LText);

    for LLoopCnt := 0 to LStrList.Count - 1 do begin
      LDirectURL := LStrList[LLoopCnt];
      LPos  := Pos('url=', LDirectURL);
      if LPos >= 1 then begin
        LDirectURL := Copy(LDirectURL, LPos + 4, Length(LDirectURL));
        break;
      end;
    end;

    //ダイレクトURLから動画ファイルをダウンロード
    if LStrList.Count >= 1 then begin
      DownloadMovie(LDirectURL, 'YoutubeMovie.flv');
    end;
  finally
    FreeAndNil(LStrList);
  end;
end;

//=============================================================================
//  表示中のウェブページのURLをTEditに表示
//  他に適切なイベントがあるかも知れない
//=============================================================================
procedure TForm1.EmbeddedWB1NavigateComplete2(ASender: TObject;
  const pDisp: IDispatch; var URL: OleVariant);
begin
  Edit1.Text := EmbeddedWB1.Doc2.url;
end;

//-----------------------------------------------------------------------------
//  ダウンロード用の関数
//  別スレッドで実行
//
//  TEmbeddedWB.DownloadFileは内部でUrlDownloadToFile関数を実行している
//  UrlDownloadToFile関数はIEのキャッシュを優先してダウンロードするので
//  DeleteUrlCacheEntry関数であらかじめIEキャッシャ内の該当ファイルを削除
//  この関数の使用にはusesにWinInetが必要
//-----------------------------------------------------------------------------
function TForm1.DownloadMovie(ADownloadURL, ASavePath: string): Boolean;
var
  LDlgFlag : Cardinal;
  LResFlag : Boolean;
begin
  Result := True;
  Button1.Enabled := False;

  if Assigned(FDownloadThread) then begin
    FDownloadThread.Terminate;
    FreeAndNil(FDownloadThread);
  end;
  DeleteUrlCacheEntry(PChar(ADownloadURL));

  FDownloadThread := TThread.CreateAnonymousThread(
    procedure()
    begin
      LResFlag := EmbeddedWB1.DownloadFile(ADownloadURL, ASavePath);

      //上の処理が終了したら...
      TThread.Synchronize(TThread.CurrentThread,
        procedure
        begin
          Button1.Enabled := True;
          if LResFlag then begin
            LDlgFlag := MB_OK or MB_ICONINFORMATION or MB_TOPMOST;
            MessageBox(Handle, 'ダウンロード終了', '情報', LDlgFlag);
          end else begin
            LDlgFlag := MB_OK or MB_ICONWARNING or MB_TOPMOST;
            MessageBox(Handle, 'ダウンロード失敗', '情報', LDlgFlag);
          end;
        end
      );
    end
  );
  FDownloadThread.FreeOnTerminate := False;
  FDownloadThread.Start;
end;

end.




08_YouTube の動画の情報を JSON 形式の文字列として取得


上の JavaScript のコード内の ytplayer.config.args は JSON オブジェクトです。
Delphi のコンポーネントに例えると、プロパティ名とプロパティの値をペアにした以下のような文字列をオブジェクトとして扱うのが JSON です。この文字列を JSON 形式の文字列とも言います。このような文字列が入れ子、つまり、ネストしている場合もあります。また、[ ] で括られた配列要素が含まれることもあります。


{"Name": "Panel1", "Caption": "OK", "Left": "100", "Top": "60", "Color": "clBlue"} 


JavaScript で取得した JSON オブジェクトを Delphi で直接は取得できないので、JSON 形式の文字列データとして Delphi で受け取るようにしたのが図の下のサンプルコードです。
そのために JavaScript 内で、以下のように JSON.stringify メソッドで JSON 文字列に変換します。

Delphi 側では、受け取った JSON 形式の文字列を JSON オブジェクトに変換し、それを操作して JSON オブジェクト内のデータを取得しています。

上のサンプルで使用した JavaScript は、ブラウザにセットする時に conv_args という関数を実行していますが、get_videoinfo 関数を実行する時に実行しても同じです。したがって、JavaScript のコードは、以下のコードにしても同じです。今回の場合は更にその下のコードにしても同じです。



javascript:

function conv_args(fmts) {
  return JSON.stringify(fmts);
}
function get_videoinfo () {
  return conv_args(ytplayer.config.args);
}

javascript:

function get_videoinfo () {
  return JSON.stringify(ytplayer.config.args);
}


下図は実行結果ですが、この実行結果は、本ページの最初の方で説明した Youtube へのリクエストの結果とほぼ同じです。取得順番は実行する度に異なることがあります。
このリストの中に url_encoded_fmt_stream_map の値があります。このパラメータの値は JSON 形式ではありませんが、この値もまた & で区切られたいくつかの値で構成されています。コンポーネントのプロパティで例えれば、集合型のプロパティといったところでしょうか。



[備考 1]
本サンプルコードはネストと配列関係の処理を省略しています。


[備考 2]
本サンプルは Delphi 10.2 Starter でテストしています。Delphi XE でも uses に DBXJSON を追加すれば JSONを扱えますが、Youtube の JSON データは処理できません。



図23
実行結果

リスト10
JSON オブジェクト ytplayer.config.args を文字列として取得して処理
このサンプルの動作確認は、Windows 7 U64(SP1) + Delphi 10.2 Starter
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
  Vcl.OleCtrls, SHDocVw_EWB, EwbCore, EmbeddedWB;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    Edit1: TEdit;
    EmbeddedWB1: TEmbeddedWB;
    Memo1: TMemo;
    Splitter1: TSplitter;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Edit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure EmbeddedWB1NavigateComplete2(ASender: TObject;
      const pDisp: IDispatch; var URL: OleVariant);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  System.Json;

{$R *.dfm}

//=============================================================================
//  フェーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit1.Text := 'https://www.youtube.com/watch?v=Yq1Ip8IzR60';
  EmbeddedWB1.Navigate(Trim(Edit1.Text));
end;

//=============================================================================
//  Edit1のOnKeyDownイベント
//  [Enter]キーを押下したらEdit1のURLを表示
//=============================================================================
procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = VK_RETURN then begin
    EmbeddedWB1.Navigate(Trim(Edit1.Text));
    Memo1.Lines.Clear;
  end;
end;

//=============================================================================
//  表示中のウェブページに対してJavaScriptのコードをセットして
//  JavaScript内の関数を実行する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LDataText   : string;
  LStrList    : TStringList;
  LJsonValue  : TJSONValue;
  LJsonObject : TJSONObject;
  LJsonPair   : TJSONPair;
  LPairName   : string;
  LPairValue  : string;
begin
  Memo1.Lines.Clear;

  LStrList := TStringList.Create;
  try
    //JavaScriptのコードを読み込んでセット
    LStrList.LoadFromFile(ExpandFileName('.\Youtube_JS.txt'));
    EmbeddedWB1.ExecScript(LStrList.Text, 'javascript');

    //JavaScript内の関数でJSON形式の文字列を取得
    LDataText := EmbeddedWB1.ExecScriptEx('get_videoinfo', ['']);

    LJsonValue := TJSONObject.ParseJSONValue(LDataText);
    if not Assigned(LJsonValue) then exit;

    LJSONObject := LJsonValue as TJSONObject;

    Memo1.Lines.BeginUpdate;
    for LJsonPair in LJsonObject do begin
      LPairName  := LJsonPair.JsonString.Value;
      LPairValue := LJsonPair.JsonValue.Value;
      LDataText := LPairName + '=' + LPairValue;

      Memo1.Lines.Add(LDataText);
    end;
    Memo1.Lines.EndUpdate;
  finally
    FreeAndNil(LStrList);
  end;
end;

//=============================================================================
//  表示中のウェブページのURLをTEditに表示
//  他に適切なイベントがあるかも知れない
//=============================================================================
procedure TForm1.EmbeddedWB1NavigateComplete2(ASender: TObject;
  const pDisp: IDispatch; var URL: OleVariant);
begin
  Edit1.Text := EmbeddedWB1.Doc2.url;
end;

end.




09_TChromium を使用して YouTube の動画をダウンロード


コンポーネント TChromium は、Google Chrome ブラウザの機能を実装するための Delphi 用のコンポーネントです。CEF4Delphi という名前で提供されています。その TChromium を使用する例です。
Youtube の動画のダイレクト URL のリストを取得する以下の JavaScript の実行結果を受け取って処理します。


javascript:

function conv_args(fmts) {
  return fmts;
}
function get_videoinfo () {
  return conv_args(ytplayer.config.args.url_encoded_fmt_stream_map);
}


TChromium ブラウザコントロールでは、表示中のウェブページ上のダウンロードリンクをクリックしてもダウンロードはしません。BeforeDownload イベント内などで ICefBeforeDownloadCallback インターフェイスの Cont コールバック関数を実行する必要があります。
ただし、本サンプルコードではダウンロードの失敗を検出できません。



図24
保存先指定のダイアログを表示してダウンロード
図25
進捗状況を表示

リスト11
CEF4Delphi の TChromium ブラウザコントロールを使用して Youtube の動画をダウンロード
使用した CEF4Delphi のバージョンは 3.3359.1768
//=============================================================================
//  ダウンロード用ボタンのクリックイベント
//
//  JavaScriptのコードをセットして、JavaScriptコード内の関数を実行
//  その関数の戻り値は動画のダイレクトURLのリスト
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList : TStringList;
  LJsFunc  : string;
begin
  LStrList := TStringList.Create;
  try
    //JavaScriptのコードをセット
    LStrList.LoadFromFile(ExpandFileName('.\Youtube_JS.txt'));
    Chromium1.ExecuteJavaScript(LStrList.Text, 'about:blank');

    //実行する関数名
    //OnConsoleMessageイベント内ではこの値で処理を分岐する
    FJSFuncName := 'get_videoinfo';
    //JavaScript内の関数を実行
    //戻り値の出力先はconsole.log(コンソール)
    LJsFunc := 'console.log(' + FJSFuncName + ' (""))';
    Chromium1.Browser.MainFrame.ExecuteJavaScript(LJsFunc, 'about:blank', 0);
  finally
    FreeAndNil(LStrList);
  end;
end;

//=============================================================================
//  Chromium1のOnConsoleMessageイベント処理
//  JavaScript内のの関数の実行結果をこのイベントで受け取る
//=============================================================================
procedure TForm1.Chromium1ConsoleMessage(Sender: TObject;
  const browser: ICefBrowser; level: Cardinal; const message, source: ustring;
  line: Integer; out Result: Boolean);
var
  LStrList   : TStringList;
  LLoopCnt   : Integer;
  LPos       : Integer;
  LTempURL   : string;
  LDirectURL : string;
begin
  if source = 'about:blank' then begin
    if FJSFuncName = 'get_videoinfo' then begin
      LStrList := TStringList.Create;
      try
        LStrList.Clear;
        LStrList.Delimiter := ',';
        LStrList.DelimitedText := TIdURI.URLDecode(message);

        LDirectURL := '';
        for LLoopCnt := 0 to LStrList.Count - 1 do begin
          LTempURL := LStrList[LLoopCnt];
          LPos     := Pos('url=', LTempURL);
          if (LPos >= 1) then begin
            LTempURL   := LStrList[LLoopCnt];
            LDirectURL := Copy(LTempURL, LPos + 4, Length(LTempURL));
            break;
          end;
        end;

        //ファイル名を指定してダウンロード開始
        if LDirectURL <> '' then begin
          Chromium1.StartDownload(LDirectURL);
        end;
      finally
        FreeAndNil(LStrList);
      end;
    end;
  end;
end;

//=============================================================================
//  Chromium1のOnBeforeDownloadイベント処理
//  このイベント内でコールバックのcallback.Contを実行しないとダウンロードしない
//
//  第2引数をTrueにすると保存ダイアログを表示する
//  第2引数をFalseにすると第1引数の場所に自動保存
//  パスのフォルダか存在しない時は保存しない
//
//  このイベントの使用にはusesにuCEFInterfaces, uCEFTypes, uCEFConstantsが必要
//=============================================================================
procedure TForm1.Chromium1BeforeDownload(Sender: TObject;
  const browser: ICefBrowser; const downloadItem: ICefDownloadItem;
  const suggestedName: ustring;
  const callback: ICefBeforeDownloadCallback);
var
  LSaveFullPath : string;
begin
  if (downloadItem = nil) or not(downloadItem.IsValid) then exit;

  if FJSFuncName = 'get_videoinfo' then begin
    LSaveFullPath := 'd:\000\Youtube.mp4';
    //ダウンロード用のコールバック実行
    callback.Cont(LSaveFullPath, True);

    //実行した関数名をクリアしておく
    FJSFuncName := '';
  end else begin
    //Youtubeの動画のダウンロード以外の時のダウンロードの処理
    //具体的なコーディング方法はCEF4Delphiに添付のデモプログラムを参照
  end;
end;

//=============================================================================
//  Chromium1のOnDownloadUpdatedイベント処理
//  ダウンロード用のコールバック関数内で発生するイベント
//  ダウンロードの進捗状況の表示に使用
//
//  このイベントの使用にはusesにuCEFInterfaces, uCEFTypes, uCEFConstantsが必要
//=============================================================================
procedure TForm1.Chromium1DownloadUpdated(Sender: TObject;
  const browser: ICefBrowser; const downloadItem: ICefDownloadItem;
  const callback: ICefDownloadItemCallback);
var
  TempString : string;
begin
  if downloadItem.IsComplete then begin
    StatusBar1.Panels[0].Text := downloadItem.FullPath + ' : completed';
  end else
  if downloadItem.IsCanceled then begin
    StatusBar1.Panels[0].Text := downloadItem.FullPath + ' : canceled';
  end else
  if downloadItem.IsInProgress then begin
    if (downloadItem.PercentComplete >= 0) then begin
      TempString := downloadItem.FullPath + ' : '
                  + IntToStr(downloadItem.PercentComplete) + ' %'
     end else begin
       TempString := downloadItem.FullPath + ' : '
                   + IntToStr(downloadItem.ReceivedBytes) + ' bytes received';
    end;
    StatusBar1.Panels[0].Text := TempString;
  end;
end;




10_YouTube の動画ダウンロード失敗の原因


本ページに掲載している JavaScript を使用した動画のダウンロードのコードは、ダウンロードに失敗することがあります。
ダウンロードに失敗した時は、EXE を 1, 2 回再起動して実行するとダウンロードできるようになることが多いようです。JavaScript では以下のように、キャッシュからリロードしないでウェブサイトからリロードすれば、同様に 1, 2 回の繰り返しでダウンロードできるようになります。


location.reload(true);



ダウンロードに失敗した時も、リダイレクトの URL としての文字列は取得できています。したがって、その取得した文字列で適切でないためと思われます。このダイレクト URL の文字列もそうですが、動画再生のウェブページの内容はウェブページを読み込む度に違う値となります。

TIdHTTP を使用してダウンロードするサンプルでは、ダウンロードに失敗したら再度リクエストを送信して情報を再取得しています。その結果、現在のところ、プレイリスト (再生リスト) 付きの動画でなければほとんど動画のダウンロードに失敗することはありません。
Youtube の動画の情報取得や動画のダウンロードに JavaScript を使用したり、ウェブブラウザコントロールのダウンロードの機能を使用する必然性はありません。

本ページのサンプルではプレイリスト (再生リスト) 付きの動画はほとんどダウンロードできません。ダウンロード可能な動画もありますが、それらにどのような違いがあるのか筆者には分かりません。プレイリスト付きの動画の情報も取得してダウンロードしたい場合は、HTML コードのパース (解析) が必要です。

前述の [YouTube Video Download Delphi Library] であればプレイリスト付きの動画もダウンロード可能です。Youtube の動画ダウンロード用のコマンドラインツールもあるようです。コマンドラインツールであれば Delphi から容易に利用することができます。
いずれにしても、Youtube の仕様が変更された時は、それに対応する必要があるでしょう。

筆者としては、ダウンロードできない原因やプレイリスト付きの動画のダウンロードについて、これ以上追及するつもりはありません。