使用php调用ffmpeg机翻视频


现在国外剧很难找到压制的或者翻译好的,本代码可以实现翻译字幕,同时将字幕添加到视频上,实现视频字幕机翻。

请提前下载好以下软件:

php-8.2.4

ffmpeg

OpenCC

都是压缩包格式,无需安装,解压缩后就可以使用。

确保将上面3个软件的运行路径添加到path路径了。

需要准备mkv视频,并且视频要有字幕。

如果视频有中文字幕,则直接使用中文字幕压制,无需调用腾讯云翻译或者百度翻译。

如果视频有繁体字幕,则会翻译成简体字幕后压制。

如果视频只有英文字幕,则会调用翻译api翻译成中文字幕,然后压制。

需要先安装腾讯云翻译sdk

{
    "require": {
        "tencentcloud/tencentcloud-sdk-php": "^3.0"
    }
}

只有1个php文件,代码如下:

<?php
/**
 * 需要下载 php、ffmpeg、OpenCC
 * php:C:\dev\php\php-8.2.4
 * ffmpeg:C:\dev\ffmpeg
 * OpenCC:C:\dev\OpenCC
 * 运行方法:C:\dev\php\php-8.2.4\php.exe -f fy.php
 */
require_once 'vendor/autoload.php';

use TencentCloud\Common\Credential;
use TencentCloud\Common\Exception\TencentCloudSDKException;
use TencentCloud\Common\Profile\ClientProfile;
use TencentCloud\Common\Profile\HttpProfile;
use TencentCloud\Tmt\V20180321\Models\TextTranslateRequest;
use TencentCloud\Tmt\V20180321\TmtClient;


// mkv视频所在文件夹位置
$translate_video_dir = 'C:\迅雷下载\机翻视频前';
// 翻译后的视频所在文件夹,包含翻译后的视频以及提取的字幕、翻译的字幕
$translate_dir = 'C:\迅雷下载\机翻视频后';
// OpenCC 繁体翻译成简体字的配置文件
$t2s = 'C:\dev\OpenCC\build\share\opencc\t2s.json';

// --------------------------------------------------------------

// 开始翻译
translate_dir($translate_video_dir);
/**
 * 翻译文件夹内的视频
 * @param $video_dir
 * @return void
 */
function translate_dir($video_dir)
{
    global $translate_video_dir, $translate_dir;
    echo PHP_EOL;
    echo '翻译视频文件夹:' . $video_dir . PHP_EOL;
    $files = scandir($video_dir);
    foreach ($files as $file) {
        if ('.' === $file || '..' === $file) {
            continue;
        }
        // 如果是文件夹
        if (is_dir($video_dir . '/' . $file)) {
            translate_dir($video_dir . '/' . $file);
            continue;
        }
//        echo $file . PHP_EOL;
        if (!str_ends_with($file, 'mkv')) {
//            echo '非mkv视频文件,不处理' . PHP_EOL;
            continue;
        }
        echo PHP_EOL;
        $file_name = pathinfo($file, PATHINFO_FILENAME);
        // mkv视频文件
        $mkv_file = realpath($video_dir . '/' . $file);
        // 相对文件夹
        $path = trim(str_replace($translate_video_dir, '', $video_dir), '\\');
        $path = trim($path, '/');
        $relate_dir = $translate_dir;
        if (!empty($path)) {
            $relate_dir = $translate_dir . '/' . $path;
        }
        // mkv翻译后的视频文件
        $mp4_file = $relate_dir . '/' . $file_name . '.mp4';
        if (file_exists($mp4_file)) {
            echo '视频已经翻译,无需再次翻译' . PHP_EOL;
            continue;
        }
        // 字幕文件夹
        $srt_dir = $relate_dir . '/字幕/' . $file_name;
        // 使用的翻译字幕文件
        $srt_file = $srt_dir . '/' . $file_name . '.srt';
        if (!file_exists($srt_file)) {
            // 提取字幕文件
            echo '提取字幕文件' . PHP_EOL;
            get_srt($mkv_file, $srt_dir);
            // 翻译字幕文件,获取最终中文简体字幕文件
            translate_video($srt_dir, $srt_file);
        }
        // 直接给视频加字幕
        mkv_to_mp4($mkv_file, $srt_file, $mp4_file);
    }
}

/**
 * 获取最终中文字幕文件
 * @param string $srt_dir 字幕文件夹
 * @param string $srt_file 最终字幕文件
 * @return void
 */
function translate_video($srt_dir, $srt_file)
{
    if (file_exists($srt_file)) {
        echo '字幕文件已存在,无需翻译:' . $srt_file . PHP_EOL;
        return;
    }
    $files = scandir($srt_dir);
    // 英文字幕文件
    $en_srt_file = '';
    $en_srt_file_size = 0;
    // 中文简体字幕文件
    $chi_srt_file = '';
    // 中文繁体字幕文件
    $chi_traditional_srt_file = '';
    foreach ($files as $file) {
        if ('.' === $file || '..' === $file) {
            continue;
        }
        if (preg_match('/_eng/i', $file)) {
            if (!empty($en_srt_file) && preg_match('/_SDH/i',$file)){
                continue;
            }
            if (filesize($srt_dir . '/' . $file) > $en_srt_file_size) {
                $en_srt_file = $srt_dir . '/' . $file;
                $en_srt_file_size = filesize($en_srt_file);
            }
        } else if (preg_match('/_chi/i', $file)) {
            if (preg_match('/(Traditional|Hong Kong)/i', $file)) {
                $chi_traditional_srt_file = $srt_dir . '/' . $file;
            } else {
                $chi_srt_file = $srt_dir . '/' . $file;
            }
        }
    }

    if (!empty($chi_srt_file)) {
        echo '简体字幕文件已存在,直接复制' . PHP_EOL;
        copy($chi_srt_file, $srt_file);
    } else if (!empty($chi_traditional_srt_file)) {
        echo '繁体字幕文件已存在,直接翻译' . PHP_EOL;
        global $t2s;
        $ouuput = `opencc -i "{$chi_traditional_srt_file}" -o "{$srt_file}" -c "{$t2s}"`;
        echo $ouuput . PHP_EOL;
    } else if (!empty($en_srt_file)) {
        echo '英文字幕文件已存在,直接翻译' . PHP_EOL;
        $srtData = file_get_contents($en_srt_file);
        $srtArr = explode("\n", $srtData);
        $out_data = [];
        foreach ($srtArr as $line) {
            $line = trim($line);
            echo $line . PHP_EOL;
            if (is_numeric($line)) {
                $out_data[] = $line;
                continue;
            }
            if (preg_match('/^\d{2}:\d{2}:\d{2},\d{3}/', $line)) {
                $out_data[] = $line;
                continue;
            }
            if (!empty($line)) {
                sleep(1);
                $fanyi = fy($line);
                echo '翻译结果:' . $fanyi . PHP_EOL;
                $out_data[] = $fanyi;
            } else {
                $out_data[] = $line;
            }
        }
        $res = file_put_contents($srt_file, implode("\n", $out_data));
        if ($res) {
            echo '字幕翻译成功' . PHP_EOL;
        } else {
            echo '字幕翻译失败' . PHP_EOL;
        }
    } else {
        echo '未找到任何可用的字幕文件' . PHP_EOL;
    }
}

/**
 * 导出视频文件所有字幕文件
 * @param string $mkv_file
 * @param string $srt_dir 字幕文件夹
 * @return void
 */
function get_srt($mkv_file, $srt_dir)
{
    echo '提取视频:' . $mkv_file . PHP_EOL;
    $output = `ffprobe -v error -select_streams s -show_entries stream=index:stream_tags=language,title -of json "{$mkv_file}"`;
    if (empty($output)) {
        echo '获取视频字幕信息失败:' . $output . PHP_EOL;
        return;
    }
    $data = json_decode($output, true);
    if (!empty($data['streams'])) {
        foreach ($data['streams'] as $v) {
            $index = $v['index'];
            $srt_file = $srt_dir . '/' . $index . '_' . $v['tags']['language'];
            if (!empty($v['tags']['title'])) {
                $srt_file .= '_' . $v['tags']['title'];
            }
            $srt_file .= '.srt';
            if (file_exists($srt_file)) {
                echo '字幕文件已存在,无需导出' . PHP_EOL;
                continue;
            }
            createDirByFile($srt_file);
            $output = `ffmpeg -i "{$mkv_file}" -map "0:{$index}" "{$srt_file}"`;
            echo $output . PHP_EOL;
        }
    }
}

/**
 * 通过给定的文件创建目录
 * @param string $file 文件路径
 * @return bool
 */
function createDirByFile(string $file): bool
{
    if (file_exists($file)) {
        return true;
    }
    $dirname = pathinfo($file, PATHINFO_DIRNAME);
    return createDir($dirname);
}

/**
 * 创建文件夹
 * @param string $dir
 * @return bool
 */
function createDir(string $dir): bool
{
    if (!is_dir($dir)) {
        return mkdir($dir, 0777, true);
    }
    return true;
}

/**
 * 视频转换为mp4并添加字幕
 * @param $mkv_file
 * @param $srt_file
 * @param $mp4_file
 * @return void
 */
function mkv_to_mp4($mkv_file, $srt_file, $mp4_file)
{
    $srt_file = realpath($srt_file);
    echo '翻译视频:' . $mkv_file . PHP_EOL;
    echo '使用字幕文件:' . $srt_file . PHP_EOL;
    if (!file_exists($srt_file)) {
        echo '字幕文件不存在' . PHP_EOL;
        return;
    }
    if (file_exists($mp4_file)) {
        echo '翻译视频文件已存在,无需翻译' . PHP_EOL;
        return;
    }
    $srt_file = str_replace('\\', '/', $srt_file);
    $mkv_file = str_replace('\\', '/', $mkv_file);
    $mp4_file = str_replace('\\', '/', $mp4_file);
    createDirByFile($mp4_file);
    echo '格式化翻译视频:' . $mkv_file . PHP_EOL;
    echo '格式化字幕文件:' . $srt_file . PHP_EOL;
    echo '翻译视频文件:' . $mp4_file . PHP_EOL;

    $output = `ffmpeg -i "{$mkv_file}" -vf "subtitles=\'{$srt_file}\'" "{$mp4_file}"`;
    echo $output . PHP_EOL;
}

/**
 * 翻译
 * @param $q
 * @return string
 */
function fy($q)
{
    $result = tencent_fy($q);
    if (empty($result)) {
        $result = baidu_fy($q);
    }
    return $result;
}

/**
 * 腾讯翻译
 * @param $q
 * @return mixed|string
 */
function tencent_fy($q)
{
    try {
        $cred = new Credential("换成您的密钥访问信息", "换成您的密钥访问信息");
        // 实例化一个http选项,可选的,没有特殊需求可以跳过
        $httpProfile = new HttpProfile();
        $httpProfile->setEndpoint("tmt.tencentcloudapi.com");
        $clientProfile = new ClientProfile();
        $clientProfile->setHttpProfile($httpProfile);
        // 实例化要请求产品的client对象,clientProfile是可选的
        $client = new TmtClient($cred, "ap-chengdu", $clientProfile);

        $req = new TextTranslateRequest();

        $params = array(
            "SourceText" => $q,
            "Source" => "auto",
            "Target" => "zh",
            "ProjectId" => 0
        );
        $req->fromJsonString(json_encode($params));

        // 返回的resp是一个TextTranslateResponse的实例,与请求对象对应
        $resp = $client->TextTranslate($req);
        if (!empty($resp->TargetText)) {
            return $resp->TargetText;
        }
    } catch (TencentCloudSDKException $e) {
        echo $e;
    }
    return '';
}

/**
 * 百度翻译
 * @param $q
 * @return mixed
 */
function baidu_fy($q)
{
    $from = 'auto';
    $to = 'zh';
    $appid = '您的应用ID';
    $salt = mt_rand(1000, 9999);
    $secret = '您的密钥';
    $sign = md5($appid . $q . $salt . $secret);
    $url = 'http://api.fanyi.baidu.com/api/trans/vip/translate?' . http_build_query([
            'q' => $q,
            'from' => $from,
            'to' => $to,
            'appid' => $appid,
            'salt' => $salt,
            'sign' => $sign,
        ]);
    $result = file_get_contents($url);
    if (empty($result)) {
        return $q;
    }
    $arr = json_decode($result, true);
    if (!empty($arr['trans_result'][0]['dst'])) {
        return $arr['trans_result'][0]['dst'];
    }
    return $q;
}

需要将代码中的翻译api密钥信息换成自己的,然后将视频放到指定文件夹下,cmd窗口下运行这个文件即可。