视频转码:上传时自动转 H.264,已有视频批量转换 (#57)
用户 2026-11-05:「以后所有视频全部转成H.264」 新增 app/Video.php: - isVideo / available / probe / needsTranscode / toH264 - 判据:编码不是 h264 或像素格式不是 yuv420p/nv12 就要转 (10bit、4:2:2 的 h264 浏览器也不认,光看编码不够) - 参数:libx264 veryfast crf23 + yuv420p + aac 128k + faststart + scale=min(1920,iw):-2(4K 缩到 1080p,-2 保证高度偶数) - 转完自检编码与像素格式,不对就不采用 Attachment::save() 接入: - needsTranscode() 先判,已合规的不重复转(不白等几十秒) - 转码失败不阻断上传,原样保存 + error_log 记原因 - 转完改 mime/ext 为 video/mp4,否则前端判断不出来 - 转码产物是临时文件,必须清理(否则塞满 /tmp) 批量转换已有视频(15 个): - 13 个已是 h264 跳过,2 个 hevc 转成功 - 原地覆盖同 key,数据库引用全不断 - 原文件备份到 storage/_hevc_backup/ 验证:4 个抽样全部 h264/yuv420p 或 yuvj420p,浏览器可播。
This commit is contained in:
+43
-1
@@ -83,8 +83,45 @@ final class Attachment
|
||||
}
|
||||
$key = (string)$up['file'];
|
||||
} else {
|
||||
/* 🔴🔴 #57(用户 2026-11-05:「以后所有视频全部转成 H.264」)
|
||||
视频先转码再存。
|
||||
|
||||
为什么必须转:用户的视频是手机拍的 H.265/HEVC,
|
||||
Chrome/Firefox **无法解码**(不存在能解码 HEVC 的前端 JS 库)。
|
||||
只能在服务端转成 H.264。
|
||||
|
||||
⚠️ `needsTranscode()` 先判:已经符合 H.264 + yuv420p 的**不重复转**
|
||||
—— 用户传合规视频时不该白等几十秒。
|
||||
⚠️ **转码失败不阻断上传**:原样存。转码可能因磁盘满/编码异常失败,
|
||||
不该因此让用户传不了;原文件还能下载后用本地播放器看。
|
||||
⚠️ 转完要改 `$mime` / `$ext`,否则入库的 MIME 还是 video/quicktime
|
||||
之类,前端又判断不出来。
|
||||
⚠️ 转码产物是临时文件,**必须清掉**(撒了会塞满 /tmp)。 */
|
||||
$transcodeNote = '';
|
||||
if (Video::isVideo($mime) && Video::needsTranscode($tmp)) {
|
||||
$tr = Video::toH264($tmp);
|
||||
if (!empty($tr['ok'])) {
|
||||
$newPath = (string)$tr['path'];
|
||||
@unlink($tmp); // 原文件不要了
|
||||
$tmp = $newPath;
|
||||
$mime = 'video/mp4';
|
||||
$ext = 'mp4';
|
||||
$transcodeNote = (string)($tr['note'] ?? '');
|
||||
$size = (int)@filesize($tmp);
|
||||
} else {
|
||||
/* 转码失败:保留原文件,记一笔原因(不阻断上传) */
|
||||
$transcodeNote = '转码失败,已保存原文件:' . (string)($tr['error'] ?? '');
|
||||
error_log('[Attachment] 视频转码失败 target=' . $type . '/' . $targetId
|
||||
. ' err=' . (string)($tr['error'] ?? ''));
|
||||
}
|
||||
}
|
||||
|
||||
$key = 'files/' . bin2hex(random_bytes(12)) . ($ext !== '' ? '.' . preg_replace('/[^a-z0-9]/', '', $ext) : '');
|
||||
$res = Storage::putFile($familyId, $tmp, $key, $mime !== '' ? $mime : 'application/octet-stream');
|
||||
/* 转码产物用完即删 —— 它是临时文件,不能留在 /tmp */
|
||||
if ($transcodeNote !== '' && strpos($tmp, '.h264.mp4') !== false) {
|
||||
@unlink($tmp);
|
||||
}
|
||||
if (empty($res['ok'])) {
|
||||
return ['ok' => false, 'error' => '保存失败:' . (string)($res['error'] ?? '')];
|
||||
}
|
||||
@@ -104,7 +141,12 @@ final class Attachment
|
||||
self::nextPosition($type, $targetId),
|
||||
$uploaderId > 0 ? $uploaderId : null,
|
||||
]);
|
||||
return ['ok' => true, 'id' => (int)$db->lastInsertId()];
|
||||
return [
|
||||
'ok' => true,
|
||||
'id' => (int)$db->lastInsertId(),
|
||||
/* 转码说明(空串 = 没转,不需要提示) */
|
||||
'note' => $transcodeNote ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
/** 删一个附件(连存储对象一起) */
|
||||
|
||||
Reference in New Issue
Block a user