Last active
October 17, 2022 02:04
-
-
Save jyxjjj/51dca37f10e3e9380425fb02d5964a1a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> | |
<title>Media</title> | |
<link rel="dns-prefetch" href="https://unpkg.com"> | |
<link rel="preconnect" href="https://unpkg.com"> | |
<link href="https://unpkg.com/bootstrap@3.4.1/dist/css/bootstrap.min.css" rel="stylesheet"> | |
</head> | |
<body> | |
<div> | |
<div id="initer"> | |
请等待初始化... | |
</div> | |
<div id="inited" style="display: none;"> | |
<div class="input-group"> | |
<span class="input-group-addon">原视频所在文件夹(-i)</span> | |
<input type="text" class="form-control" name="input" id="input"> | |
</div> | |
<div class="input-group"> | |
<span class="input-group-addon">输出目标文件夹</span> | |
<input type="text" class="form-control" name="output" id="output"> | |
</div> | |
<div class="input-group"> | |
<span class="input-group-addon">视频开始时间(-ss)</span> | |
<input type="text" class="form-control" name="ss" id="ss" placeholder="00:00:03.000" value="00:00:03.000"> | |
</div> | |
<div class="alert alert-info" style="margin-bottom: 0;"> | |
(移除3秒请输入00:00:03.000) | |
</div> | |
<div class="input-group"> | |
<span class="input-group-addon">视频结束时间(-to)</span> | |
<input type="text" class="form-control" name="to" id="to"> | |
</div> | |
<div class="alert alert-info" style="margin-bottom: 0;"> | |
(不统一视频时长或称保留原视频时长请留空) | |
</div> | |
<div class="input-group"> | |
重新编码(-c:v libx264 -c:a aac) | |
<input type="checkbox" name="recovert" id="recovert"> | |
</div> | |
<div class="alert alert-warning" style="margin-bottom: 0;"> | |
(如希望保留原视频比特率、帧率、音频比特率、音频采样率,请不要选择重新编码) | |
</div> | |
<div id="params" style="display: none;"> | |
<div class="input-group"> | |
<span class="">尝试使用NVIDIA GPU加速(-c:v h264_nvenc)</span> | |
<input type="checkbox" name="gpu" id="gpu"> | |
</div> | |
<div class="alert alert-danger" id="noNVIDIA" style="display: none;"> | |
您的系统不支持NVIDIA GPU加速 | |
</div> | |
<div class="input-group"> | |
<span class="input-group-addon">视频比特率(-b:v)</span> | |
<input type="number" class="form-control" name="vbitrate" id="vbitrate" min="1000" max="10200" step="100"> | |
</div> | |
<div class="alert alert-info" style="margin-bottom: 0;"> | |
(1440k请输入1440,2M请输入2048) | |
<br /> | |
(常用比特率列表请参考:<a class="alert-link" href="https://support.google.com/youtube/answer/1722171?hl=zh-Hans">Google Youtube Support</a>) | |
<br /> | |
(注意:设置更高比特率将保留更高画质,但如果原视频无法达到该比特率,则设置更高比特率没有任何意义,反而会增加文件大小) | |
<br /> | |
(如希望让系统自动设置,请输入0) | |
</div> | |
<div class="input-group"> | |
<span class="input-group-addon">视频帧率(-r)</span> | |
<select class="form-control" name="vrate" id="vrate"> | |
<option value="23.98">23.98</option> | |
<option value="24">24</option> | |
<option value="30" selected>30</option> | |
<option value="60">60</option> | |
</select> | |
</div> | |
<div class="input-group"> | |
<span class="input-group-addon">音频比特率(-b:a)</span> | |
<select class="form-control" name="abitrate" id="abitrate"> | |
<option value="128" selected>128k</option> | |
<option value="192">192k</option> | |
<option value="256">256k</option> | |
</select> | |
</div> | |
<div class="input-group"> | |
<span class="input-group-addon">音频采样率(-ar)</span> | |
<select class="form-control" name="arate" id="arate"> | |
<option value="44100" selected>44100Hz</option> | |
<option value="48000">48000Hz</option> | |
</select> | |
</div> | |
</div> | |
<a class="btn btn-primary btn-sm" href="javascript:refreshdemo();">刷新样例</a> | |
<a class="btn btn-primary btn-sm" href="javascript:startgenerate();">开始生成</a> | |
<div id="stdout1"> | |
命令样例:<br /> | |
<code id="stdout1_span">ffmpeg -i '输入文件名' -ss 00:00:03.000 -c:v copy -c:a copy '输出文件名'</code> | |
</div> | |
<textarea readonly id="stdout2" class="form-control" style="height: 200px;"></textarea> | |
<a class="btn btn-primary btn-sm" id="copier" href="javascript:copyresult();">复制输出结果</a> | |
</div> | |
</div> | |
<script src="https://unpkg.com/jquery@3.6.0/dist/jquery.min.js"></script> | |
<script src="https://unpkg.com/bootstrap@3.4.1/dist/js/bootstrap.min.js"></script> | |
<script> | |
let win = nw.Window.get(); | |
win.height = 920; | |
win.width = 1280; | |
win.on('close', event => { | |
win.close(true); | |
App.quit(); | |
}); | |
$('#inited').show(); | |
$('#initer').hide(); | |
$('#recovert').on('change', () => { | |
if ($('#recovert').is(':checked')) { | |
$('#params').show(); | |
} else { | |
$('#params').hide(); | |
} | |
}); | |
</script> | |
<script> | |
let demo = $('#stdout1_span'); | |
demo.text("ffmpeg -hide_banner -i \"输入文件名\" -ss 00:00:03.000 -c:v copy -c:a copy \"输出文件名\""); | |
</script> | |
<script> | |
checkNVIDIA = () => { | |
let noNVIDIA = $('#noNVIDIA'); | |
const cp = require('child_process'); | |
let proc = cp.spawnSync('ffmpeg', [ | |
'-hide_banner', | |
'-encoders', | |
], { | |
maxBuffer: 4096 * 4096, | |
shell: false, | |
windowsHide: true, | |
}); | |
if (proc.stdout.toString().includes('h264_nvenc')) { | |
noNVIDIA.hide(); | |
return true; | |
} else { | |
noNVIDIA.show(); | |
return false; | |
} | |
} | |
refreshdemo = () => { | |
let ss = $('#ss').val(); | |
let to = $('#to').val(); | |
let recovert = $('#recovert').is(':checked'); | |
let useNVIDIA = $('#gpu').is(':checked'); | |
let vbitrate = $('#vbitrate').val(); | |
let vrate = $('#vrate').val(); | |
let abitrate = $('#abitrate').val(); | |
let arate = $('#arate').val(); | |
let cmd = `ffmpeg -hide_banner -i "输入文件名"`; | |
ss != '' && (cmd += ` -ss ${ss}`); | |
to != '' && (cmd += ` -to ${to}`); | |
if (recovert) { | |
if (useNVIDIA && checkNVIDIA()) { | |
cmd += ` -c:v h264_nvenc`; | |
} else { | |
cmd += ` -c:v libx264`; | |
} | |
if (vbitrate == '') { | |
vbitrate = 0; | |
} | |
if (vbitrate != 0) { | |
cmd += ` -b:v ${vbitrate}k`; | |
} | |
cmd += ` -r ${vrate} -c:a aac -b:a ${abitrate}k -ar ${arate}`; | |
} else { | |
cmd += ` -c:v copy -c:a copy`; | |
} | |
cmd += ` "输出文件名"`; | |
demo.text(cmd); | |
} | |
startgenerate = () => { | |
refreshdemo(); | |
const fs = require('fs'); | |
let input = $('#input').val(); | |
let output = $('#output').val(); | |
if (input == '') { | |
alert('请输入原视频所在文件夹'); | |
return; | |
} | |
if (output == '') { | |
alert('请输入输出目标文件夹'); | |
return; | |
} | |
let cmd = demo.text(); | |
input = input.replace(/\\/g, '/'); | |
output = output.replace(/\\/g, '/'); | |
// scan input dir | |
let files = []; | |
let dir = fs.readdirSync(input); | |
for (let i = 0; i < dir.length; i++) { | |
let file = dir[i]; | |
// if not dir, push to files | |
if (!fs.statSync(`${input}/${file}`).isDirectory()) { | |
console.log(`${input}/${file}`); | |
files.push(file); | |
} | |
} | |
// generate cmd | |
let cmds = []; | |
for (let i = 0; i < files.length; i++) { | |
let file = files[i]; | |
console.log(file); | |
temp = cmd.replace(/输入文件名/g, `${input}/${file}`); | |
temp = temp.replace(/输出文件名/g, `${output}/${file}`); | |
cmds.push(temp); | |
} | |
// out put cmd to textarea | |
let stdout = $('#stdout2'); | |
stdout.text(''); | |
for (let i = 0; i < cmds.length; i++) { | |
stdout.text(stdout.text() + cmds[i] + '\n'); | |
} | |
} | |
copyresult = () => { | |
let stdout = $('#stdout2'); | |
let clipboard = nw.Clipboard.get(); | |
clipboard.set(stdout.text(), 'text'); | |
let copier = $('#copier'); | |
if (clipboard.get('text') == stdout.text()) { | |
copier.text('复制成功'); | |
setTimeout(() => { | |
copier.text('复制输出结果'); | |
}, 1000); | |
} | |
} | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Commands; | |
use Illuminate\Console\Scheduling\Schedule; | |
use LaravelZero\Framework\Commands\Command; | |
class BuildCommand extends Command | |
{ | |
protected $signature = 'build'; | |
protected $description = 'Build Media Commands to remove 3 seconds at the video start'; | |
public function handle(): int | |
{ | |
$input = $this->anticipate('请输入现有媒体文件所在文件夹', []); | |
if (!is_dir($input)) { | |
$this->error('输入文件夹不存在'); | |
return self::INVALID; | |
} | |
$output = $this->anticipate('请输入媒体文件输出目标文件夹', []); | |
if (!is_dir($output)) { | |
$this->error('输出文件夹不存在'); | |
return self::INVALID; | |
} | |
$this->info('本工具暂只支持修改输出文件编码,输入文件的编码将使用默认解码器进行CPU解码'); | |
$recode = $this->ask('是否重新编码为h264+aac格式?(请输入y或n)', 'y'); | |
$recode = $recode === 'y'; | |
if ($recode) { | |
$acodec = 'aac'; | |
$useNvidia = $this->ask('是否尝试使用NVIDIA加速?(请输入y或n)', 'y'); | |
$useNvidia = $useNvidia === 'y'; | |
if ($useNvidia) { | |
$encoder = $this->getEncoder(); | |
} else { | |
$encoder = 'libx264'; | |
} | |
if ($useNvidia && $encoder === 'libx264') { | |
$this->warn('尝试使用NVIDIA加速失败,你的计算机没有安装NVIDIA显卡。'); | |
$this->info('如果你的计算机已经安装了NVIDIA显卡,请点此联系作者:https://t.me/jyxjjj'); | |
} else { | |
$this->info('成功检测到NVIDIA显卡加速支持'); | |
} | |
$this->info("将使用{$encoder}编码器"); | |
$vcodec = $encoder; | |
} else { | |
$vcodec = 'copy'; | |
$acodec = 'copy'; | |
} | |
if ($vcodec == 'copy') { | |
$command = "ffmpeg -hide_banner -i '输入文件名' -ss 00:00:03.00 -c:v copy -c:a copy '输出文件名'"; | |
} else { | |
$vbitrate = $this->anticipate(<<<EOF | |
请输入输出视频比特率 | |
(请勿输入单位如K、M。但必须使用以K为单位的纯数字。如1440k请输入1440,2M请输入2048) | |
(常用比特率列表请参考:https://support.google.com/youtube/answer/1722171?hl=zh-Hans) | |
(注意:设置更高比特率将保留更高画质,但如果原视频无法达到该比特率,则设置更高比特率没有任何意义) | |
(如希望让系统自动设置,请输入0,如希望保留原视频比特率,请重新运行本命令,且不要选择重新编码) | |
请输入输出视频比特率 | |
EOF | |
, []); | |
$abitrate = $this->choice(<<<EOF | |
请输入输出音频比特率 | |
暂只支持128或192或256 | |
(请勿输入单位如K。但必须使用以K为单位的纯数字。如128k请输入128) | |
(如希望让系统自动设置,请输入0,如希望保留原音频比特率,请重新运行本命令,且不要选择重新编码) | |
请输入输出音频比特率 | |
EOF | |
, ['0', '128', '192', '256',], 0); | |
$vrate = $this->choice(<<<EOF | |
请输入输出视频帧率 | |
暂只支持30或60 | |
(请勿输入单位如r。但必须使用以r为单位的纯数字。如30帧请输入30) | |
(如希望让系统自动设置,请输入0,如希望保留原视频帧率,请重新运行本命令,且不要选择重新编码) | |
请输入输出视频帧率 | |
EOF | |
, ['0', '30', '60',], 0); | |
$arate = $this->choice(<<<EOF | |
请输入输出音频采样率 | |
暂只支持44100或48000 | |
(如希望让系统自动设置,请输入0,如希望保留原音频采样率,请重新运行本命令,且不要选择重新编码) | |
请输入输出音频采样率 | |
EOF | |
, ['0', '44100', '48000',], 0); | |
$command = "ffmpeg -hide_banner -i '输入文件名' -ss 00:00:03.00 -c:v {$vcodec} "; | |
if ($vbitrate != 0) { | |
$command .= "-b:v {$vbitrate}k "; | |
} | |
if ($vrate != 0) { | |
$command .= "-r {$vrate} "; | |
} | |
$command .= "-c:a {$acodec} "; | |
if ($abitrate != 0) { | |
$command .= "-b:a {$abitrate}k "; | |
} | |
if ($arate != 0) { | |
$command .= "-ar {$arate} "; | |
} | |
$command .= "'输出文件名'"; | |
} | |
$this->info('完整命令行例子如下:'); | |
$this->info($command); | |
$confirm = $this->confirm('确定要生成此命令吗?', true); | |
if ($confirm) { | |
$commands = ''; | |
$this->info('正在生成命令...'); | |
$files = $this->getAllFiles($input); | |
$this->info('共找到' . count($files) . '个文件'); | |
foreach ($files as $file) { | |
$c = str_replace('输入文件名', $file, $command); | |
$c = str_replace('输出文件名', str_replace($input, $output, $file), $c); | |
$commands .= $c . PHP_EOL; | |
} | |
$this->info('命令生成完毕'); | |
$this->line($commands); | |
} | |
return self::SUCCESS; | |
} | |
public function schedule(Schedule $schedule) | |
{ | |
} | |
private function getEncoder(): string | |
{ | |
$command = 'ffmpeg -hide_banner -encoders'; | |
$proc = proc_open( | |
$command, | |
[ | |
1 => ['pipe', 'w'], | |
2 => ['pipe', 'w'], | |
], | |
$pipes, | |
__DIR__, | |
null, | |
[ | |
'suppress_errors' => true, | |
'bypass_shell' => true, | |
'blocking_pipes' => true, | |
'create_process_group' => false, | |
'create_new_console' => false, | |
] | |
); | |
$stdout = stream_get_contents($pipes[1]); | |
$stderr = stream_get_contents($pipes[2]); | |
fclose($pipes[1]); | |
fclose($pipes[2]); | |
$status = proc_get_status($proc); | |
$exitCode = proc_close($proc); | |
$codecs = explode("\n", $stdout); | |
// only get all h264 codecs | |
$h264Codecs = array_filter($codecs, function ($codec) { | |
return str_contains($codec, 'h264'); | |
}); | |
// trim | |
$h264Codecs = array_map(function ($codec) { | |
return trim($codec); | |
}, $h264Codecs); | |
// get the codecs name at the 2nd column | |
$h264Codecs = array_map(function ($codec) { | |
return explode(' ', $codec)[1]; | |
}, $h264Codecs); | |
// detect if support nvenc | |
$nvenc = in_array('h264_nvenc', $h264Codecs); | |
if ($nvenc) { | |
return 'h264_nvenc'; | |
} else { | |
return 'libx264'; | |
} | |
} | |
private function getAllFiles($dir): array | |
{ | |
$files = []; | |
$dirs = array_diff(scandir($dir), ['.', '..']); | |
foreach ($dirs as $d) { | |
if (!is_dir("{$dir}/{$d}")) { | |
$files[] = "{$dir}/{$d}"; | |
$this->info("发现文件: {$dir}/{$d}"); | |
} else { | |
$this->warn("本工具暂不支持子文件夹,将跳过子文件夹: {$dir}/{$d}"); | |
} | |
} | |
return $files; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment