You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Flutter FFmpeg合并相册视频失败及相关问题咨询

问题解答

一、视频路径后缀显示为jpeg的原因

通过LecleFlutterAbsolutePath获取到的路径后缀为jpeg,但实际是视频文件,核心原因是:

  • 该插件处理媒体URI时,会将文件临时复制到应用缓存目录,且自动生成的临时文件名后缀不匹配文件实际格式,属于插件命名逻辑的问题,和文件本身的内容无关。

解决方式:

  • 无需纠结后缀:FFmpeg会根据文件实际编码识别内容,不受后缀名影响,即使后缀是jpeg,只要文件是视频格式就能正常处理。
  • 手动修正后缀:如果担心后续逻辑受影响,可获取路径后替换后缀:
    String correctedV1Path = v1Path.replaceAll(RegExp(r'\.[^.]+$'), '.mp4');
    
  • 替换路径获取方式:放弃使用LecleFlutterAbsolutePath,直接通过Android的MediaStore查询原视频真实路径,避免插件的命名bug。

二、FFmpeg报错及相册显示输出视频的解决方案

1. FFmpeg报错error 1的排查与修复

报错属于通用执行失败,常见原因及解决:

  • 视频参数不兼容:concat滤镜要求两段视频的分辨率、帧率、编码格式、像素格式完全一致,否则会失败。建议改用兼容性更好的concat demuxer方式:
    // 先创建文本文件列出视频路径
    final tempDir = await getTemporaryDirectory();
    final listFile = File('${tempDir.path}/video_list.txt');
    await listFile.writeAsString('file \'$v1\'\nfile \'$v2\'');
    // 使用demuxer命令
    final command = '-f concat -safe 0 -i ${listFile.path} -c copy $outputPath';
    
  • 命令转义问题:原命令中的单引号在Dart字符串里易引发解析错误,可直接移除内部单引号:
    final command = '-i $v1 -i $v2 -filter_complex [0:0][1:0]concat=n=2:v=1:a=0[out] -map [out] $outputPath';
    
  • 权限缺失:若输出到公共目录,需确保应用申请了WRITE_EXTERNAL_STORAGE权限(Android 10+可改用MediaStore或MANAGE_EXTERNAL_STORAGE权限)。
  • 查看详细日志:通过以下代码获取FFmpeg执行日志,定位具体错误:
    final rc = await FFmpegKit.execute(command);
    final logs = await rc.getAllLogsAsString();
    print("FFmpeg日志:$logs");
    

2. 设置输出路径让视频在相册显示

应用私有目录(getApplicationDocumentsDirectory())的文件不会被系统媒体扫描器索引,因此相册无法识别。需将输出文件放到公共视频目录,并通知系统扫描:

步骤1:获取公共视频目录路径

import 'package:path_provider/path_provider.dart';

final externalVideoDir = await getExternalStoragePublicDirectory(DirectoryMovies);
final outputDir = Directory('${externalVideoDir.path}/MyMergedVideos');
await outputDir.create(recursive: true);
// 生成唯一文件名避免覆盖
final outputPath = '${outputDir.path}/merged_video_${DateTime.now().millisecondsSinceEpoch}.mp4';

步骤2:通知系统扫描新文件

生成视频后,需触发系统媒体扫描器,让相册识别该文件:

import 'package:flutter/services.dart';

// 定义MethodChannel调用原生扫描逻辑
void notifyMediaScanner(String filePath) {
  const MethodChannel channel = MethodChannel('com.example.media_scanner');
  channel.invokeMethod('scanFile', {'path': filePath});
}

同时在Android原生代码中实现扫描逻辑(MainActivity.kt):

class MainActivity : FlutterActivity() {
    private val CHANNEL = "com.example.media_scanner"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
            if (call.method == "scanFile") {
                val path = call.argument<String>("path") ?: run {
                    result.error("INVALID_PATH", "路径为空", null)
                    return@setMethodCallHandler
                }
                MediaScannerConnection.connect(this, object : MediaScannerConnection.MediaScannerConnectionClient {
                    override fun onMediaScannerConnected() {
                        MediaScannerConnection.scanFile(this@MainActivity, arrayOf(path), null) { _, _ ->
                            MediaScannerConnection.disconnect()
                            result.success(null)
                        }
                    }

                    override fun onScanCompleted(p0: String?, p1: Uri?) {}
                })
            } else {
                result.notImplemented()
            }
        }
    }
}

3. 修正merge方法逻辑

原Video.merge方法忽略了传入的output参数,直接使用私有目录,需调整为使用公共路径:

class Video {
  static Future<void> merge(String v1, String v2) async {
    // 获取公共视频目录
    final externalVideoDir = await getExternalStoragePublicDirectory(DirectoryMovies);
    final outputDir = Directory('${externalVideoDir.path}/MyMergedVideos');
    await outputDir.create(recursive: true);
    final outputPath = '${outputDir.path}/merged_${DateTime.now().millisecondsSinceEpoch}.mp4';

    // 使用concat demuxer合并
    final tempDir = await getTemporaryDirectory();
    final listFile = File('${tempDir.path}/video_list.txt');
    await listFile.writeAsString('file \'$v1\'\nfile \'$v2\'');
    final command = '-f concat -safe 0 -i ${listFile.path} -c copy $outputPath';

    try {
      final rc = await FFmpegKit.execute(command);
      final returnCode = await rc.getReturnCode();
      if (ReturnCode.isSuccess(returnCode)) {
        print("视频合并成功");
        notifyMediaScanner(outputPath); // 通知系统扫描
      } else {
        final logs = await rc.getAllLogsAsString();
        print("合并失败,日志:$logs");
      }
    } catch (e) {
      print("合并异常:$e");
    }
  }
}

内容的提问来源于stack exchange,提问作者Stéphane de Luca

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 15:10:41