一、概要
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;
五、成品