Flutter视频字幕编辑插件video_subtitle_editor的使用
Flutter视频字幕编辑插件video_subtitle_editor的使用
简介
video_subtitle_editor
是一个用于 Flutter 的视频字幕编辑库,支持字幕生成、字幕编辑、字幕导出以及相关的用户界面组件。
功能 | Android 支持 | iOS 支持 |
---|---|---|
支持 | SDK 16+ | 11.0+ |
安装
在您的 Flutter 项目中添加此库作为依赖项,可以按照以下步骤操作:
-
在终端运行命令:
flutter pub add video_subtitle_editor
或者手动将以下内容添加到
pubspec.yaml
文件中:dependencies: video_subtitle_editor: ^1.0.0
-
在代码中导入包:
import 'package:video_subtitle_editor/video_subtitle_editor.dart';
截图
字幕滑块 | 编辑文本 |
---|---|
![]() |
使用方法
1. 初始化字幕控制器
可以通过文件或资源初始化字幕控制器。
late final VideoSubtitleController _controller = VideoSubtitleController.file(
widget.videoFile,
);
[@override](/user/override)
void initState() {
super.initState();
var subtitlePath = "assets/test.srt";
var controller = SubtitleController(
provider: AssetSubtitle(subtitlePath),
);
_controller
.initializeVideo()
.then((_) => setState(() {}))
.catchError((error) {});
_controller.initialSubtitles(controller);
_controller.addListener(() {
setState(() {});
});
}
[@override](/user/override)
void dispose() {
_controller.dispose();
super.dispose();
}
2. 添加视频查看器到你的 Widget 树中
VideoViewer(
controller: controller,
child: SubtitleTextView(
controller: controller,
),
);
3. 添加字幕查看器
SubtitleSlider(
height: 100,
controller: _controller,
),
示例代码
以下是一个完整的示例代码,展示如何使用 video_subtitle_editor
插件进行视频字幕编辑。
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:video_player/video_player.dart';
import 'package:video_subtitle_editor/video_subtitle_editor.dart';
import 'widgets/export_result.dart';
void main() => runApp(
MaterialApp(
title: 'Flutter Video Editor Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.grey,
brightness: Brightness.dark,
tabBarTheme: const TabBarTheme(
indicator: UnderlineTabIndicator(
borderSide: BorderSide(color: Colors.white),
),
),
dividerColor: Colors.white,
),
home: const VideoEditorExample(),
),
);
class VideoEditorExample extends StatefulWidget {
const VideoEditorExample({super.key});
[@override](/user/override)
State<VideoEditorExample> createState() => _VideoEditorExampleState();
}
class _VideoEditorExampleState extends State<VideoEditorExample> {
final ImagePicker _picker = ImagePicker();
void _pickVideo(isUseDemo) async {
if (!isUseDemo) {
final XFile? file = await _picker.pickVideo(source: ImageSource.gallery);
if (mounted && file != null) {
Navigator.push(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) =>
VideoEditor(sourceType: DataSourceType.file, filePath: file.path),
),
);
}
} else {
Navigator.push(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) =>
VideoEditor(sourceType: DataSourceType.asset, filePath: "assets/test.mp4"),
),
);
}
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Video Picker")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("Click on the button to select video"),
ElevatedButton(
onPressed: () {
_pickVideo(false);
},
child: const Text("Pick Video From Gallery"),
),
ElevatedButton(
onPressed: () {
_pickVideo(true);
},
child: const Text("Add demo Video"),
),
],
),
),
);
}
}
//-------------------//
// VIDEO EDITOR SCREEN //
//-------------------//
class VideoEditor extends StatefulWidget {
const VideoEditor({super.key, required this.sourceType, required this.filePath});
final String filePath;
final DataSourceType sourceType;
[@override](/user/override)
State<VideoEditor> createState() => _VideoEditorState();
}
class _VideoEditorState extends State<VideoEditor> {
final _exportingProgress = ValueNotifier<double>(0.0);
final _isExporting = ValueNotifier<bool>(false);
late VideoSubtitleController _controller;
[@override](/user/override)
void initState() {
super.initState();
if (widget.sourceType == DataSourceType.file) {
_controller = VideoSubtitleController.file(widget.filePath);
} else if (widget.sourceType == DataSourceType.asset) {
_controller = VideoSubtitleController.asset(widget.filePath);
}
var subtitlePath = "assets/test.srt";
var controller = SubtitleController(
provider: AssetSubtitle(subtitlePath),
);
_controller
.initializeVideo()
.then((_) => setState(() {}))
.catchError((error) {});
_controller.initialSubtitles(controller);
_controller.addListener(() {
setState(() {});
});
}
[@override](/user/override)
void dispose() async {
_exportingProgress.dispose();
_isExporting.dispose();
_controller.dispose();
super.dispose();
}
void _showErrorSnackBar(String message) => ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
duration: const Duration(seconds: 10),
),
);
double getFFmpegProgress(double time) {
final double progressValue =
time / _controller.videoPosition.inMilliseconds;
return progressValue.clamp(0.0, 1.0);
}
void _exportVideo() async {
_exportingProgress.value = 0;
_isExporting.value = true;
// 如何基于 _controller.subtitles 生成字幕文件
String content = _controller.generateSubtitleContent();
var subtitlePath = await ExportService.createTempSubtitleFile(content);
var videoOutputPath = await ExportService.generateOutputPath();
await ExportService.exportVideoWithSubtitles(
videoPath: widget.filePath,
subtitlePath: subtitlePath,
outputPath: videoOutputPath,
onProgress: (stats) {
_exportingProgress.value = getFFmpegProgress(stats.getTime());
},
onError: (e, s) {
_isExporting.value = false;
if (!mounted) return;
print("export Error on export video :( $s");
_showErrorSnackBar("Error on export video :( $e");
},
onCompleted: (file) {
_isExporting.value = false;
if (!mounted) return;
showDialog(
context: context,
builder: (_) => VideoResultPopup(video: file),
);
},
);
}
[@override](/user/override)
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
_controller.dismissHighlightedSubtitle();
},
child: Scaffold(
backgroundColor: Colors.black,
body: _controller.initialized
? SafeArea(
child: Stack(
children: [
Column(
children: [
_topNavBar(),
Expanded(
child: buildVideoView(_controller),
),
Text(
"${formatter(_controller.videoPosition)}/${formatter(_controller.videoDuration)}",
style: const TextStyle(
color: Colors.white, fontSize: 16),
),
Container(
margin: const EdgeInsets.only(top: 10, bottom: 50),
child: SubtitleSlider(
height: 100,
controller: _controller,
),
),
ValueListenableBuilder(
valueListenable: _isExporting,
builder: (_, bool export, Widget? child) =>
AnimatedSize(
duration: kThemeAnimationDuration,
child: export ? child : null,
),
child: AlertDialog(
title: ValueListenableBuilder(
valueListenable: _exportingProgress,
builder: (_, double value, __) => Text(
"Exporting video ${(value * 100).ceil()}%",
style: const TextStyle(fontSize: 12),
),
),
),
)
],
),
],
),
)
: const Center(child: CircularProgressIndicator()),
),
);
}
Widget _topNavBar() {
return SafeArea(
child: SizedBox(
height: 100,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: IconButton(
onPressed: () => Navigator.of(context).pop(),
icon: const Icon(Icons.exit_to_app),
tooltip: 'Leave editor',
),
),
const VerticalDivider(endIndent: 22, indent: 22),
Expanded(
child: PopupMenuButton(
tooltip: 'Open export menu',
icon: const Icon(Icons.save),
itemBuilder: (context) => [
PopupMenuItem(
onTap: _exportVideo,
child: const Text('Export video'),
),
],
),
),
],
),
),
);
}
/// 返回带有编辑视图的 [VideoViewer]
/// 在视频区域外绘制矩形框
Widget buildVideoView(VideoSubtitleController controller) {
return VideoViewer(
controller: controller,
child: SubtitleTextView(
controller: controller,
),
);
}
String formatter(Duration duration) =>
[
duration.inMinutes.remainder(60).toString().padLeft(2, '0'),
duration.inSeconds.remainder(60).toString().padLeft(2, '0')
].join(":");
}
更多关于Flutter视频字幕编辑插件video_subtitle_editor的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
video_subtitle_editor
是一个用于在 Flutter 应用中编辑视频字幕的插件。它允许用户在视频上添加、编辑和同步字幕。以下是如何使用 video_subtitle_editor
插件的基本步骤:
1. 添加依赖
首先,你需要在 pubspec.yaml
文件中添加 video_subtitle_editor
插件的依赖:
dependencies:
flutter:
sdk: flutter
video_subtitle_editor: ^latest_version
然后运行 flutter pub get
来获取依赖。
2. 导入插件
在你的 Dart 文件中导入 video_subtitle_editor
插件:
import 'package:video_subtitle_editor/video_subtitle_editor.dart';
3. 使用 VideoSubtitleEditor
你可以使用 VideoSubtitleEditor
小部件来加载视频并编辑字幕。以下是一个简单的示例:
class VideoEditorScreen extends StatefulWidget {
@override
_VideoEditorScreenState createState() => _VideoEditorScreenState();
}
class _VideoEditorScreenState extends State<VideoEditorScreen> {
final VideoSubtitleEditorController _controller = VideoSubtitleEditorController();
@override
void initState() {
super.initState();
_controller.loadVideo('path_to_your_video.mp4');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Video Subtitle Editor'),
),
body: Column(
children: [
Expanded(
child: VideoSubtitleEditor(
controller: _controller,
),
),
ElevatedButton(
onPressed: () {
// 添加字幕
_controller.addSubtitle(
Subtitle(
start: Duration(seconds: 0),
end: Duration(seconds: 5),
text: 'Hello, World!',
),
);
},
child: Text('Add Subtitle'),
),
ElevatedButton(
onPressed: () async {
// 保存字幕
final subtitleFile = await _controller.saveSubtitles();
print('Subtitles saved to: ${subtitleFile.path}');
},
child: Text('Save Subtitles'),
),
],
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
4. 加载视频
使用 _controller.loadVideo('path_to_your_video.mp4')
来加载视频文件。你可以从本地文件系统或网络加载视频。
5. 添加字幕
使用 _controller.addSubtitle(Subtitle(...))
来添加字幕。Subtitle
类包含字幕的开始时间、结束时间和文本内容。
6. 保存字幕
使用 _controller.saveSubtitles()
来保存字幕。该方法返回一个 File
对象,你可以将其保存到本地文件系统或上传到服务器。
7. 其他功能
video_subtitle_editor
插件还提供了其他功能,如删除字幕、编辑字幕、调整字幕时间等。你可以通过 VideoSubtitleEditorController
来访问这些功能。
8. 处理字幕文件
你可以使用 SubtitleFile
类来处理字幕文件。例如,你可以加载 .srt
或 .vtt
文件,并将其转换为 Subtitle
对象列表。
final subtitleFile = SubtitleFile.fromFile(File('path_to_subtitle.srt'));
final subtitles = subtitleFile.subtitles;
9. 自定义 UI
你可以通过自定义 VideoSubtitleEditor
的 UI 来满足你的需求。例如,你可以添加一个时间轴控件来显示视频的进度,并允许用户通过拖动来调整字幕的时间。
10. 处理错误
在使用 video_subtitle_editor
插件时,可能会遇到各种错误,如视频加载失败、字幕格式错误等。你可以通过捕获异常来处理这些错误,并向用户显示友好的错误信息。
try {
await _controller.loadVideo('path_to_your_video.mp4');
} catch (e) {
print('Failed to load video: $e');
}