flutter_ffmpeg_example.dart 28 KB

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