环境介绍:
windows10
apache2.4
php7.4
thinkphp5.0
php-curl扩展 composer require php-curl-class/php-curl-class @dev
ffmpeg https://www.gyan.dev/ffmpeg/builds/ffmpeg-git-essentials.7z 解压后需要配置下bin目录环境变量
路由文件配置:
<?php
use think\Route;
Route::rule('/hls/','Hls/index');
Route::rule('/hls_path/:tsname','Hls/hlsPath'); //映射hls片目录
目录介绍:
cid 腾讯视频唯一id
c:/ku 保存下载的所有文件
c:/ku/list 保存视频m3u8文件
c:/ku/hls 保存所有视频片文件
c:/ku/hls/cid 保存单个视频片文件
c:/ku/video 保存
ffmpeg输出的mp4视频文件
请手动创建【c:/ku】【c:/ku/list】【
c:/ku/hls 】【
c:/ku/video】这几个文件夹,如要保存到其他位置请对应修改下面Hls.php中的配置
腾讯视频hls解析接口:
$hls_api = 'https://movie.etafort.cn/hlsapi/'.$cid;
该接口返回对应视频m3u8文件
核心代码Hls.php:
<?php
namespace app\controller;
use Curl\Curl;
use Curl\MultiCurl;
use think\Request;
@ini_set('memory_limit', '1024M'); //php内存限制调整
class Hls
{
//本教程下载的视频文件请不要用于商业用途
private $curl;
private $list_path = '/ku/list';
private $hls_path = '/ku/hls';
private $video_path = '/ku/video';
private $host = 'http://hls.com'; //本地虚拟域名 C:\Windows\System32\drivers\etc\hosts中配置
private $urls = false;
private $binfa = 100;
private $cid_tmp = false;
public function __construct()
{
$this->curl = new Curl();
}
//入口函数
public function index(){
$cids = [
'mzc00200ekwugpe',
'mzc002003rf9c06',
'mzc002007bdjvq1',
'mzc00200jrfpndx',
'mzc00200z8hg7m6',
'mzc00200dabqlu0',
'mzc00200b5n7vvf',
'mzc002003k94702',
'mzc002001dhvlxz',
'mzc0020026w4xg5',
'mzc00200xut0hno',
'mzc00200gtanynb',
'mzc002007gnu3ra',
];
foreach ($cids as $cid){
$this->downloadList($cid);
$this->downloadHls($cid);
$this->downloadVideo($cid);
}
}
//本地hls片映射
public function hlsPath($tsname){
$file = Request::instance()->path();
$file = str_replace('hls_path',$this->hls_path,$file);
return file_get_contents($file);
}
//下载单个.m3u8文件
private function downloadList($cid){
echo '【downloadList】'.$cid."\n";
$file = $this->list_path.'/'.$cid.'.m3u8';
if(file_exists($file))
return;
$hls_api = 'https://movie.etafort.cn/hlsapi/'.$cid;
$hls_link = $this->download($hls_api);
$hls_list = $this->download($hls_link);
$hls_list_arr = explode("\n",$hls_list);
$hls_list_arr = array_filter($hls_list_arr);
$list_new = "";
foreach ($hls_list_arr as $item){
if($item[0]==='#'){
$list_new .= $item."\n";
continue;
}
$list_new .= $this->host.'/hls_path/'.$cid.'/'.$item."\n";
}
file_put_contents($file,$list_new);
}
//下载.ts片文件
private function downloadHls($cid){
echo '【downloadHls】'.$cid."\n";
$urls = [];
$hls_api = 'https://movie.etafort.cn/hlsapi/'.$cid;
$hls_link = $this->download($hls_api);
$hls_host = $this->getHost($hls_link);
$hls_list = $this->download($hls_link);
$hls_list_arr = explode("\n",$hls_list);
$hls_list_arr = array_filter($hls_list_arr);
foreach ($hls_list_arr as $item){
if($item[0]==='#')
continue;
$tmp = explode('?',$item);
$ts_name = $tmp[0];
$url = $hls_host.$item.'&ts_name='.$ts_name;
$url_id = md5($url);
$urls[$url_id]=$url;
}
$this->downloads($urls,$cid,true);
}
//.ts转.mp4
private function downloadVideo($cid){
echo '【downloadVideo】'.$cid."\n";
$list_file = $this->list_path.'/'.$cid.'.m3u8';
$out_video = $this->video_path.'/'.$cid.'.mp4';
if(file_exists($out_video))
return;
if(!file_exists($list_file))
die('404');
exec('ffmpeg -allowed_extensions ALL -protocol_whitelist "file,http,crypto,tcp,https" -i '.$list_file.' -c copy '.$out_video,$res);
dump($res);
}
//多线程下载.ts文件
private function downloads($urls,$cid,$huancun=true){
if($this->urls===false){
$this->urls = $urls;
}
$this->cid_tmp = $cid;
if(!is_dir($this->hls_path.'/'.$this->cid_tmp))
mkdir($this->hls_path.'/'.$this->cid_tmp);
if(count($this->urls)===0){
$this->urls=false;
// echo "下载完成\n";
return true;
}
$multi_curl = new MultiCurl();
$multi_curl->setOpt(CURLOPT_SSL_VERIFYPEER,false);
$multi_curl->setOpt(CURLOPT_SSL_VERIFYHOST,false);
$multi_curl->success(function($instance) {
$tmp = explode('=',$instance->url);
$ts_name = $tmp[count($tmp)-1];
$url_id = md5($instance->url);
$file = $this->hls_path.'/'.$this->cid_tmp.'/'.$ts_name;
file_put_contents($file,$instance->response);
unset($this->urls[$url_id]);
echo $url_id . " ok<br/>\n";
});
$multi_curl->error(function($instance) {
echo 'call to "' . $instance->url . '" was unsuccessful.' . "<br/>\n";
echo 'error code: ' . $instance->errorCode . "<br/>\n";
echo 'error message: ' . $instance->errorMessage . "<br/>\n";
});
$multi_curl->complete(function($instance) {
// echo 'call completed' . "\n";
usleep(20000);//20毫秒
});
$count = 0;
foreach ($this->urls as $url_id=>$url){
if($huancun===true){
$tmp = explode('=',$url);
$ts_name = $tmp[count($tmp)-1];
$file = $this->hls_path.'/'.$this->cid_tmp.'/'.$ts_name;
if(!file_exists($file)){
$multi_curl->addGet($url);
$count++;
}else{
unset($this->urls[$url_id]);
}
}else{
$multi_curl->addGet($url);
$count++;
}
if($count==$this->binfa||$count==count($this->urls))
break;
}
$multi_curl->start(); // Blocks until all items in the queue have been processed.
$this->downloads($this->urls,$cid,$huancun);
}
//下载单个文件
private function download($url){
$this->curl->get($url);
if($this->curl->error)
die($this->curl->errorMessage);
return $this->curl->response;
}
//解析腾讯视频hls host
private function getHost($hls_link){
$host = '';
$tmp = explode('/',$hls_link);
unset($tmp[count($tmp)-1]);
foreach ($tmp as $item){
$host.=$item.'/';
}
return $host;
}
}
运行:
切换至项目public目录
shift+右键 在此处打开powershell窗口
php index.php /hls/
备注:
切勿用于商业用途
请保证硬盘空间足够,一个视频x2大概2.5G左右