composer require php-ffmpeg/php-ffmpeg
注意:请在 php.ini 中开启这两个函数proc_open,proc_get_status。找到 disable_functions 将里面的这两个函数去掉就好了
ffmpeg下载
<?php
namespace App\Helpers\Utils;
use FFMpeg\FFMpeg;
use FFMpeg\Format\Video\X264;
use Illuminate\Support\Facades\Storage;
class FFMpegUtil
{
protected $ffmpeg;
public function __construct()
{
$this->ffmpeg = FFMpeg::create(array(
'ffmpeg.binaries' => env('FFMPEG_PATH', '/usr/bin/ffmpeg'),
'ffprobe.binaries' => env('FFPROBE_PATH', '/usr/bin/ffprobe'),
'timeout' => 3600, // The timeout for the underlying process
'ffmpeg.threads' => 12, // The number of threads that FFMpegUtil should use
));
}
public function create()
{
return FFMpeg::create(array(
'ffmpeg.binaries' => env('FFMPEG_PATH', '/opt/local/ffmpeg/bin/ffmpeg'),
'ffprobe.binaries' => env('FFPROBE_PATH', '/opt/local/ffmpeg/bin/ffmpeg'),
'timeout' => 3600, // The timeout for the underlying process
'ffmpeg.threads' => 12, // The number of threads that FFMpegUtil should use
));
}
public function getVideoSeconds($video_url)
{
$stream = $this->ffmpeg->getFFProbe()->streams($video_url)->videos()->first();
return ceil($stream->get('duration'));
}
/**
* video to audio
* @param $video_url
* @return array
*/
public function transformToAudio($video_url, $audioformat = 'mp3')
{
$video = $this->ffmpeg->open($video_url);
$format = new X264('libmp3lame', 'libx264');
$format->setKiloBitrate(1000) // 视频码率
->setAudioChannels(2) // 音频声道
->setAudioKiloBitrate(256); // 音频码率
// 保存为
$store = Storage::disk('temp');
// $store->makeDirectory(date('Ymd'));
$filename = uniqid().'.'.$audioformat;
$dir = $store->path($filename);
$video->save($format, $dir);
$url = env('APP_URL').'/'.str_replace("\\", "/", $dir);
return ['message' => 'succeed', 'path' => $dir, 'url' => $url];
}
}
config/filesystems.php
'temp' => [
'driver' => 'local',
'root' => 'temp/'.date("Ymd"),
'url' => env('APP_URL').'/temp/'.date("Ymd"),
'visibility' => 'public',
],
'disks' => [
// ...
],
添加上面的内容
调用
public function extractAudio(Request $request) {
/** @var $file \Illuminate\Http\UploadedFile */
$file = $request->file('file');
//判断文件是否上传成功
if (!$file->isValid()) {
return $this->failed(ErrorCode::PARAM_ERROR);
}
$videoPath = "./upload/".uniqid().'.tmp';
move_uploaded_file($file->getRealPath(), $videoPath);
$ffmpeg = new FFMpegUtil();
$ans = $ffmpeg->transformToAudio($videoPath, "wav");
unlink($videoPath);
return $ans;
}
返回
{
"message": "succeed",
"path": "temp/20220907\\63181056de07f.wav",
"url": "http://api-tts.021city.xyz/temp/20220907/63181056de07f.wav"
}
视频去掉声音,换另一个声音
ffmpeg -i sampleVideo.mp4 -vcodec copy –an audioless.mp4
ffmpeg -i audioless.mp4 -i ..\audio\huolala01.wav -c:v copy -c:a aac output.mp4
ffplay output.mp4
VideoService.php
<?php
namespace App\Services;
use FFMpeg\Coordinate\TimeCode;
use FFMpeg\FFMpeg;
use FFMpeg\FFProbe;
use FFMpeg\Format\Audio\Wav;
use FFMpeg\Format\Video\X264;
use FFMpeg\Media\Video;
use Illuminate\Support\Facades\Log;
class VideoService
{
/** @var FFMpeg */
private $ffmpeg;
const CHUNK_SECONDS = 60; // 视频切分片段时长(s)
/**
* linux/mac上再安装 ffmpeg, ffprobe
*/
public function __construct() {
$this->ffmpeg = FFMpeg::create(array(
'ffmpeg.binaries' => base_path().'/bin/'.PHP_OS.'/ffmpeg.exe',
'ffprobe.binaries' => base_path().'/bin/'.PHP_OS.'/ffprobe.exe',
'timeout' => 3600, // The timeout for the underlying process
'ffmpeg.threads' => 4, // The number of threads that FFMpeg should use
));
}
/**
* 用ffmpeg从视频提取音频
* @param string $videoPath
* @param array $audioPathList
* @return array
*/
public function extractVideo(string $videoPath, array &$audioPathList) {
$today = date("Y-m-d");
/** @var $dir string 创建临时工作目录 */
$dir = public_path('temp').DIRECTORY_SEPARATOR.$today;
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
// 取得视频时长
$ffprobe = FFProbe::create();
$duration = $ffprobe->format($videoPath)->get('duration');
Log::debug("video duration=".$duration);
/** @var $uriList array 音频相对路径 */
$uriList = [];
$startSeconds = 0;
/** @var $video Video 切分视频 */
$video = $this->ffmpeg->open($videoPath);
$videoMd5 = md5_file($videoPath);
$videoChunks = [];
for ($leftSeconds = $duration, $i = 0; $leftSeconds > 0; $leftSeconds -= self::CHUNK_SECONDS, $i+=1) {
if ($leftSeconds > self::CHUNK_SECONDS) {
$endSeconds = $startSeconds + self::CHUNK_SECONDS;
} else {
$endSeconds = $duration; // 最后一段时长不足self::CHUNK_SECONDS
}
/** @var $clip \FFMpeg\Media\Clip 取一段试试 */
$clip = $video->clip(TimeCode::fromSeconds($startSeconds), TimeCode::fromSeconds($endSeconds));
$chunkPath = $dir.DIRECTORY_SEPARATOR.'video_'.$videoMd5.'_'.$i.'.avi';
// 上一步超时文件已存在,继续上一步的
if (!file_exists($chunkPath)) {
$format = new X264();
$clip->save($format, $chunkPath);
// 记录执行的命令
Log::info("ffmpeg ".json_encode($clip->getFinalCommand($format, $chunkPath)));
}
/** @var $video \FFMpeg\Media\AdvancedMedia */
$advancedMedia = $this->ffmpeg->openAdvanced(array($chunkPath));
$file = 'audio_'.$videoMd5.sprintf("%03d", $i).'.wav';
$audioPath = $dir.DIRECTORY_SEPARATOR.$file;
if (!file_exists($audioPath)) {
$advancedMedia->map(array('0:a'), new Wav(), $audioPath)->save();
Log::info("ffmpeg ".$advancedMedia->getFinalCommand());
}
$uri = "temp/".$today.'/'.$file;
$audioPathList[] = $audioPath;
$uriList[] = $uri;
// 更新开始时刻
$startSeconds = $endSeconds;
// 准备删除临时文件
$videoChunks[] = $chunkPath;
}
foreach ($videoChunks as $toDel) {
unlink($toDel);
}
return array_map(function(string $uri) {
return config("app.url").'/'.$uri;
}, $uriList);
}
public function pcm2wav(string $pcmPath, string $wavPath) {
$audio = $this->ffmpeg->open($pcmPath);
return $audio->save(new Wav(), $wavPath);
}
}