使用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窗口下运行这个文件即可。