flutter_ffmpeg_example.dart 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887
  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter/services.dart';
  5. import 'package:flutter_ffmpeg/flutter_ffmpeg.dart';
  6. import 'package:flutter_ffmpeg/log_level.dart';
  7. import 'package:path/path.dart';
  8. import 'package:path_provider/path_provider.dart';
  9. void main() => runApp(FlutterFFmpegTestApp());
  10. class FlutterFFmpegTestApp extends StatelessWidget {
  11. @override
  12. Widget build(BuildContext context) {
  13. return MaterialApp(
  14. theme: ThemeData(
  15. primaryColor: Color(0xFFF46842),
  16. ),
  17. home: MainPage(),
  18. );
  19. }
  20. }
  21. class MainPage extends StatefulWidget {
  22. @override
  23. FlutterFFmpegTestAppState createState() => new FlutterFFmpegTestAppState();
  24. }
  25. class DecoratedTabBar extends StatelessWidget implements PreferredSizeWidget {
  26. DecoratedTabBar({@required this.tabBar, @required this.decoration});
  27. final TabBar tabBar;
  28. final BoxDecoration decoration;
  29. @override
  30. Size get preferredSize => tabBar.preferredSize;
  31. @override
  32. Widget build(BuildContext context) {
  33. return Stack(
  34. children: [
  35. Positioned.fill(child: Container(decoration: decoration)),
  36. tabBar,
  37. ],
  38. );
  39. }
  40. }
  41. class VideoUtil {
  42. static Future<Directory> get tempDirectory async {
  43. return await getTemporaryDirectory();
  44. }
  45. static Future<File> copyFileAssets(String assetName, String localName) async {
  46. final ByteData assetByteData = await rootBundle.load(assetName);
  47. final List<int> byteList = assetByteData.buffer
  48. .asUint8List(assetByteData.offsetInBytes, assetByteData.lengthInBytes);
  49. final String fullTemporaryPath =
  50. join((await tempDirectory).path, localName);
  51. return new File(fullTemporaryPath)
  52. .writeAsBytes(byteList, mode: FileMode.writeOnly, flush: true);
  53. }
  54. static Future<String> assetPath(String assetName) async {
  55. return join((await tempDirectory).path, assetName);
  56. }
  57. static String generateEncodeVideoScript(
  58. String image1Path,
  59. String image2Path,
  60. String image3Path,
  61. String videoFilePath,
  62. String videoCodec,
  63. String customOptions) {
  64. return "-hide_banner -y -loop 1 -i '" +
  65. image1Path +
  66. "' " +
  67. "-loop 1 -i \"" +
  68. image2Path +
  69. "\" " +
  70. "-loop 1 -i \"" +
  71. image3Path +
  72. "\" " +
  73. "-filter_complex " +
  74. "\"[0:v]setpts=PTS-STARTPTS,scale=w='if(gte(iw/ih,640/427),min(iw,640),-1)':h='if(gte(iw/ih,640/427),-1,min(ih,427))',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream1out1][stream1out2];" +
  75. "[1:v]setpts=PTS-STARTPTS,scale=w='if(gte(iw/ih,640/427),min(iw,640),-1)':h='if(gte(iw/ih,640/427),-1,min(ih,427))',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream2out1][stream2out2];" +
  76. "[2:v]setpts=PTS-STARTPTS,scale=w='if(gte(iw/ih,640/427),min(iw,640),-1)':h='if(gte(iw/ih,640/427),-1,min(ih,427))',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream3out1][stream3out2];" +
  77. "[stream1out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=3,select=lte(n\\,90)[stream1overlaid];" +
  78. "[stream1out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30)[stream1ending];" +
  79. "[stream2out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=2,select=lte(n\\,60)[stream2overlaid];" +
  80. "[stream2out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30),split=2[stream2starting][stream2ending];" +
  81. "[stream3out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=2,select=lte(n\\,60)[stream3overlaid];" +
  82. "[stream3out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30)[stream3starting];" +
  83. "[stream2starting][stream1ending]blend=all_expr='if(gte(X,(W/2)*T/1)*lte(X,W-(W/2)*T/1),B,A)':shortest=1[stream2blended];" +
  84. "[stream3starting][stream2ending]blend=all_expr='if(gte(X,(W/2)*T/1)*lte(X,W-(W/2)*T/1),B,A)':shortest=1[stream3blended];" +
  85. "[stream1overlaid][stream2blended][stream2overlaid][stream3blended][stream3overlaid]concat=n=5:v=1:a=0,scale=w=640:h=424,format=yuv420p[video]\"" +
  86. " -map [video] -vsync 2 -async 1 " +
  87. customOptions +
  88. "-c:v " +
  89. videoCodec +
  90. " -r 30 " +
  91. videoFilePath;
  92. }
  93. }
  94. class FlutterFFmpegTestAppState extends State<MainPage>
  95. with TickerProviderStateMixin {
  96. static const String ASSET_1 = "1.jpg";
  97. static const String ASSET_2 = "2.jpg";
  98. static const String ASSET_3 = "3.jpg";
  99. final FlutterFFmpegConfig _flutterFFmpegConfig = new FlutterFFmpegConfig();
  100. final FlutterFFmpeg _flutterFFmpeg = new FlutterFFmpeg();
  101. final FlutterFFprobe _flutterFFprobe = new FlutterFFprobe();
  102. TextEditingController _commandController;
  103. TabController _controller;
  104. String _commandOutput;
  105. String _encodeOutput;
  106. String _currentCodec;
  107. List<DropdownMenuItem<String>> _codecDropDownMenuItems;
  108. @override
  109. void initState() {
  110. super.initState();
  111. _commandController = TextEditingController();
  112. _controller = TabController(length: 2, vsync: this);
  113. _commandOutput = "";
  114. _encodeOutput = "";
  115. _codecDropDownMenuItems = _getCodecDropDownMenuItems();
  116. _currentCodec = _codecDropDownMenuItems[0].value;
  117. startupTests();
  118. prepareAssets();
  119. }
  120. void startupTests() {
  121. getFFmpegVersion().then((version) => print("FFmpeg version: $version"));
  122. getPlatform().then((platform) => print("Platform: $platform"));
  123. getLogLevel().then(
  124. (level) => print("Old log level: " + LogLevel.levelToString(level)));
  125. setLogLevel(LogLevel.AV_LOG_INFO);
  126. getLogLevel().then(
  127. (level) => print("New log level: " + LogLevel.levelToString(level)));
  128. getPackageName().then((packageName) => print("Package name: $packageName"));
  129. getExternalLibraries().then((packageList) {
  130. packageList.forEach((value) => print("External library: $value"));
  131. });
  132. }
  133. void prepareAssets() {
  134. VideoUtil.copyFileAssets('assets/pyramid.jpg', ASSET_1)
  135. .then((path) => print('Loaded asset $path.'));
  136. VideoUtil.copyFileAssets('assets/colosseum.jpg', ASSET_2)
  137. .then((path) => print('Loaded asset $path.'));
  138. VideoUtil.copyFileAssets('assets/tajmahal.jpg', ASSET_3)
  139. .then((path) => print('Loaded asset $path.'));
  140. }
  141. void testParseArguments() {
  142. testParseSimpleCommand();
  143. testParseSingleQuotesInCommand();
  144. testParseDoubleQuotesInCommand();
  145. testParseDoubleQuotesAndEscapesInCommand();
  146. }
  147. void testRunFFmpegCommand() {
  148. getLastReturnCode().then((rc) => print("Last rc: $rc"));
  149. getLastCommandOutput().then((output) =>
  150. debugPrint("Last command output: \"$output\"", wrapWidth: 1024));
  151. print("Testing ParseArguments.");
  152. testParseArguments();
  153. registerNewFFmpegPipe().then((path) => print("New FFmpeg pipe: $path"));
  154. print("Testing FFmpeg COMMAND.");
  155. // ENABLE LOG CALLBACK ON EACH CALL
  156. _flutterFFmpegConfig.enableLogCallback(commandOutputLogCallback);
  157. _flutterFFmpegConfig.enableStatisticsCallback(statisticsCallback);
  158. // CLEAR OUTPUT ON EACH EXECUTION
  159. _commandOutput = "";
  160. // COMMENT OPTIONAL TESTS
  161. // VideoUtil.tempDirectory.then((tempDirectory) {
  162. // Map<String, String> mapNameMap = new Map();
  163. // mapNameMap["my_custom_font"] = "my custom font";
  164. // setFontDirectory(tempDirectory.path, null);
  165. // });
  166. VideoUtil.tempDirectory.then((tempDirectory) {
  167. setFontconfigConfigurationPath(tempDirectory.path);
  168. });
  169. // disableRedirection();
  170. // disableLogs();
  171. // enableLogs();
  172. executeFFmpeg(_commandController.text)
  173. .then((rc) => print("FFmpeg process exited with rc $rc"));
  174. // executeWithArguments(_commandController.text.split(" ")).then((rc) => print("FFmpeg process exited with rc $rc"));
  175. setState(() {});
  176. }
  177. void testRunFFprobeCommand() {
  178. getLastReturnCode().then((rc) => print("Last rc: $rc"));
  179. getLastCommandOutput().then((output) =>
  180. debugPrint("Last command output: \"$output\"", wrapWidth: 1024));
  181. print("Testing ParseArguments.");
  182. testParseArguments();
  183. registerNewFFmpegPipe().then((path) => print("New FFmpeg pipe: $path"));
  184. print("Testing FFprobe COMMAND.");
  185. // ENABLE LOG CALLBACK ON EACH CALL
  186. _flutterFFmpegConfig.enableLogCallback(commandOutputLogCallback);
  187. _flutterFFmpegConfig.enableStatisticsCallback(statisticsCallback);
  188. // CLEAR OUTPUT ON EACH EXECUTION
  189. _commandOutput = "";
  190. VideoUtil.tempDirectory.then((tempDirectory) {
  191. setFontconfigConfigurationPath(tempDirectory.path);
  192. });
  193. executeFFprobe(_commandController.text)
  194. .then((rc) => print("FFprobe process exited with rc $rc"));
  195. setState(() {});
  196. }
  197. void testGetMediaInformation(String mediaPath) {
  198. print("Testing Get Media Information.");
  199. // ENABLE LOG CALLBACK ON EACH CALL
  200. _flutterFFmpegConfig.enableLogCallback(commandOutputLogCallback);
  201. _flutterFFmpegConfig.enableStatisticsCallback(null);
  202. // CLEAR OUTPUT ON EACH EXECUTION
  203. _commandOutput = "";
  204. VideoUtil.assetPath(mediaPath).then((image1Path) {
  205. getMediaInformation(image1Path).then((info) {
  206. print('Media Information');
  207. print('Path: ${info['path']}');
  208. print('Format: ${info['format']}');
  209. print('Duration: ${info['duration']}');
  210. print('Start time: ${info['startTime']}');
  211. print('Bitrate: ${info['bitrate']}');
  212. if (info['streams'] != null) {
  213. final streamsInfoArray = info['streams'];
  214. if (streamsInfoArray.length > 0) {
  215. for (var streamsInfo in streamsInfoArray) {
  216. print('Stream id: ${streamsInfo['index']}');
  217. print('Stream type: ${streamsInfo['type']}');
  218. print('Stream codec: ${streamsInfo['codec']}');
  219. print('Stream full codec: ${streamsInfo['fullCodec']}');
  220. print('Stream format: ${streamsInfo['format']}');
  221. print('Stream full format: ${streamsInfo['fullFormat']}');
  222. print('Stream width: ${streamsInfo['width']}');
  223. print('Stream height: ${streamsInfo['height']}');
  224. print('Stream bitrate: ${streamsInfo['bitrate']}');
  225. print('Stream sample rate: ${streamsInfo['sampleRate']}');
  226. print('Stream sample format: ${streamsInfo['sampleFormat']}');
  227. print('Stream channel layout: ${streamsInfo['channelLayout']}');
  228. print('Stream sar: ${streamsInfo['sampleAspectRatio']}');
  229. print('Stream dar: ${streamsInfo['displayAspectRatio']}');
  230. print(
  231. 'Stream average frame rate: ${streamsInfo['averageFrameRate']}');
  232. print('Stream real frame rate: ${streamsInfo['realFrameRate']}');
  233. print('Stream time base: ${streamsInfo['timeBase']}');
  234. print('Stream codec time base: ${streamsInfo['codecTimeBase']}');
  235. final metadataMap = streamsInfo['metadata'];
  236. if (metadataMap != null) {
  237. print('Stream metadata encoder: ${metadataMap['encoder']}');
  238. print('Stream metadata rotate: ${metadataMap['rotate']}');
  239. print(
  240. 'Stream metadata creation time: ${metadataMap['creation_time']}');
  241. print(
  242. 'Stream metadata handler name: ${metadataMap['handler_name']}');
  243. }
  244. final sideDataMap = streamsInfo['sidedata'];
  245. if (sideDataMap != null) {
  246. print(
  247. 'Stream side data displaymatrix: ${sideDataMap['displaymatrix']}');
  248. }
  249. }
  250. }
  251. }
  252. });
  253. });
  254. setState(() {});
  255. }
  256. void testEncodeVideo() {
  257. print("Testing VIDEO.");
  258. // ENABLE LOG CALLBACK ON EACH CALL
  259. _flutterFFmpegConfig.enableLogCallback(encodeOutputLogCallback);
  260. _flutterFFmpegConfig.enableStatisticsCallback(statisticsCallback);
  261. // CLEAR OUTPUT ON EACH EXECUTION
  262. _encodeOutput = "";
  263. disableStatistics();
  264. enableStatistics();
  265. VideoUtil.assetPath(ASSET_1).then((image1Path) {
  266. VideoUtil.assetPath(ASSET_2).then((image2Path) {
  267. VideoUtil.assetPath(ASSET_3).then((image3Path) {
  268. final String videoPath = getVideoPath();
  269. final String customOptions = getCustomEncodingOptions();
  270. final String ffmpegCodec = getFFmpegCodecName();
  271. VideoUtil.assetPath(videoPath).then((fullVideoPath) {
  272. executeFFmpeg(VideoUtil.generateEncodeVideoScript(image1Path, image2Path,
  273. image3Path, fullVideoPath, ffmpegCodec, customOptions))
  274. .then((rc) {
  275. if (rc == 0) {
  276. testGetMediaInformation(fullVideoPath);
  277. }
  278. });
  279. // resetStatistics();
  280. getLastReceivedStatistics().then((lastStatistics) {
  281. if (lastStatistics == null) {
  282. print('No last statistics');
  283. } else {
  284. print('Last statistics');
  285. int time = lastStatistics['time'];
  286. int size = lastStatistics['size'];
  287. double bitrate = _doublePrecision(lastStatistics['bitrate'], 2);
  288. double speed = _doublePrecision(lastStatistics['speed'], 2);
  289. int videoFrameNumber = lastStatistics['videoFrameNumber'];
  290. double videoQuality =
  291. _doublePrecision(lastStatistics['videoQuality'], 2);
  292. double videoFps =
  293. _doublePrecision(lastStatistics['videoFps'], 2);
  294. statisticsCallback(time, size, bitrate, speed, videoFrameNumber,
  295. videoQuality, videoFps);
  296. }
  297. });
  298. });
  299. });
  300. });
  301. });
  302. setState(() {});
  303. }
  304. void commandOutputLogCallback(int level, String message) {
  305. _commandOutput += message;
  306. setState(() {});
  307. }
  308. void encodeOutputLogCallback(int level, String message) {
  309. _encodeOutput += message;
  310. setState(() {});
  311. }
  312. void statisticsCallback(int time, int size, double bitrate, double speed,
  313. int videoFrameNumber, double videoQuality, double videoFps) {
  314. print(
  315. "Statistics: time: $time, size: $size, bitrate: $bitrate, speed: $speed, videoFrameNumber: $videoFrameNumber, videoQuality: $videoQuality, videoFps: $videoFps");
  316. }
  317. Future<String> getFFmpegVersion() async {
  318. return await _flutterFFmpegConfig.getFFmpegVersion();
  319. }
  320. Future<String> getPlatform() async {
  321. return await _flutterFFmpegConfig.getPlatform();
  322. }
  323. Future<int> executeFFmpegWithArguments(List arguments) async {
  324. return await _flutterFFmpeg.executeWithArguments(arguments);
  325. }
  326. Future<int> executeFFmpeg(String command) async {
  327. return await _flutterFFmpeg.execute(command);
  328. }
  329. Future<int> executeFFprobeWithArguments(List arguments) async {
  330. return await _flutterFFprobe.executeWithArguments(arguments);
  331. }
  332. Future<int> executeFFprobe(String command) async {
  333. return await _flutterFFprobe.execute(command);
  334. }
  335. Future<void> cancel() async {
  336. return await _flutterFFmpeg.cancel();
  337. }
  338. Future<void> disableRedirection() async {
  339. return await _flutterFFmpegConfig.disableRedirection();
  340. }
  341. Future<int> getLogLevel() async {
  342. return await _flutterFFmpegConfig.getLogLevel();
  343. }
  344. Future<void> setLogLevel(int logLevel) async {
  345. return await _flutterFFmpegConfig.setLogLevel(logLevel);
  346. }
  347. Future<void> enableLogs() async {
  348. return await _flutterFFmpegConfig.enableLogs();
  349. }
  350. Future<void> disableLogs() async {
  351. return await _flutterFFmpegConfig.disableLogs();
  352. }
  353. Future<void> enableStatistics() async {
  354. return await _flutterFFmpegConfig.enableStatistics();
  355. }
  356. Future<void> disableStatistics() async {
  357. return await _flutterFFmpegConfig.disableStatistics();
  358. }
  359. Future<Map<dynamic, dynamic>> getLastReceivedStatistics() async {
  360. return await _flutterFFmpegConfig.getLastReceivedStatistics();
  361. }
  362. Future<void> resetStatistics() async {
  363. return await _flutterFFmpegConfig.resetStatistics();
  364. }
  365. Future<void> setFontconfigConfigurationPath(String path) async {
  366. return await _flutterFFmpegConfig.setFontconfigConfigurationPath(path);
  367. }
  368. Future<void> setFontDirectory(
  369. String fontDirectory, Map<String, String> fontNameMap) async {
  370. return await _flutterFFmpegConfig.setFontDirectory(fontDirectory, fontNameMap);
  371. }
  372. Future<String> getPackageName() async {
  373. return await _flutterFFmpegConfig.getPackageName();
  374. }
  375. Future<List<dynamic>> getExternalLibraries() async {
  376. return await _flutterFFmpegConfig.getExternalLibraries();
  377. }
  378. Future<int> getLastReturnCode() async {
  379. return await _flutterFFmpegConfig.getLastReturnCode();
  380. }
  381. Future<String> getLastCommandOutput() async {
  382. return await _flutterFFmpegConfig.getLastCommandOutput();
  383. }
  384. Future<Map<dynamic, dynamic>> getMediaInformation(String path) async {
  385. return await _flutterFFprobe.getMediaInformation(path);
  386. }
  387. Future<String> registerNewFFmpegPipe() async {
  388. return await _flutterFFmpegConfig.registerNewFFmpegPipe();
  389. }
  390. void _changedCodec(String selectedCodec) {
  391. setState(() {
  392. _currentCodec = selectedCodec;
  393. });
  394. }
  395. String getFFmpegCodecName() {
  396. String ffmpegCodec = _currentCodec;
  397. // VIDEO CODEC MENU HAS BASIC NAMES, FFMPEG NEEDS LONGER LIBRARY NAMES.
  398. if (ffmpegCodec == "x264") {
  399. ffmpegCodec = "libx264";
  400. } else if (ffmpegCodec == "x265") {
  401. ffmpegCodec = "libx265";
  402. } else if (ffmpegCodec == "xvid") {
  403. ffmpegCodec = "libxvid";
  404. } else if (ffmpegCodec == "vp8") {
  405. ffmpegCodec = "libvpx";
  406. } else if (ffmpegCodec == "vp9") {
  407. ffmpegCodec = "libvpx-vp9";
  408. }
  409. return ffmpegCodec;
  410. }
  411. String getVideoPath() {
  412. String ffmpegCodec = _currentCodec;
  413. String videoPath;
  414. if ((ffmpegCodec == "vp8") || (ffmpegCodec == "vp9")) {
  415. videoPath = "video.webm";
  416. } else {
  417. // mpeg4, x264, x265, xvid
  418. videoPath = "video.mp4";
  419. }
  420. return videoPath;
  421. }
  422. String getCustomEncodingOptions() {
  423. String videoCodec = _currentCodec;
  424. if (videoCodec == "x265") {
  425. return "-crf 28 -preset fast ";
  426. } else if (videoCodec == "vp8") {
  427. return "-b:v 1M -crf 10 ";
  428. } else if (videoCodec == "vp9") {
  429. return "-b:v 2M ";
  430. } else {
  431. return "";
  432. }
  433. }
  434. List<DropdownMenuItem<String>> _getCodecDropDownMenuItems() {
  435. List<DropdownMenuItem<String>> items = new List();
  436. items.add(new DropdownMenuItem(value: "mpeg4", child: new Text("mpeg4")));
  437. items.add(new DropdownMenuItem(value: "x264", child: new Text("x264")));
  438. items.add(new DropdownMenuItem(value: "x265", child: new Text("x265")));
  439. items.add(new DropdownMenuItem(value: "xvid", child: new Text("xvid")));
  440. items.add(new DropdownMenuItem(value: "vp8", child: new Text("vp8")));
  441. items.add(new DropdownMenuItem(value: "vp9", child: new Text("vp9")));
  442. return items;
  443. }
  444. double _doublePrecision(double value, int precision) {
  445. if (value == null) {
  446. return 0;
  447. } else {
  448. return num.parse(value.toStringAsFixed(precision));
  449. }
  450. }
  451. @override
  452. Widget build(BuildContext context) {
  453. return Scaffold(
  454. appBar: AppBar(
  455. title: Text('FlutterFFmpeg Test'),
  456. centerTitle: true,
  457. ),
  458. bottomNavigationBar: Material(
  459. child: DecoratedTabBar(
  460. tabBar: TabBar(
  461. tabs: <Tab>[
  462. Tab(text: "COMMAND"),
  463. Tab(
  464. text: "VIDEO",
  465. )
  466. ],
  467. controller: _controller,
  468. labelColor: Color(0xFF1e90ff),
  469. unselectedLabelColor: Color(0xFF808080),
  470. ),
  471. decoration: BoxDecoration(
  472. border: Border(
  473. top: BorderSide(
  474. color: Color(0xFF808080),
  475. width: 1.0,
  476. ),
  477. bottom: BorderSide(
  478. width: 0.0,
  479. ),
  480. ),
  481. ),
  482. ),
  483. ),
  484. body: TabBarView(
  485. children: <Widget>[
  486. Column(
  487. crossAxisAlignment: CrossAxisAlignment.center,
  488. children: <Widget>[
  489. Container(
  490. padding: const EdgeInsets.fromLTRB(20, 40, 20, 40),
  491. child: TextField(
  492. controller: _commandController,
  493. decoration: InputDecoration(
  494. border: const OutlineInputBorder(
  495. borderSide:
  496. const BorderSide(color: Color(0xFF3498DB)),
  497. borderRadius: const BorderRadius.all(
  498. const Radius.circular(5),
  499. ),
  500. ),
  501. focusedBorder: const OutlineInputBorder(
  502. borderSide:
  503. const BorderSide(color: Color(0xFF3498DB)),
  504. borderRadius: const BorderRadius.all(
  505. const Radius.circular(5),
  506. ),
  507. ),
  508. enabledBorder: const OutlineInputBorder(
  509. borderSide:
  510. const BorderSide(color: Color(0xFF3498DB)),
  511. borderRadius: const BorderRadius.all(
  512. const Radius.circular(5),
  513. ),
  514. ),
  515. contentPadding: EdgeInsets.fromLTRB(8, 12, 8, 12),
  516. hintStyle: new TextStyle(
  517. fontSize: 14, color: Colors.grey[400]),
  518. hintText: "Enter command"),
  519. style: new TextStyle(fontSize: 14, color: Colors.black),
  520. ),
  521. ),
  522. Container(
  523. padding: const EdgeInsets.only(bottom: 20),
  524. child: new InkWell(
  525. onTap: () => testRunFFmpegCommand(),
  526. child: new Container(
  527. width: 130,
  528. height: 38,
  529. decoration: new BoxDecoration(
  530. color: Color(0xFF2ECC71),
  531. borderRadius: new BorderRadius.circular(5),
  532. ),
  533. child: new Center(
  534. child: new Text(
  535. 'RUN FFMPEG',
  536. style: new TextStyle(
  537. fontSize: 14.0,
  538. fontWeight: FontWeight.bold,
  539. color: Colors.white),
  540. ),
  541. ),
  542. ),
  543. ),
  544. ),
  545. Container(
  546. padding: const EdgeInsets.only(bottom: 20),
  547. child: new InkWell(
  548. onTap: () => testRunFFprobeCommand(),
  549. child: new Container(
  550. width: 130,
  551. height: 38,
  552. decoration: new BoxDecoration(
  553. color: Color(0xFF2ECC71),
  554. borderRadius: new BorderRadius.circular(5),
  555. ),
  556. child: new Center(
  557. child: new Text(
  558. 'RUN FFPROBE',
  559. style: new TextStyle(
  560. fontSize: 14.0,
  561. fontWeight: FontWeight.bold,
  562. color: Colors.white),
  563. ),
  564. ),
  565. ),
  566. ),
  567. ),
  568. Expanded(
  569. child: Container(
  570. alignment: Alignment(-1.0, -1.0),
  571. margin: EdgeInsets.all(20.0),
  572. padding: EdgeInsets.all(4.0),
  573. decoration: new BoxDecoration(
  574. borderRadius:
  575. BorderRadius.all(new Radius.circular(5)),
  576. color: Color(0xFFF1C40F)),
  577. child: SingleChildScrollView(
  578. reverse: true, child: Text(_commandOutput))),
  579. )
  580. ],
  581. ),
  582. Column(
  583. crossAxisAlignment: CrossAxisAlignment.center,
  584. children: <Widget>[
  585. Container(
  586. padding: const EdgeInsets.fromLTRB(20, 40, 20, 40),
  587. child: Container(
  588. width: 200,
  589. alignment: Alignment(0.0, 0.0),
  590. decoration: BoxDecoration(
  591. color: Color.fromRGBO(155, 89, 182, 1.0),
  592. borderRadius: BorderRadius.circular(5)),
  593. child: DropdownButtonHideUnderline(
  594. child: DropdownButton(
  595. style: new TextStyle(
  596. fontSize: 14,
  597. color: Colors.black,
  598. ),
  599. value: _currentCodec,
  600. items: _codecDropDownMenuItems,
  601. onChanged: _changedCodec,
  602. iconSize: 0,
  603. isExpanded: false,
  604. )),
  605. )),
  606. Container(
  607. padding: const EdgeInsets.only(bottom: 20),
  608. child: new InkWell(
  609. onTap: () => testEncodeVideo(),
  610. child: new Container(
  611. width: 100,
  612. height: 38,
  613. decoration: new BoxDecoration(
  614. color: Color(0xFF2ECC71),
  615. borderRadius: new BorderRadius.circular(5),
  616. ),
  617. child: new Center(
  618. child: new Text(
  619. 'ENCODE',
  620. style: new TextStyle(
  621. fontSize: 14.0,
  622. fontWeight: FontWeight.bold,
  623. color: Colors.white),
  624. ),
  625. ),
  626. ),
  627. ),
  628. ),
  629. Expanded(
  630. child: Container(
  631. alignment: Alignment(-1.0, -1.0),
  632. margin: EdgeInsets.all(20.0),
  633. padding: EdgeInsets.all(4.0),
  634. decoration: new BoxDecoration(
  635. borderRadius:
  636. BorderRadius.all(new Radius.circular(5)),
  637. color: Color(0xFFF1C40F)),
  638. child: SingleChildScrollView(
  639. reverse: true, child: Text(_encodeOutput))),
  640. )
  641. ],
  642. ),
  643. ],
  644. controller: _controller,
  645. ));
  646. }
  647. @override
  648. void dispose() {
  649. super.dispose();
  650. _commandController.dispose();
  651. }
  652. void testParseSimpleCommand() {
  653. var argumentArray = FlutterFFmpeg.parseArguments(
  654. "-hide_banner -loop 1 -i file.jpg -filter_complex [0:v]setpts=PTS-STARTPTS[video] -map [video] -vsync 2 -async 1 video.mp4");
  655. assert(argumentArray != null);
  656. assert(argumentArray.length == 14);
  657. assert("-hide_banner" == argumentArray[0]);
  658. assert("-loop" == argumentArray[1]);
  659. assert("1" == argumentArray[2]);
  660. assert("-i" == argumentArray[3]);
  661. assert("file.jpg" == argumentArray[4]);
  662. assert("-filter_complex" == argumentArray[5]);
  663. assert("[0:v]setpts=PTS-STARTPTS[video]" == argumentArray[6]);
  664. assert("-map" == argumentArray[7]);
  665. assert("[video]" == argumentArray[8]);
  666. assert("-vsync" == argumentArray[9]);
  667. assert("2" == argumentArray[10]);
  668. assert("-async" == argumentArray[11]);
  669. assert("1" == argumentArray[12]);
  670. assert("video.mp4" == argumentArray[13]);
  671. }
  672. void testParseSingleQuotesInCommand() {
  673. var argumentArray = FlutterFFmpeg.parseArguments(
  674. "-loop 1 'file one.jpg' -filter_complex '[0:v]setpts=PTS-STARTPTS[video]' -map [video] video.mp4 ");
  675. assert(argumentArray != null);
  676. assert(argumentArray.length == 8);
  677. assert("-loop" == argumentArray[0]);
  678. assert("1" == argumentArray[1]);
  679. assert("file one.jpg" == argumentArray[2]);
  680. assert("-filter_complex" == argumentArray[3]);
  681. assert("[0:v]setpts=PTS-STARTPTS[video]" == argumentArray[4]);
  682. assert("-map" == argumentArray[5]);
  683. assert("[video]" == argumentArray[6]);
  684. assert("video.mp4" == argumentArray[7]);
  685. }
  686. void testParseDoubleQuotesInCommand() {
  687. var argumentArray = FlutterFFmpeg.parseArguments(
  688. "-loop 1 \"file one.jpg\" -filter_complex \"[0:v]setpts=PTS-STARTPTS[video]\" -map [video] video.mp4 ");
  689. assert(argumentArray != null);
  690. assert(argumentArray.length == 8);
  691. assert("-loop" == argumentArray[0]);
  692. assert("1" == argumentArray[1]);
  693. assert("file one.jpg" == argumentArray[2]);
  694. assert("-filter_complex" == argumentArray[3]);
  695. assert("[0:v]setpts=PTS-STARTPTS[video]" == argumentArray[4]);
  696. assert("-map" == argumentArray[5]);
  697. assert("[video]" == argumentArray[6]);
  698. assert("video.mp4" == argumentArray[7]);
  699. argumentArray = FlutterFFmpeg.parseArguments(
  700. " -i file:///tmp/input.mp4 -vcodec libx264 -vf \"scale=1024:1024,pad=width=1024:height=1024:x=0:y=0:color=black\" -acodec copy -q:v 0 -q:a 0 video.mp4");
  701. assert(argumentArray != null);
  702. assert(argumentArray.length == 13);
  703. assert("-i" == argumentArray[0]);
  704. assert("file:///tmp/input.mp4" == argumentArray[1]);
  705. assert("-vcodec" == argumentArray[2]);
  706. assert("libx264" == argumentArray[3]);
  707. assert("-vf" == argumentArray[4]);
  708. assert("scale=1024:1024,pad=width=1024:height=1024:x=0:y=0:color=black" ==
  709. argumentArray[5]);
  710. assert("-acodec" == argumentArray[6]);
  711. assert("copy" == argumentArray[7]);
  712. assert("-q:v" == argumentArray[8]);
  713. assert("0" == argumentArray[9]);
  714. assert("-q:a" == argumentArray[10]);
  715. assert("0" == argumentArray[11]);
  716. assert("video.mp4" == argumentArray[12]);
  717. }
  718. void testParseDoubleQuotesAndEscapesInCommand() {
  719. var argumentArray = FlutterFFmpeg.parseArguments(
  720. " -i file:///tmp/input.mp4 -vf \"subtitles=file:///tmp/subtitles.srt:force_style=\'FontSize=16,PrimaryColour=&HFFFFFF&\'\" -vcodec libx264 -acodec copy -q:v 0 -q:a 0 video.mp4");
  721. assert(argumentArray != null);
  722. assert(argumentArray.length == 13);
  723. assert("-i" == argumentArray[0]);
  724. assert("file:///tmp/input.mp4" == argumentArray[1]);
  725. assert("-vf" == argumentArray[2]);
  726. assert(
  727. "subtitles=file:///tmp/subtitles.srt:force_style='FontSize=16,PrimaryColour=&HFFFFFF&'" ==
  728. argumentArray[3]);
  729. assert("-vcodec" == argumentArray[4]);
  730. assert("libx264" == argumentArray[5]);
  731. assert("-acodec" == argumentArray[6]);
  732. assert("copy" == argumentArray[7]);
  733. assert("-q:v" == argumentArray[8]);
  734. assert("0" == argumentArray[9]);
  735. assert("-q:a" == argumentArray[10]);
  736. assert("0" == argumentArray[11]);
  737. assert("video.mp4" == argumentArray[12]);
  738. argumentArray = FlutterFFmpeg.parseArguments(
  739. " -i file:///tmp/input.mp4 -vf \"subtitles=file:///tmp/subtitles.srt:force_style=\\\"FontSize=16,PrimaryColour=&HFFFFFF&\\\"\" -vcodec libx264 -acodec copy -q:v 0 -q:a 0 video.mp4");
  740. assert(argumentArray != null);
  741. assert(argumentArray.length == 13);
  742. assert("-i" == argumentArray[0]);
  743. assert("file:///tmp/input.mp4" == argumentArray[1]);
  744. assert("-vf" == argumentArray[2]);
  745. assert(
  746. "subtitles=file:///tmp/subtitles.srt:force_style=\\\"FontSize=16,PrimaryColour=&HFFFFFF&\\\"" ==
  747. argumentArray[3]);
  748. assert("-vcodec" == argumentArray[4]);
  749. assert("libx264" == argumentArray[5]);
  750. assert("-acodec" == argumentArray[6]);
  751. assert("copy" == argumentArray[7]);
  752. assert("-q:v" == argumentArray[8]);
  753. assert("0" == argumentArray[9]);
  754. assert("-q:a" == argumentArray[10]);
  755. assert("0" == argumentArray[11]);
  756. assert("video.mp4" == argumentArray[12]);
  757. }
  758. }