flutter_ffmpeg_test_app_state.dart 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. import 'dart:async';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter_ffmpeg/flutter_ffmpeg.dart';
  4. import 'package:flutter_ffmpeg/log_level.dart';
  5. import 'package:flutter_ffmpeg_example/flutter_ffmpeg_test_app.dart';
  6. import 'package:flutter_ffmpeg_example/video_util.dart';
  7. class FlutterFFmpegTestAppState extends State<MainPage> with TickerProviderStateMixin {
  8. static const String ASSET_1 = "1.jpg";
  9. static const String ASSET_2 = "2.jpg";
  10. static const String ASSET_3 = "3.jpg";
  11. final FlutterFFmpeg _flutterFFmpeg = new FlutterFFmpeg();
  12. TextEditingController _commandController;
  13. TabController _controller;
  14. String _commandOutput;
  15. String _encodeOutput;
  16. String _currentCodec;
  17. List<DropdownMenuItem<String>> _codecDropDownMenuItems;
  18. @override
  19. void initState() {
  20. super.initState();
  21. _commandController = TextEditingController();
  22. _controller = TabController(length: 2, vsync: this);
  23. _commandOutput = "";
  24. _encodeOutput = "";
  25. _codecDropDownMenuItems = _getCodecDropDownMenuItems();
  26. _currentCodec = _codecDropDownMenuItems[0].value;
  27. startupTests();
  28. prepareAssets();
  29. }
  30. void startupTests() {
  31. getFFmpegVersion().then((version) => print("FFmpeg version: $version"));
  32. getPlatform().then((platform) => print("Platform: $platform"));
  33. getLogLevel().then((level) => print("Old log level: " + LogLevel.levelToString(level)));
  34. setLogLevel(LogLevel.AV_LOG_INFO);
  35. getLogLevel().then((level) => print("New log level: " + LogLevel.levelToString(level)));
  36. getPackageName().then((packageName) => print("Package name: $packageName"));
  37. getExternalLibraries().then((packageList) {
  38. packageList.forEach((value) => print("External library: $value"));
  39. });
  40. }
  41. void prepareAssets() {
  42. VideoUtil.copyFileAssets('assets/pyramid.jpg', ASSET_1).then((path) => print('Loaded asset $path.'));
  43. VideoUtil.copyFileAssets('assets/colosseum.jpg', ASSET_2).then((path) => print('Loaded asset $path.'));
  44. VideoUtil.copyFileAssets('assets/tajmahal.jpg', ASSET_3).then((path) => print('Loaded asset $path.'));
  45. }
  46. void testRunCommand() {
  47. getLastReturnCode().then((rc) => print("Last rc: $rc"));
  48. getLastCommandOutput().then((output) => print("Last command output: $output"));
  49. print("Testing COMMAND.");
  50. // ENABLE LOG CALLBACK ON EACH CALL
  51. enableLogCallback(commandOutputLogCallback);
  52. enableStatisticsCallback(statisticsCallback);
  53. // CLEAR OUTPUT ON EACH EXECUTION
  54. _commandOutput = "";
  55. // COMMENT OPTIONAL TESTS
  56. // VideoUtil.tempDirectory.then((tempDirectory) {
  57. // Map<String, String> mapNameMap = new Map();
  58. // mapNameMap["my_custom_font"] = "my custom font";
  59. // setFontDirectory(tempDirectory.path, null);
  60. // });
  61. VideoUtil.tempDirectory.then((tempDirectory) {
  62. setFontconfigConfigurationPath(tempDirectory.path);
  63. });
  64. // disableRedirection();
  65. // disableLogs();
  66. // enableLogs();
  67. // execute(_commandController.text).then((rc) => print("FFmpeg process exited with rc $rc"));
  68. // executeWithDelimiter(_commandController.text, "_").then((rc) => print("FFmpeg process exited with rc $rc") );
  69. executeWithArguments(_commandController.text.split(" ")).then((rc) => print("FFmpeg process exited with rc $rc"));
  70. setState(() {});
  71. }
  72. void testGetMediaInformation(String mediaPath) {
  73. print("Testing Get Media Information.");
  74. // ENABLE LOG CALLBACK ON EACH CALL
  75. enableLogCallback(commandOutputLogCallback);
  76. enableStatisticsCallback(null);
  77. // CLEAR OUTPUT ON EACH EXECUTION
  78. _commandOutput = "";
  79. VideoUtil.assetPath(mediaPath).then((image1Path) {
  80. getMediaInformation(image1Path).then((info) {
  81. print('Media Information');
  82. print('Path: ${info['path']}');
  83. print('Format: ${info['format']}');
  84. print('Duration: ${info['duration']}');
  85. print('Start time: ${info['startTime']}');
  86. print('Bitrate: ${info['bitrate']}');
  87. if (info['streams'] != null) {
  88. final streamsInfoArray = info['streams'];
  89. if (streamsInfoArray.length > 0) {
  90. for (var streamsInfo in streamsInfoArray) {
  91. print('Stream id: ${streamsInfo['index']}');
  92. print('Stream type: ${streamsInfo['type']}');
  93. print('Stream codec: ${streamsInfo['codec']}');
  94. print('Stream full codec: ${streamsInfo['fullCodec']}');
  95. print('Stream format: ${streamsInfo['format']}');
  96. print('Stream full format: ${streamsInfo['fullFormat']}');
  97. print('Stream width: ${streamsInfo['width']}');
  98. print('Stream height: ${streamsInfo['height']}');
  99. print('Stream bitrate: ${streamsInfo['bitrate']}');
  100. print('Stream sample rate: ${streamsInfo['sampleRate']}');
  101. print('Stream sample format: ${streamsInfo['sampleFormat']}');
  102. print('Stream channel layout: ${streamsInfo['channelLayout']}');
  103. print('Stream sar: ${streamsInfo['sampleAspectRatio']}');
  104. print('Stream dar: ${streamsInfo['displayAspectRatio']}');
  105. print('Stream average frame rate: ${streamsInfo['averageFrameRate']}');
  106. print('Stream real frame rate: ${streamsInfo['realFrameRate']}');
  107. print('Stream time base: ${streamsInfo['timeBase']}');
  108. print('Stream codec time base: ${streamsInfo['codecTimeBase']}');
  109. }
  110. }
  111. }
  112. });
  113. });
  114. setState(() {});
  115. }
  116. void testEncodeVideo() {
  117. print("Testing VIDEO.");
  118. // ENABLE LOG CALLBACK ON EACH CALL
  119. enableLogCallback(encodeOutputLogCallback);
  120. enableStatisticsCallback(statisticsCallback);
  121. // CLEAR OUTPUT ON EACH EXECUTION
  122. _encodeOutput = "";
  123. disableStatistics();
  124. enableStatistics();
  125. VideoUtil.assetPath(ASSET_1).then((image1Path) {
  126. VideoUtil.assetPath(ASSET_2).then((image2Path) {
  127. VideoUtil.assetPath(ASSET_3).then((image3Path) {
  128. final String videoPath = getVideoPath();
  129. final String customOptions = getCustomEncodingOptions();
  130. final String ffmpegCodec = getFFmpegCodecName();
  131. VideoUtil.assetPath(videoPath).then((fullVideoPath) {
  132. execute(VideoUtil.generateEncodeVideoScript(image1Path, image2Path, image3Path, fullVideoPath, ffmpegCodec, customOptions)).then((rc) {
  133. if (rc == 0) {
  134. testGetMediaInformation(fullVideoPath);
  135. }
  136. });
  137. // COMMENT OPTIONAL TESTS
  138. // execute(VideoUtil.generateEncodeVideoScript(image1Path, image2Path, image3Path, videoPath, _currentCodec, "")).timeout(Duration(milliseconds: 1300), onTimeout: () { cancel(); });
  139. // resetStatistics();
  140. getLastReceivedStatistics().then((lastStatistics) {
  141. if (lastStatistics == null) {
  142. print('No last statistics');
  143. } else {
  144. print('Last statistics');
  145. int time = lastStatistics['time'];
  146. int size = lastStatistics['size'];
  147. double bitrate = _doublePrecision(lastStatistics['bitrate'], 2);
  148. double speed = _doublePrecision(lastStatistics['speed'], 2);
  149. int videoFrameNumber = lastStatistics['videoFrameNumber'];
  150. double videoQuality = _doublePrecision(lastStatistics['videoQuality'], 2);
  151. double videoFps = _doublePrecision(lastStatistics['videoFps'], 2);
  152. statisticsCallback(time, size, bitrate, speed, videoFrameNumber, videoQuality, videoFps);
  153. }
  154. });
  155. });
  156. });
  157. });
  158. });
  159. setState(() {});
  160. }
  161. void commandOutputLogCallback(int level, String message) {
  162. _commandOutput += message;
  163. setState(() {});
  164. }
  165. void encodeOutputLogCallback(int level, String message) {
  166. _encodeOutput += message;
  167. setState(() {});
  168. }
  169. void statisticsCallback(int time, int size, double bitrate, double speed, int videoFrameNumber, double videoQuality, double videoFps) {
  170. print("Statistics: time: $time, size: $size, bitrate: $bitrate, speed: $speed, videoFrameNumber: $videoFrameNumber, videoQuality: $videoQuality, videoFps: $videoFps");
  171. }
  172. Future<String> getFFmpegVersion() async {
  173. return await _flutterFFmpeg.getFFmpegVersion();
  174. }
  175. Future<String> getPlatform() async {
  176. return await _flutterFFmpeg.getPlatform();
  177. }
  178. Future<int> executeWithArguments(List arguments) async {
  179. return await _flutterFFmpeg.executeWithArguments(arguments);
  180. }
  181. Future<int> execute(String command) async {
  182. return await _flutterFFmpeg.execute(command);
  183. }
  184. Future<int> executeWithDelimiter(String command, String delimiter) async {
  185. return await _flutterFFmpeg.execute(command, delimiter);
  186. }
  187. Future<void> cancel() async {
  188. return await _flutterFFmpeg.cancel();
  189. }
  190. Future<void> disableRedirection() async {
  191. return await _flutterFFmpeg.disableRedirection();
  192. }
  193. Future<int> getLogLevel() async {
  194. return await _flutterFFmpeg.getLogLevel();
  195. }
  196. Future<void> setLogLevel(int logLevel) async {
  197. return await _flutterFFmpeg.setLogLevel(logLevel);
  198. }
  199. Future<void> enableLogs() async {
  200. return await _flutterFFmpeg.enableLogs();
  201. }
  202. Future<void> disableLogs() async {
  203. return await _flutterFFmpeg.disableLogs();
  204. }
  205. Future<void> enableStatistics() async {
  206. return await _flutterFFmpeg.enableStatistics();
  207. }
  208. Future<void> disableStatistics() async {
  209. return await _flutterFFmpeg.disableStatistics();
  210. }
  211. Future<void> enableLogCallback(Function(int level, String message) logCallback) async {
  212. await _flutterFFmpeg.enableLogCallback(logCallback);
  213. }
  214. Future<void> enableStatisticsCallback(Function(int time, int size, double bitrate, double speed, int videoFrameNumber, double videoQuality, double videoFps) statisticsCallback) async {
  215. await _flutterFFmpeg.enableStatisticsCallback(statisticsCallback);
  216. }
  217. Future<Map<dynamic, dynamic>> getLastReceivedStatistics() async {
  218. return await _flutterFFmpeg.getLastReceivedStatistics();
  219. }
  220. Future<void> resetStatistics() async {
  221. return await _flutterFFmpeg.resetStatistics();
  222. }
  223. Future<void> setFontconfigConfigurationPath(String path) async {
  224. return await _flutterFFmpeg.setFontconfigConfigurationPath(path);
  225. }
  226. Future<void> setFontDirectory(String fontDirectory, Map<String, String> fontNameMap) async {
  227. return await _flutterFFmpeg.setFontDirectory(fontDirectory, fontNameMap);
  228. }
  229. Future<String> getPackageName() async {
  230. return await _flutterFFmpeg.getPackageName();
  231. }
  232. Future<List<dynamic>> getExternalLibraries() async {
  233. return await _flutterFFmpeg.getExternalLibraries();
  234. }
  235. Future<int> getLastReturnCode() async {
  236. return await _flutterFFmpeg.getLastReturnCode();
  237. }
  238. Future<String> getLastCommandOutput() async {
  239. return await _flutterFFmpeg.getLastCommandOutput();
  240. }
  241. Future<Map<dynamic, dynamic>> getMediaInformation(String path) async {
  242. return await _flutterFFmpeg.getMediaInformation(path);
  243. }
  244. void _changedCodec(String selectedCodec) {
  245. setState(() {
  246. _currentCodec = selectedCodec;
  247. });
  248. }
  249. String getFFmpegCodecName() {
  250. String ffmpegCodec = _currentCodec;
  251. // VIDEO CODEC MENU HAS BASIC NAMES, FFMPEG NEEDS LONGER LIBRARY NAMES.
  252. if (ffmpegCodec == "h264") {
  253. ffmpegCodec = "libx264";
  254. } else if (ffmpegCodec == "x265") {
  255. ffmpegCodec = "libx265";
  256. } else if (ffmpegCodec == "xvid") {
  257. ffmpegCodec = "libxvid";
  258. } else if (ffmpegCodec == "vp8") {
  259. ffmpegCodec = "libvpx";
  260. } else if (ffmpegCodec == "vp9") {
  261. ffmpegCodec = "libvpx-vp9";
  262. }
  263. return ffmpegCodec;
  264. }
  265. String getVideoPath() {
  266. String ffmpegCodec = _currentCodec;
  267. String videoPath;
  268. if ((ffmpegCodec == "vp8") || (ffmpegCodec == "vp9")) {
  269. videoPath = "video.webm";
  270. } else {
  271. // mpeg4, x264, x265, xvid
  272. videoPath = "video.mp4";
  273. }
  274. return videoPath;
  275. }
  276. String getCustomEncodingOptions() {
  277. String videoCodec = _currentCodec;
  278. if (videoCodec == "x265") {
  279. return "-crf 28 -preset fast ";
  280. } else if (videoCodec == "vp8") {
  281. return "-b:v 1M -crf 10 ";
  282. } else if (videoCodec == "vp9") {
  283. return "-b:v 2M ";
  284. } else {
  285. return "";
  286. }
  287. }
  288. List<DropdownMenuItem<String>> _getCodecDropDownMenuItems() {
  289. List<DropdownMenuItem<String>> items = new List();
  290. items.add(new DropdownMenuItem(value: "mpeg4", child: new Text("mpeg4")));
  291. items.add(new DropdownMenuItem(value: "h264", child: new Text("h264")));
  292. items.add(new DropdownMenuItem(value: "x265", child: new Text("x265")));
  293. items.add(new DropdownMenuItem(value: "xvid", child: new Text("xvid")));
  294. items.add(new DropdownMenuItem(value: "vp8", child: new Text("vp8")));
  295. items.add(new DropdownMenuItem(value: "vp9", child: new Text("vp9")));
  296. return items;
  297. }
  298. double _doublePrecision(double value, int precision) {
  299. if (value == null) {
  300. return 0;
  301. } else {
  302. return num.parse(value.toStringAsFixed(precision));
  303. }
  304. }
  305. @override
  306. Widget build(BuildContext context) {
  307. return Scaffold(
  308. appBar: AppBar(
  309. title: Text('FlutterFFmpeg Test'),
  310. centerTitle: true,
  311. ),
  312. bottomNavigationBar: Material(
  313. child: DecoratedTabBar(
  314. tabBar: TabBar(
  315. tabs: <Tab>[
  316. Tab(text: "COMMAND"),
  317. Tab(
  318. text: "VIDEO",
  319. )
  320. ],
  321. controller: _controller,
  322. labelColor: Color(0xFF1e90ff),
  323. unselectedLabelColor: Color(0xFF808080),
  324. ),
  325. decoration: BoxDecoration(
  326. border: Border(
  327. top: BorderSide(
  328. color: Color(0xFF808080),
  329. width: 1.0,
  330. ),
  331. bottom: BorderSide(
  332. width: 0.0,
  333. ),
  334. ),
  335. ),
  336. ),
  337. ),
  338. body: TabBarView(
  339. children: <Widget>[
  340. Column(
  341. crossAxisAlignment: CrossAxisAlignment.center,
  342. children: <Widget>[
  343. Container(
  344. padding: const EdgeInsets.fromLTRB(20, 40, 20, 40),
  345. child: TextField(
  346. controller: _commandController,
  347. decoration: InputDecoration(
  348. border: const OutlineInputBorder(
  349. borderSide: const BorderSide(color: Color(0xFF3498DB)),
  350. borderRadius: const BorderRadius.all(
  351. const Radius.circular(5),
  352. ),
  353. ),
  354. focusedBorder: const OutlineInputBorder(
  355. borderSide: const BorderSide(color: Color(0xFF3498DB)),
  356. borderRadius: const BorderRadius.all(
  357. const Radius.circular(5),
  358. ),
  359. ),
  360. enabledBorder: const OutlineInputBorder(
  361. borderSide: const BorderSide(color: Color(0xFF3498DB)),
  362. borderRadius: const BorderRadius.all(
  363. const Radius.circular(5),
  364. ),
  365. ),
  366. contentPadding: EdgeInsets.fromLTRB(8, 12, 8, 12),
  367. hintStyle: new TextStyle(fontSize: 14, color: Colors.grey[400]),
  368. hintText: "Enter command"),
  369. style: new TextStyle(fontSize: 14, color: Colors.black),
  370. ),
  371. ),
  372. Container(
  373. padding: const EdgeInsets.only(bottom: 20),
  374. child: new InkWell(
  375. onTap: () => testRunCommand(),
  376. child: new Container(
  377. width: 100,
  378. height: 38,
  379. decoration: new BoxDecoration(
  380. color: Color(0xFF2ECC71),
  381. borderRadius: new BorderRadius.circular(5),
  382. ),
  383. child: new Center(
  384. child: new Text(
  385. 'RUN',
  386. style: new TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold, color: Colors.white),
  387. ),
  388. ),
  389. ),
  390. ),
  391. ),
  392. Expanded(
  393. child: Container(
  394. alignment: Alignment(-1.0, -1.0),
  395. margin: EdgeInsets.all(20.0),
  396. padding: EdgeInsets.all(4.0),
  397. decoration: new BoxDecoration(borderRadius: BorderRadius.all(new Radius.circular(5)), color: Color(0xFFF1C40F)),
  398. child: SingleChildScrollView(reverse: true, child: Text(_commandOutput))),
  399. )
  400. ],
  401. ),
  402. Column(
  403. crossAxisAlignment: CrossAxisAlignment.center,
  404. children: <Widget>[
  405. Container(
  406. padding: const EdgeInsets.fromLTRB(20, 40, 20, 40),
  407. child: Container(
  408. width: 200,
  409. alignment: Alignment(0.0, 0.0),
  410. decoration: BoxDecoration(color: Color.fromRGBO(155, 89, 182, 1.0), borderRadius: BorderRadius.circular(5)),
  411. child: DropdownButtonHideUnderline(
  412. child: DropdownButton(
  413. style: new TextStyle(
  414. fontSize: 14,
  415. color: Colors.black,
  416. ),
  417. value: _currentCodec,
  418. items: _codecDropDownMenuItems,
  419. onChanged: _changedCodec,
  420. iconSize: 0,
  421. isExpanded: false,
  422. )),
  423. )),
  424. Container(
  425. padding: const EdgeInsets.only(bottom: 20),
  426. child: new InkWell(
  427. onTap: () => testEncodeVideo(),
  428. child: new Container(
  429. width: 100,
  430. height: 38,
  431. decoration: new BoxDecoration(
  432. color: Color(0xFF2ECC71),
  433. borderRadius: new BorderRadius.circular(5),
  434. ),
  435. child: new Center(
  436. child: new Text(
  437. 'ENCODE',
  438. style: new TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold, color: Colors.white),
  439. ),
  440. ),
  441. ),
  442. ),
  443. ),
  444. Expanded(
  445. child: Container(
  446. alignment: Alignment(-1.0, -1.0),
  447. margin: EdgeInsets.all(20.0),
  448. padding: EdgeInsets.all(4.0),
  449. decoration: new BoxDecoration(borderRadius: BorderRadius.all(new Radius.circular(5)), color: Color(0xFFF1C40F)),
  450. child: SingleChildScrollView(reverse: true, child: Text(_encodeOutput))),
  451. )
  452. ],
  453. ),
  454. ],
  455. controller: _controller,
  456. ));
  457. }
  458. @override
  459. void dispose() {
  460. super.dispose();
  461. _commandController.dispose();
  462. }
  463. }