Ente Photos 视频编辑器完全指南:修剪、裁剪、旋转与双引擎导出原理
2026/9/12 16:54:23 网站建设 项目流程

Ente Photos 视频编辑器完全指南:修剪、裁剪、旋转与双引擎导出原理

【免费下载链接】ente💚 End-to-end encrypted cloud for everything.项目地址: https://gitcode.com/GitHub_Trending/en/ente

导读

本文围绕 Ente Photos(本项目 E2EE 云相册)内置的视频编辑器展开,详细讲解其三项核心编辑能力——Trim(修剪)Crop(裁剪)Rotate(旋转)的完整使用流程、保存语义,并深入源码层面剖析其「原生导出 + FFmpeg 回退」的双引擎实现原理与输出格式约定。读完本文,你将掌握如何在 iOS/Android 端完成一次不损坏原片的视频编辑,理解裁剪比例背后的坐标系与旋转补偿逻辑,以及导出引擎在何种条件下走 Passthrough(免重编码)路径。

提示:官方说明指出,视频编辑器在v1.2.18版本中经历了重构,大幅提升了大体积视频的编辑速度并优化了用户体验。本文所述功能与源码以当前仓库main分支为准。

功能总览

视频编辑器让你直接在 Ente Photos 应用内对视频做基础编辑,支持三种主要操作:

操作用途关键参数
Trim 修剪精确裁切视频长度时间轴滑块设置起止点,最短保留 1 秒
Crop 裁剪调整画面比例或自定义尺寸Free / 1:1 / 9:16 / 16:9 / 3:4 / 4:3
Rotate 旋转校正视频方向左旋 90° / 右旋 90°

所有编辑均以「另存为新副本」的方式落盘,原始视频始终保持不变。

Trim(修剪)

  • 使用时间轴滑块设定起点(start)终点(end),实时预览选区。
  • 保存前可反复预览确认所选片段。
  • 最短成片时长为 1 秒,由控制器层的minDuration = const Duration(seconds: 1)硬性约束(见 video_editor_controller.dart)。updateTrim中会先对起止点做[0, videoDuration]范围钳制,若boundedEnd - boundedStart < minDuration则直接拒绝本次修改(见 video_editor_controller.dart)。
  • 修剪后的时长在编辑界面实时显示。

值得注意的细节:当视频播放到修剪终点时,控制器会自动跳回起点循环预览(_onVideoChanged中的 trim boundary 处理),方便你反复确认裁剪区间(见 video_editor_controller.dart)。

Crop(裁剪)

裁剪支持固定比例与自由调整两种模式,内置比例在 crop_value.dart 中以枚举CropValue定义,并通过Fraction精确表达比例值:

比例适用场景(官方文档说明)源码对应枚举
Free无约束手动调整裁剪框CropValue.freenull
1:1方形格式(如 Instagram 帖子)CropValue.ratio_1_11/1
9:16竖版格式(如 Instagram Stories、TikTok)CropValue.ratio_9_169/16
16:9横屏宽幅(如 YouTube、大多数视频)CropValue.ratio_16_916/9
3:4竖版人像比例CropValue.ratio_3_43/4
4:3经典比例CropValue.ratio_4_34/3

注:CropValue还包含一个original(原片比例)选项,选择后裁剪框重置为全幅(updateCrop(Offset.zero, Offset(1.0, 1.0)))。比例选择 UI 位于 video_crop_page.dart。

裁剪与旋转的联动是此处最核心的实现细节

  • 裁剪框坐标使用归一化坐标系(0.0~1.0),updateCrop会对四个边界做clamp(0.0, 1.0)校验(见 video_editor_controller.dart)。
  • 当你点击某一比例后,preferredCropAspectRatio被写入,控制器调用_fitCropToDisplayAspectRatio以当前旋转状态修正显示比例:若视频处于 90°/270° 旋转,则内部比例取倒数(sourceRatio = _rotation == 90 || _rotation == 270 ? 1 / displayRatio : displayRatio),并以sourceDisplaySize(来自原生元数据的displayWidth/displayHeight)换算归一化矩形(见 video_editor_controller.dart)。
  • 旋转时若已锁定比例,比例值会自动翻转(_preferredCropAspectRatio = 1 / _preferredCropAspectRatio!),确保横竖屏切换后比例语义仍正确(见 video_editor_controller.dart)。
  • 视觉裁剪框与文件坐标之间的换算通过rotateNormalizedRect完成(见 video_editor_controller.dart),这正是文档所述「裁剪工具自动考虑视频旋转,保证最终输出中比例显示正确」的源码实现。

Rotate(旋转)

  • 向左旋转:逆时针 90°
  • 向右旋转:顺时针 90°

旋转状态在控制器中以累加的度数保存,rotate90Degrees每次按方向累加 90° 并对 360 取模(见 video_editor_controller.dart)。原生接口层严格限定合法旋转值为90、180、270,非法值会直接抛出ArgumentError(见 native_video_editor.dart)。

如何使用视频编辑器

移动端操作步骤(iOS / Android):

  1. 在图库中打开任意视频;
  2. 点击右上角溢出菜单(⋮),选择Edit
  3. 编辑器底部会出现三个操作按钮:
    • Trim—— 调整视频长度
    • Crop—— 改变视频画幅
    • Rotate—— 调整视频方向
  4. 可组合使用一个或多个编辑工具(修剪、裁剪、旋转可叠加应用);
  5. 点击右上角Save copy(保存副本),生成编辑后的视频。

在 UI 源码层面,这三个入口分别对应VideoTrimPageVideoCropPageVideoRotatePage三个子编辑器页面,由主页面 video_editor_page.dart 中的VideoEditorMainActions统一组织,各操作使用assets/video-editor/下的 SVG 图标标识。

保存副本后会发生什么

  • Ente 创建一个应用了全部编辑的新视频文件;
  • 原始视频保持原样,不做任何改动;
  • 新视频继承以下元数据:
    • 原视频的创建时间newFile.creationTime = widget.file.creationTime);
    • 位置数据(优先继承原文件,若缺失则尝试从相册资源异步读取经纬度,见 video_editor_page.dart);
    • 所属相册/合集newFile.collectionID = widget.file.collectionID);
  • 编辑后的视频通过SyncService.instance.sync()自动同步到你的账号;
  • 原视频与编辑副本同时出现在图库中。

保存流程的完整链路(以源码为准):导出得到临时 MP4 →PhotoManager.stopChangeNotify()暂停系统相册通知 →PhotoManager.editor.saveVideo写入系统相册 → 构建新的EnteFile并插入本地FilesDB→ 广播LocalPhotosUpdatedEvent刷新界面 → 触发云同步 → 展示成功 Toast 并跳回详情页(见 video_editor_page.dart)。整个过程结束后,临时导出文件会被清理。

视频预览与播放

编辑过程中你可以:

  • 在预览区实时查看所有已应用变换(修剪区间、裁剪框、旋转方向叠加生效);
  • 使用播放控制条播放/暂停,逐段核对编辑效果;
  • 在时间轴上拖动定位,检查任意时间点。

预览区使用VideoEditorPreview+VideoEditorPlayerControl组合实现,套在圆角容器内并带 Hero 动画标签(见 video_editor_page.dart)。预览所呈现的画面与实际导出结果一致——所有变换在保存时按同一套控制器状态(snapshot())计算输出。

技术细节:双引擎导出原理

Ente 为保证不同设备上都能成功保存编辑结果,采用了「原生导出优先,FFmpeg 兜底」的双引擎策略。该逻辑完整实现在 video_editor_page.dart:

  1. flagService.useNativeVideoEditor开启(默认走原生路径),先尝试原生导出;
  2. 原生导出抛出NativeVideoEditorException或任何异常时,记录告警日志并把进度归零,自动切换 FFmpeg 再次导出
  3. 若原生开关关闭,则直接走 FFmpeg。

(内部用户还能在界面上看到一个「Native (i)」调试开关,用于手动切换两条路径对比,见 video_editor_page.dart。)

每条导出路径自身还带有快速失败重试机制:若导出在 1 秒内失败(疑似输出路径瞬时问题),会用新的时间戳输出路径重试一次(见 video_editor_page.dart)。

原生导出(默认)

平台底层技术编码策略
iOSAVFoundation(AVMutableComposition+AVAssetExportSessionHighestQuality 预设,纯修剪时 Passthrough(免重编码)
AndroidMedia3 Transformer(Transformer+EditedMediaItem+EffectsH.264 编码,纯修剪时optimizeTrim直通
  • 速度更快、画质保留更好,支持设备硬件加速;
  • 输出 MP4(H.264),尽可能保持原始质量。

iOS 侧实现见 VideoExportEngine.swift:通过AVURLAsset读取时长、视频轨与preferredTransform,构建AVMutableComposition后交给AVAssetExportSession,并用异步任务循环上报progress(0~1)驱动进度条。

Android 侧实现见 VideoExportEngine.kt:先解析裁剪区间clipRange,构造MediaItem,将裁剪(Cropeffect)、旋转(ScaleAndRotateTransformationPresentation)组装为Effects,最终以Composition提交给 Media3Transformer;当effects.isEmpty()且仅修剪时走optimizeTrim直通路径(见 VideoExportEngine.kt)。

Dart 侧的原生能力封装位于 native_video_editor.dart:通过MethodChannel('native_video_editor')调用processVideo方法,入参为inputPath/outputPath与可选的trimStartMs/trimEndMs/rotateDegrees/cropX/cropY/cropWidth/cropHeight,通过EventChannel接收进度,返回结构包含outputPathisReEncoded标记;同时提供inspectVideo读取原生视频元数据(时长、宽高、旋转角、码率、帧率)用于初始化编辑器。

值得注意的工程细节:当没有任何编辑操作时,原生路径直接复制文件isReEncoded: false),完全不重编码(见 native_video_export_service.dart);而 FFmpeg 路径同样会依据是否存在裁剪/旋转来决定是否附加-vf滤镜链(见 export_video_service.dart)。

FFmpeg 兜底导出

  • 仅在原生导出失败时自动启用;
  • 跨平台纯软件编码,兼容性最广;
  • 输出 MP4:H.264 视频(libx264)+ AAC 音频

FFmpeg 兜底命令的完整参数序列由ExportService.createPlan构建(见 export_video_service.dart):

-y -ss <start> -i <input> -t <duration> \ -map 0:v:0 -map 0:a? [-vf <filters>] \ -c:v libx264 -preset ultrafast -pix_fmt yuv420p \ -c:a aac -map_metadata 0 -metadata:s:v:0 rotate=0 \ -movflags +faststart <outputPath>

各参数含义:

参数作用
-ss/-t修剪起点与时长(秒)
-map 0:v:0 -map 0:a?只取首路视频流,音频流可选(?表示没有则跳过)
-vf视频滤镜链(crop / rotate 组合),无编辑时不附加
-c:v libx264 -preset ultrafastH.264 软件编码,ultrafast 预设换取速度
-pix_fmt yuv420p保证最大播放器兼容性
-c:a aacAAC 音频编码
-map_metadata 0保留原文件元数据
-metadata:s:v:0 rotate=0输出中清空旋转标记(旋转已烘焙进画面)
-movflags +faststart将 moov 元数据前置,便于流式播放

FFmpeg 路径通过ffmpeg_kit_flutterexecuteWithArgumentsAsync异步执行,并校验返回码与输出文件非空(见 export_video_service.dart)。

输出格式

所有编辑后的视频统一导出为MP4文件:

  • 文件命名[原文件名]_edited_[时间戳].mp4。源码中时间戳取DateTime.now().microsecondsSinceEpoch的微秒级数值以保证唯一性,随后再经getMediaStoreCompatibleTitle处理为相册兼容名称(见 video_editor_page.dart);
  • 分辨率:尽可能保留原始分辨率(原生路径下尤其如此);
  • 画质:原生导出保持最高质量;FFmpeg 兜底采用标准 H.264/AAC 编码。

处理时间与进度

导出耗时取决于以下因素:

  • 视频时长与分辨率;
  • 应用的编辑项数量(修剪、裁剪、旋转的组合);
  • 设备处理性能;
  • 导出引擎(原生 vs FFmpeg)。

导出过程中会弹出进度对话框LinearProgressDialog,标题为「Saving edits」),进度由各引擎的onProgress回调实时刷新;原生导出失败切换 FFmpeg 时会先把进度重置为 0(见 video_editor_page.dart)。

限制与注意事项

  • 最短时长:修剪后视频必须至少 1 秒(控制器初始化时若原视频不足 1 秒,会抛出VideoMinimumDurationError并直接关闭编辑器,见 video_editor_controller.dart);
  • 仅限移动端:目前仅 iOS 与 Android 应用可用,桌面端与 Web 端不提供该编辑器;
  • 单视频编辑:一次只能编辑一个视频;
  • 破坏性工作流:无法保存/复用编辑预设——每次导出都会产出一个最终视频成品(编辑器虽内置snapshot()/restore()状态快照用于页面切换时的状态保持,但不存在可跨会话保存的编辑方案)。

源码导读

如果你想继续深入,可以从以下文件切入:

  • 编辑器主页面与导出编排:video_editor_page.dart
  • 编辑器状态控制器(修剪/裁剪/旋转的状态机):video_editor_controller.dart
  • 裁剪比例定义:crop_value.dart、裁剪页面 video_crop_page.dart
  • 原生导出封装(Dart 层):native_video_editor.dart、native_video_export_service.dart
  • FFmpeg 导出计划:export_video_service.dart
  • Android 原生引擎:VideoExportEngine.kt(另有VideoClipRangeUsTest.ktVideoModelsTest.kt单元测试)
  • iOS 原生引擎:VideoExportEngine.swift(配套 VideoTransformPlannerTests.swift)
  • 端到端验证:video_editor_test.dart(覆盖 Trim 组合、Native/FFmpeg 导出对比等场景)、video_editor_controller_test.dart 与 video_editor_contract_test.dart

上述测试用例直接验证了本文描述的众多行为,例如:修剪起点偏移与时长组合、裁剪矩形计算、旋转角度与比例翻转、导出成功/失败与回退路径等,可作为阅读源码时的参照。

【免费下载链接】ente💚 End-to-end encrypted cloud for everything.项目地址: https://gitcode.com/GitHub_Trending/en/ente

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询