delphi 调用youtube-dl命令,下载youtube视频,原理及源代码

一、概要


1、Youtube-dl工具

  强大的视频下载命令行工具Youtube-dl项目由Ricardo Garcia创建于2008年,源代码由Python编写,托管在GitHub上,
最初仅支持YouTube,但随着项目的发展,也开始支持其他视频网站,优势在于使用简单、功能齐全、体积小巧,
但唯一遗憾的是国内使用需要开启代理。 可以从YouTube、Dailymotion、Google Video、Photobucket、Facebook、Yahoo、Metacafe、Depositfiles、Bilibili 和类似网站下载视频。
目前已知 youtube-dl 所支持的国内、外音、视频平台共有 1226 个之多,详情请参考:
http://ytdl-org.github.io/youtube-dl/supportedsites.html 
它不受平台限制,可以在任何 GNU/Linux、Windows 或 macOS 系统上运行。
  下载地址:

https://github.com/ytdl-org/youtube-dl

该脚本源代码基于Python 编写,需要安装 Python 3.2以上版本,Python 下安装命令:

#直接安装 youtube-dl

pip install youtube-dl

#更新安装 youtube-dl

pip install --upgrade youtube-dl

2、FFmpeg组件


​ FFmpeg是处理多媒体内容(例如音频、视频、字幕和相关元数据)的库和工具的集合。FFmpeg是处理多媒体内容(例如音频、视频、字幕和相关元数据)的库和工具的集合。从组件下载地址下载完成后,解压到想要的位置即可。
下载地址:(我下载的是gpl-shared版)
https://github.com/BtbN/FFmpeg-Builds/releases
​ 在这里使用FFmpeg的原因是:Youtube-dl下载的内容可能是音视频分开下载的(某些分辨率或者某些站点),用视频剪辑软件合并又要浪费一定的时间,而安装FFmpeg之后,则可以自动合并(merge)。
​ FFmpeg分为3个版本:Static,Shared,Dev。前两个版本可以直接在命令行中使用,他们的区别在于:

Static里面只有3个应用程序:ffmpeg.exe,ffplay.exe,ffprobe.exe,每个exe的体积都很大,相关的Dll已经被编译到exe里面去了。
Shared里面除了3个应用程序:ffmpeg.exe,ffplay.exe,ffprobe.exe之外,还有一些Dll,比如说avcodec-54.dll之类的。Shared里面的exe体积很小,他们在运行的时候,到相应的Dll中调用功能。
Dev版本是用于开发的,里面包含了include(头文件xxx.h)和lib(库文件xxx.lib),这个版本不包含exe文件。
​ 最后,确保已经在PATH中已经配置环境,我的配置如下:
​ 按Win+R或直接在任务栏搜索框,输入“cmd”,再输入以下命令,检测是否安装成功,

ffmpeg -version

二、Delphi调用Cmd命令行并能过管道取得返回结果


使用CreateProcess创建cmd进程。

 zeromemory(@sa,sizeof(sa));
  sa.nLength := Sizeof(sa);
  设置允许继承,否则在NT和2000下无法取得输出结果
  sa.bInheritHandle := True;
  sa.lpSecurityDescriptor := nil;
  hReadPipe:=0;
  hWritePipe:=0;
try
  //创建管道
  ret := CreatePipe(hReadPipe, hWritePipe, @sa, 0);
  if not ret then
  begin
    uLog.Log('CreatePipe false.');
    exit;
  end;
  //FillChar(StartupInfo, SizeOf(StartupInfo), 0);
  zeromemory(@StartupInfo, SizeOf(StartupInfo));
  StartupInfo.cb := sizeof(StartupInfo);
  GetStartupInfo(StartupInfo);
   //使用指定的句柄作为标准输入输出的文件句柄,使用指定的显示方式
  StartupInfo.dwFlags:=STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
  StartupInfo.wShowWindow:=SW_HIDE; //SW_HIDE

  StartupInfo.hStdOutput:=hWritePipe;
  StartupInfo.hStdError:=hWritePipe;

  zeromemory(@ProcessInfo, SizeOf(ProcessInfo));
  ret:=CreateProcess(nil,pchar(cmdline), @sa, @sa, true, 0, nil, pchar(uConfig.workdir), StartupInfo, ProcessInfo);

//从管道读取cmd返回的信息
function TTubeDown.getPipeInfo2(hReadPipe:THandle):string;
var
  //buf:array[0..1023] of ansichar;
  buf:array[0..1023] of byte;
  lngBytesread:cardinal;
  ret:BOOL;
  dwRead,dwAvail:DWORD;
begin
  result:='';
  ret:=true;
  while ret do
  begin
    if(not PeekNamedPipe(hReadPipe, nil, 0, @dwRead, @dwAvail, nil))then break;
    if(dwAvail<=0)then break;
    FillChar(buf,Sizeof(buf),#0);
    ret := ReadFile(hReadPipe, buf, 1024, lngBytesread, nil);
    if(lngBytesread<=0)then break;
    //result:=result+string(buf);
    result:=getStringFrombuf(buf,lngBytesRead);
  end;
end;

三、完整代码:

unit uTubeDown;

interface
uses
  windows,classes,strutils,sysutils,uLog,uConfig,uAuth;
const
  wm_user=$0400;
  wm_downfile=wm_user+100+1;
  //cmd命令类别
  CMD_DOWN=1;                  //下载
  CMD_FIND=2;                    //查询
  CMD_FIND_DOWN=3;       //查询下载
  CMD_UPGRADE=4;           //升级 
  //cmd参数
  PARAM_FIND='-F';              //查询
  PARAM_FIND_DOWN='-f';    //查询下载
  PARAM_FIND_BEST='-f best';     //以最高分辨率下载
type


  TTubeDown=class(TThread)
   private
     FId:cardinal;
     Furl:string;                        //视频地址
     Ffilename:string;          //保存的文件名
     FsaveDir:string;           //保存目录 
     Fmsg:string;               //消息
     Fcmd:integer;            //cmd命令类别
     Fparam:string;              //视频地址
     class var Fform: HWND;       //视频地址
     procedure SetId(id:cardinal);
     procedure SetCmd(cmd:integer);
     procedure SetParam(param:string);
     procedure SetSaveDir(dir:string);
     class procedure SetForm(const hForm: HWND); static;

     function getPipeInfo(hReadPipe:THandle):string;
     function getPipeInfo2(hReadPipe:THandle):string;
     function getStringFromBuf(buf:array of byte;length:integer):string;
   protected
     procedure Execute; override;
   public
     constructor Create(id:cardinal;url:string);
     destructor Destroy;
     function WaitExe(): integer;
     property id:cardinal read FId write SetId;
     property url:string read Furl;
     property msg:string read Fmsg;
     property filename:string read Ffilename;
     property cmd:integer read Fcmd write SetCmd;
     property param:string read Fparam write SetParam;
     property savedir:string read FsaveDir write SetSaveDir;
     class property form: HWND read Fform write SetForm;

  end;


implementation
constructor TTubeDown.Create(id:cardinal;url:string);
begin
  //inherited;
  //FreeOnTerminate := True;
  inherited Create(True);
  FId:=id;
  Furl:=url;
  //FComplete:=false;
end;
destructor TTubeDown.Destroy;
begin
  inherited Destroy;
end;
procedure TTubeDown.Execute;
begin
  WaitExe();
end;

//-----------------------------------------------------------------------------------
//****************************************************
//* 函数功能:执行程序至完成
//* 函数名称: WaitExe
技术支持:QQ:39848872;V:byc6352
//****************************************************
function TTubeDown.WaitExe(): integer;
var
  sa:TSecurityAttributes;
  hReadPipe,hWritePipe:THandle;
  ret:BOOL;
  info:string;
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
  lngBytesread,i:DWORD;
  cmdline:string;
  downcount:integer;
  dwRead:DWORD;
  buf:array[0..1023] of byte;
begin
  Result:=0;
  if(Fcmd=CMD_UPGRADE)then
  begin
    cmdline:=Furl;
  end else begin
  if(pos('bilibili',Furl)>0)then
  begin
    cmdline:='bbdown -tv --work-dir '+Fsavedir+' '+Furl;
  end else if (pos('youtube.com/shorts',Furl)>0)then
  begin
    //cmdline:='you-get.exe --debug -o '+Fsavedir+' '+Furl;
    cmdline:='yt-dlp.exe -o '+Fsavedir+'/%(title)s.%(ext)s '+Fparam+' '+Furl;
 end else if (pos('twitter.com',Furl)>0)then
  begin
    cmdline:='youtube-dl.exe -o '+Fsavedir+'/%(title)s.%(ext)s '+Fparam+' '+Furl;
  end else begin
    //yt-dlp.exe为youtube-dl的一个分支
    cmdline:='yt-dlp.exe -o '+Fsavedir+'/%(title)s.%(ext)s '+Fparam+' '+Furl;
  end;
  end;
  Log(cmdLine);

  zeromemory(@sa,sizeof(sa));
  sa.nLength := Sizeof(sa);
  sa.bInheritHandle := True;
  sa.lpSecurityDescriptor := nil;
  hReadPipe:=0;
  hWritePipe:=0;
try
  ret := CreatePipe(hReadPipe, hWritePipe, @sa, 0);  //创建管道
  if not ret then
  begin
    uLog.Log('CreatePipe false.');
    exit;
  end;
  //FillChar(StartupInfo, SizeOf(StartupInfo), 0);
  zeromemory(@StartupInfo, SizeOf(StartupInfo));
  StartupInfo.cb := sizeof(StartupInfo);
  GetStartupInfo(StartupInfo);
  StartupInfo.dwFlags:=STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
  StartupInfo.wShowWindow:=SW_HIDE; //SW_HIDE

  StartupInfo.hStdOutput:=hWritePipe;
  StartupInfo.hStdError:=hWritePipe;

  zeromemory(@ProcessInfo, SizeOf(ProcessInfo));

  ret:=CreateProcess(nil,pchar(cmdline), @sa, @sa, true, 0, nil, pchar(uConfig.workdir), StartupInfo, ProcessInfo);
  if not ret then
  begin
    uLog.Log('CreateProcess false.');
    Exit;
  end;
  while true do
  begin
    lngBytesread:=WaitForSingleObject(ProcessInfo.hProcess, 1000);//1秒钟输出一次信息;
    if(lngBytesRead=WAIT_FAILED)then
    begin
      uLog.Log('WaitForSingleObject false.');
      Exit;
    end;
    if(lngBytesRead= WAIT_OBJECT_0)then         //1秒获取信息一次
    begin

      if(Fcmd=CMD_FIND)then
        info:=getPipeInfo2(hReadPipe)         //取cmd输出信息
      else
        info:=getPipeInfo(hReadPipe);
      Log(info);
      Fmsg:=info;
      SendMessage(Fform,wm_downfile,0,integer(self));  //信息发送至窗体
      info:='complete';
      Log(info);
      result:=1;
      Fmsg:=info;
      SendMessage(Fform,wm_downfile,0,integer(self));
      exit;

    end;
    if(lngBytesRead=WAIT_TIMEOUT)then             //cmd进程结束
    begin
      if(Fcmd=CMD_FIND)then continue;
      info:=getPipeInfo(hReadPipe);                    //取cmd输出信息
      Log(info);
      Fmsg:=info;
      SendMessage(Fform,wm_downfile,0,integer(self));
    end;
  end;
  result:=1;
  info:='complete over.';
  Log(info);
  Fmsg:=info;
  SendMessage(Fform,wm_downfile,0,integer(self));
  //------------------------------------------------------------
finally
  if(hReadPipe<>0)then CloseHandle(hReadPipe);
  if(hWritePipe<>0)then CloseHandle(hWritePipe);
  if(ProcessInfo.hThread<>0)then  CloseHandle(ProcessInfo.hThread);
  if(ProcessInfo.hProcess<>0)then CloseHandle(ProcessInfo.hProcess);
end;
end;
//字节转换成字符串
function TTubeDown.getStringFromBuf(buf:array of byte;length:integer):string;
var
  tmp:array of byte;
  str:ansistring;
begin
  result:='';
  if(length<=0)then exit;
try
  setlength(tmp,length);
  copymemory(tmp,@buf[0],length);
  result:=Tencoding.UTF8.GetString(tmp);              //youtube等外网视频站点是utf8编码
except
  setlength(str,length);
  copymemory(@str[1],@buf[0],length);
  result:=str;

end;
end;
//非阻塞方式读取管道信息
function TTubeDown.getPipeInfo2(hReadPipe:THandle):string;
var
  //buf:array[0..1023] of ansichar;
  buf:array[0..1023] of byte;
  lngBytesread:cardinal;
  ret:BOOL;
  dwRead,dwAvail:DWORD;
begin
  result:='';
  ret:=true;
  while ret do
  begin
    if(not PeekNamedPipe(hReadPipe, nil, 0, @dwRead, @dwAvail, nil))then break;
    if(dwAvail<=0)then break;
    FillChar(buf,Sizeof(buf),#0);
    ret := ReadFile(hReadPipe, buf, 1024, lngBytesread, nil);
    if(lngBytesread<=0)then break;
    //result:=result+string(buf);
    result:=getStringFrombuf(buf,lngBytesRead);
  end;
end;
//阻塞方式读取管道信息
function TTubeDown.getPipeInfo(hReadPipe:THandle):string;
var
  //buf:array[0..1023] of ansichar;
  buf:array[0..1023] of byte;
  lngBytesread:cardinal;
  ret:BOOL;
begin
  result:='';
  FillChar(buf,Sizeof(buf),#0);
  ret := ReadFile(hReadPipe, buf, 1024, lngBytesread, nil);
  if(ret=true)then
  begin
    //result:=buf;
    result:=getStringFrombuf(buf,lngBytesRead);
  end;
end;
{
procedure TTubeDown.start();
begin
  Execute;
end;
}
//------------------------------------------属性方法-------------------------------------
 procedure TTubeDown.SetId(Id:cardinal);
 begin
   FId:=Id;
 end;
 class procedure TTubeDown.SetForm(const hForm: HWND);
 begin
   Fform:=hForm;
 end;
 procedure TTubeDown.SetCmd(cmd:integer);
 begin
   Fcmd:=cmd;
 end;
 procedure TTubeDown.SetParam(param:string);
 begin
   Fparam:=param;
 end;
 procedure TTubeDown.SetSaveDir(dir:string);
  begin
   FSaveDir:=dir;
 end;
end.

四、使用方法:

var
  tube:TTubeDown;
begin
     tube:=TTubeDown.Create(i,url);
      tube.cmd:=CMD_DOWN;
      tube.param:='';
      tube.savedir:=uConfig.saveDir;
      tube.start();
end;

五、成品

delphi 调用youtube-dl命令,下载youtube视频,原理及源代码_第1张图片

 

你可能感兴趣的:(音视频,ffmpeg,delphi,cmd,youtobe-dl)