PHP实现MP4视频上传Windows正常 iOS Safari提示无效文件
问题说明
在Linux环境下基于PHP开发MP4视频上传、自动生成缩略图功能,通过FFmpeg组件截取视频指定帧生成缩略图。代码在所有Windows端浏览器下均可正常运行,但在iOS端Safari浏览器上传时,会直接返回「INVALID FILE」错误提示,无法完成上传,原实现代码如下:
ini_set('display_errors', 'On'); error_reporting(E_ALL); require_once '../../vendor/autoload.php' ; if($_FILES["upload_file"]["name"] != '') { $data = explode(".", $_FILES["upload_file"]["name"]); $extension = $data[1]; $allowed_extension = array("mp4"); if(in_array($extension, $allowed_extension)) { $name_only = pathinfo($_FILES["upload_file"]['name'], PATHINFO_FILENAME) ; // Main Name Only $new_video_name = $name_only.'.mp4'; $new_img_name = $name_only.'.jpg'; $path = $_POST["hidden_folder_name"] .'/'. $new_video_name; $img_path = $_POST["hidden_folder_name"] .'/'.$new_img_name ; if(move_uploaded_file($_FILES["upload_file"]["tmp_name"], $path)) { $ffmpeg = FFMpeg\FFMpeg::create(array( 'ffmpeg.binaries' => '/usr/local/bin/ffmpeg', 'ffprobe.binaries' => '/usr/local/bin/ffprobe', 'timeout' => 3600, 'ffmpeg.threads' => 12, )); $video = $ffmpeg->open($path); $video->filters()->resize(new FFMpeg\Coordinate\Dimension(320, 180))->synchronize(); $video->frame(FFMpeg\Coordinate\TimeCode::fromSeconds(5))->save($img_path); echo 'FILE UPLOADED'; } else { echo 'THERE IS AN ERROR'; } } else { echo 'INVALID FILE'; } } else { echo 'PLEASE SELECT FILE'; }
故障原因
- 后缀获取逻辑存在硬伤:原代码用
explode(".", 文件名)取下标1的元素作为文件后缀,只要文件名包含多个.(比如iOS设备生成的IMG_2024.05.20.mp4这类命名),取到的就不是真实后缀mp4,直接触发后缀校验失败返回错误。 - 未兼容后缀大小写:iOS Safari可能上传后缀为大写
.MP4的视频文件,原代码严格匹配小写mp4,也会导致校验不通过。 - 隐藏隐患:固定截取第5秒帧的逻辑,遇到iOS端上传的时长不足5秒的短视频时,FFmpeg会直接报错导致流程中断。
修复方案
- 替换后缀获取逻辑,使用PHP原生
pathinfo()的PATHINFO_EXTENSION常量获取真实后缀,完全兼容文件名带多个.的场景。 - 后缀校验前统一转小写,兼容大写后缀的上传场景。
- 增加MIME类型校验,避免仅靠后缀判断带来的恶意文件上传风险。
- 截取缩略图前先判断视频总时长,不足5秒时取第0秒帧,避免短视频处理报错。
修复后代码
ini_set('display_errors', 'On'); error_reporting(E_ALL); require_once '../../vendor/autoload.php' ; if(!empty($_FILES["upload_file"]["name"])) { // 安全获取文件后缀,兼容多.文件名,统一转小写 $extension = strtolower(pathinfo($_FILES["upload_file"]["name"], PATHINFO_EXTENSION)); $allowed_extension = array("mp4"); // 校验文件MIME类型,拦截非法文件 $allowed_mime = ["video/mp4"]; $file_mime = mime_content_type($_FILES["upload_file"]["tmp_name"]); if(in_array($extension, $allowed_extension) && in_array($file_mime, $allowed_mime)) { $name_only = pathinfo($_FILES["upload_file"]['name'], PATHINFO_FILENAME); $new_video_name = $name_only.'.mp4'; $new_img_name = $name_only.'.jpg'; $path = $_POST["hidden_folder_name"] .'/'. $new_video_name; $img_path = $_POST["hidden_folder_name"] .'/'.$new_img_name ; if(move_uploaded_file($_FILES["upload_file"]["tmp_name"], $path)) { $ffmpeg = FFMpeg\FFMpeg::create(array( 'ffmpeg.binaries' => '/usr/local/bin/ffmpeg', 'ffprobe.binaries' => '/usr/local/bin/ffprobe', 'timeout' => 3600, 'ffmpeg.threads' => 12, )); $ffprobe = FFMpeg\FFProbe::create(); $video = $ffmpeg->open($path); $video->filters()->resize(new FFMpeg\Coordinate\Dimension(320, 180))->synchronize(); // 适配短视频场景,时长不足5秒取首帧 $duration = $ffprobe->format($path)->get('duration'); $capture_time = $duration >= 5 ? 5 : 0; $video->frame(FFMpeg\Coordinate\TimeCode::fromSeconds($capture_time))->save($img_path); echo 'FILE UPLOADED'; } else { echo 'THERE IS AN ERROR'; } } else { echo 'INVALID FILE'; } } else { echo 'PLEASE SELECT FILE'; }
内容的提问来源于stack exchange,提问作者robotron
相关产品推荐
相关产品推荐

