Skip to content

Instantly share code, notes, and snippets.

@jyxjjj
Last active October 17, 2022 02:04
Show Gist options
  • Save jyxjjj/51dca37f10e3e9380425fb02d5964a1a to your computer and use it in GitHub Desktop.
Save jyxjjj/51dca37f10e3e9380425fb02d5964a1a to your computer and use it in GitHub Desktop.
<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>
<?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