php實現ffmpeg處理視訊

huaweichenai發表於2021-12-15

最近有一個專案需要使用ffmpeg處理視訊,這裡我寫了一個demo,方便我們來實現視訊操作

ffmpeg操作demo

<?php

namespace common\helpers;

use common\models\Config;
use common\models\VideoApiLog;
use Yii;
use yii\helpers\ArrayHelper;
use common\helpers\Universal;
use yii\helpers\FileHelper;
use yii\httpclient\Client;
use yii\web\ServerErrorHttpException;

/**
 * ffmpeg視訊處理
 *
 * @author wangjian
 * @since 0.1
 */
class FfmpegVideo
{

    public $ffmpeg = 'ffmpeg';


    public function __construct($ffmpeg = null)
    {
        if ($ffmpeg) {
            $this->ffmpeg = $ffmpeg;
        }
    }


    /**
     * 新增視訊文字滾動
     * @param $source string 視訊
     * @param $saveFile string 儲存檔案
     * @param $text string 水印文字
     * @param array $options 水印樣式
     * @param int $step 每秒步長
     * @param int $star 出現時間
     */
    public function titleMod($source, $saveFile, $text, $options = [], $step = 20, $star = 0)
    {
        $command = $this->ffmpeg .' -y -i '. $source .' -async 1 -metadata:s:v:0 start_time=0 -vf ';

        $fonts = Yii::getAlias('@webroot') . "/fonts/simsun.ttc";
        $fonts = str_replace('\\', '/', $fonts);
        $fonts = str_replace(':', '\\:', $fonts);
        $command .= '"drawtext=fontfile=\''. $fonts .'\': text=\''. $text .'\'';

        foreach ($options as $key => $value) {
            $command .= ':' . $key . '=' . $value;
        }

        $command .= ':x=\'if(gte(t,'. $star .'),((t-'. $star .') * '. $step .'),NAN)\'';

        $command .= '" ';
        $command .= $saveFile;
        exec($command, $output, $result_code);
        if ($result_code == 0) {
            return true;
        }
        return  false;
    }


    /**
     * 圖片水印
     * @param $source string 視訊
     * @param $saveFile string 儲存檔案
     * @param $waterImage string 水印圖片
     * @param $left integer 水印水平位置
     * @param $top integer 水印垂直位置
     * @param null $star 水印開始時間
     * @param null $duration 水印時長
     */
    public function imageWater($source, $saveFile, $waterImage, $left, $top, $star = null, $duration = null)
    {
        $waterImage = str_replace('\\', '/', $waterImage);
        $waterImage = str_replace(':', '\\:', $waterImage);
        $command = $this->ffmpeg . ' -y -i '. $source .' -vf "movie=\''. $waterImage .'\'[watermark];';
        $command .= '[in][watermark] overlay='. $left .':'. $top;
        if ($star) {
            $end = ($duration) ? $star + $duration : $star;
            $command .= ':enable=\'between(t,'. $star .','. $end .')\'';
        }
        $command .= '[out] " ' . $saveFile;
        exec($command, $output, $result_code);
        if ($result_code == 0) {
            return true;
        }
        return  false;
    }


    /**
     * 給視訊新增文字水印
     * @param $source string 視訊
     * @param $saveFile string 儲存檔案
     * @param $text string 水印文字
     * @param array $options 水印樣式
     * @param null $star 水印開始時間
     * @param null $duration 水印時長
     */
    public function titleWater($source, $saveFile, $text, $options = [], $star = null, $duration = null)
    {
        $command = $this->ffmpeg .' -y -i '. $source .' -vf ';
        $fonts = Yii::getAlias('@webroot') . "/fonts/STZHONGS.TTF";
        $fonts = str_replace('\\', '/', $fonts);
        $fonts = str_replace(':', '\\:', $fonts);
        $command .= '"drawtext=fontfile=\''. $fonts .'\': text=\''. $text .'\'';

        foreach ($options as $key => $value) {
            $command .= ':' . $key . '=' . $value;
        }
        if ($star) {
            $end = ($duration) ? $star + $duration : $star;
            $command .= ':enable=\'between(t,'. $star .','. $end .')\'';
        }
        $command .= '" ';
        $command .= $saveFile;

        exec($command, $output, $result_code);
        if ($result_code == 0) {
            return true;
        }
        return  false;
    }

    /**
     * 將音訊合併到視訊中
     * @param $videoFile string 視訊檔案
     * @param $audioFile string 音訊檔案
     * @param $saveFile string 儲存檔案
     * @param $delay integer 聲音插入延時秒數
     */
    public function mergeVideoAudio($videoFile, $audioFile, $saveFile, $delay = null)
    {
        $delayTime = 0;
        if ($delay) {
            $delayTime = $delay * 1000;
        }
        $command =  $this->ffmpeg . ' -y -i '. $audioFile .' -i '. $videoFile .' -c:v copy -c:a aac -strict experimental -filter_complex "[0]adelay='. $delayTime .'|'. $delayTime .'[del1],[1][del1]amix" ' . $saveFile;
        exec($command, $output, $result_code);
        if ($result_code == 0) {
            return true;
        }
        return  false;
    }


    /**
     * 靜音
     */
    public function audioMute($source, $saveFile)
    {
        $command =  $this->ffmpeg . ' -y -i '. $source .' -filter:a "volume=0" ' . $saveFile;
        exec($command, $output, $result_code);
        if ($result_code == 0) {
            return true;
        }
        return  false;
    }


    /**
     * 提取視訊的音訊
     * @param $source string 需要提取聲音的視訊
     * @param $saveFile string 提取聲音後儲存的音訊
     * @return bool
     */
    public function collectAudio($source, $saveFile)
    {
        $command =  $this->ffmpeg . ' -y -i '. $source .' -vn -acodec copy ' . $saveFile;
        exec($command, $output, $result_code);
        if ($result_code == 0) {
            return true;
        }
        return  false;
    }

    /**
     * 去除視訊聲音
     * @param $source string 需要去除聲音的視訊
     * @param $saveFile string 去除聲音後儲存的視訊
     */
    public function removeAudio($source, $saveFile)
    {
        $command =  $this->ffmpeg . ' -y -i '. $source .' -an ' . $saveFile;

        exec($command, $output, $result_code);
        if ($result_code == 0) {
            return true;
        }
        return  false;
    }

    /**
     * 視訊拼接
     * @param $sources array 需要拼接的視訊/音訊
     * @param $saveFile string 拼接後的視訊/音訊
     */
    public function spliceVideo($sources, $saveFile)
    {
        $commands = [];
        $temporaryFile = [];
        $basePath = sys_get_temp_dir();
        $index = 0;
        foreach ($sources as $i => $source) {
            $file = $basePath . '/' . $i . '.ts';
            $commands[$index] = $this->ffmpeg . ' -y -i '. $source .' -vcodec copy -acodec copy -vbsf h264_mp4toannexb ' . $file;
            $temporaryFile[] = $file;
            $index++;
        }
        $commands[$index] = $this->ffmpeg . ' -y -i "concat:'. implode('|', $temporaryFile) .'"  -acodec copy -vcodec copy -absf aac_adtstoasc ' . $saveFile;
        foreach ($commands as $command) {
            exec($command, $output, $result_code);
        }

        foreach ($temporaryFile as $file) {
            @unlink($file);
        }
        return true;

    }


    /**
     * 視訊剪下
     * @param $source string 需要剪下視訊/音訊
     * @param $saveFile string 剪下後儲存視訊/音訊
     * @param $star string 剪下開始時間
     * @param null $duration string 剪下時長
     */
    public function clipVideo($source, $saveFile, $star, $duration = null)
    {
        $command = $this->ffmpeg . ' -y -ss '. $star;
        if ($duration) {
            $command .= ' -t '. $duration;
        }
        $command .= ' -i '. $source .' -acodec copy ' . $saveFile;
        exec($command, $output, $result_code);
        if ($result_code == 0) {
            return true;
        }
        return  false;
    }

    const ROTATE_90 = 'transpose=1';
    const ROTATE_180 = 'hflip,vflip';
    const ROTATE_270 = 'transpose=2';

    /**
     * 視訊旋轉
     * @param $source string 需要旋轉的視訊
     * @param $saveFile string 旋轉後視訊
     * @param $rotate string 旋轉角度
     */
    public function transposeVideo($source, $saveFile, $rotate)
    {

        $command = $this->ffmpeg . ' -y -i ' . $source . ' -vf ""transpose=1"" ' . $saveFile;
        exec($command, $output, $result_code);
        if ($result_code == 0) {
            return true;
        }
        return  false;
    }

    /**
     * 視訊轉碼
     * @param $source string 需要轉碼的視訊/音訊
     * @param $saveFile string 轉碼後的視訊/音訊
     */
    public function acodecVideo($source, $saveFile)
    {
        $command = $this->ffmpeg . ' -y -i '. $source .' -acodec copy -vcodec copy -f mp4 ' . $saveFile;
        exec($command, $output, $result_code);
        if ($result_code == 0) {
            return true;
        }
        return  false;
    }

    /**
     * 視訊拼接
     * @param $sources array 需要拼接的視訊/音訊
     * @param $saveFile string 拼接後的視訊/音訊
     */
    public function concatVideo($sources, $saveFile)
    {
        $file = $this->createTemporaryFile();
        $fileStream = @fopen($file, 'w');
        if($fileStream === false) {
            throw new ServerErrorHttpException('Cannot open the temporary file.');
        }

        $count_videos = 0;
        if(is_array($sources) && (count($sources) > 0)) {
            foreach ($sources as $videoPath) {
                $line = "";
                if($count_videos != 0)
                    $line .= "\n";
                $line .= "file '". str_replace('\\','/',$videoPath) ."'";

                fwrite($fileStream, $line);
                $count_videos++;
            }
        }
        else {
            throw new ServerErrorHttpException('The list of videos is not a valid array.');
        }

        $command = $this->ffmpeg .' -y -f concat -safe 0 -i '. $file . ' -c copy ' . $saveFile;
        exec($command, $output, $result_code);
        fclose($fileStream);
        @unlink($file);//刪除檔案
        if ($result_code == 0) {
            return true;
        }
        return  false;
    }

    /**
     * 建立一個臨時檔案
     */
    public function createTemporaryFile()
    {
        $basePath = sys_get_temp_dir();
        if (false === $file = @tempnam($basePath, null)) {
            throw new ServerErrorHttpException('Unable to generate a temporary filename');
        }
        return $file;
    }

    /**
     * 獲取視訊資訊
     * @param $source string 需要獲取時長的資源
     */
    public function getAttributes($source)
    {
        ob_start();
        $command = $this->ffmpeg . ' -i "'. $source .'" 2>&1';
        passthru($command);
        $getContent = ob_get_contents();
        ob_end_clean();
        $duration = 0;
        $widht = 0;
        $height = 0;
        if (preg_match("/Duration: (.*?), start: (.*?), bitrate: (\d*) kb\/s/", $getContent, $match)) {
            $matchs = explode(':', $match[1]);
            $duration = $matchs[0] * 3600 + $matchs[1] * 60 + $matchs[2]; //轉換播放時間為秒數
        }

        if (preg_match("/Video: (.*?), (.*?), (.*?)[,\s]/", $getContent, $match)) {
            $matchs = explode('x', $match[3]);
            $widht = $matchs[0];
            $height = $matchs[1];
        }

        return [
            'duration' => intval($duration),
            'widht' => intval($widht),
            'height' => intval($height),
        ];
    }

}

使用簡單示例

這裡注意如果無法執行ffmpeg,例項化時需要傳入ffmpeg的安裝地址,例如linux下ffmpeg安裝地址為/usr/local/ffmepg,那麼例項化時需要傳入/usr/local/ffmpeg/bin/ffmpeg

1:給視訊新增文字

$ffmpeg = new FfmpegVideo();
$ffmpeg ->titleWater(
    'XXX',//原視訊
    'XXX',//處理後儲存視訊
    'XXX',//文字
    [
        'x' => 30,//水平距離
        'y' => 30,//垂直距離
        'fontsize' => 20,//文字大小
        'fontcolor' => 'red',//文字顏色
        'shadowy' => 2,//文字陰影
    ],
    200,//每秒移動步長
    2//文字出現時間(秒)
);

2:將視訊設為靜音

$ffmpeg = new FfmpegVideo();
$ffmpeg->audioMute(
    'XXX',//原視訊
    'XXX',//處理後儲存視訊
);

3:視訊裁剪

$ffmpeg = new FfmpegVideo();
$ffmpeg->clipVideo(
    'XXX',//原視訊
    'XXX',//處理後儲存視訊
    0,//裁剪開始時間
    10//裁剪時長
);

4:視訊拼接

$ffmpeg = new FfmpegVideo();
$ffmpeg->concatVideo(
    ['XXX', 'XXX'],//需要拼接的視訊
    'XXX',//處理後儲存視訊
);

5:將音訊合併到視訊中

$ffmpeg = new FfmpegVideo();
$ffmpeg->mergeVideoAudio(
    'XXX',//視訊
    'XXX',//音訊
    'XXX',//處理後儲存視訊
    0//音訊插入視訊延時時間(秒)
);

6:獲取視訊資訊(長,寬,時長)

$ffmpeg = new FfmpegVideo();
$ffmpeg->getAttributes(
    'XXX',//視訊
);

其他方法可檢視demo

相關文章