├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── issue-template.md └── workflows │ └── stale.yml ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── LICENSE.GPLv3 ├── README.md ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── arthenica │ └── flutter │ └── ffmpeg │ ├── FlutterFFmpegExecuteFFmpegAsyncArgumentsTask.java │ ├── FlutterFFmpegExecuteFFprobeAsyncArgumentsTask.java │ ├── FlutterFFmpegGetMediaInformationAsyncTask.java │ ├── FlutterFFmpegPlugin.java │ └── FlutterFFmpegResultHandler.java ├── doc └── assets │ ├── flutter-ffmpeg-logo-v2-cropped.png │ ├── flutter-ffmpeg-logo-v2.png │ ├── flutter_test_app.gif │ ├── tip_png_files.png │ ├── tip_runner_deployment_target.png │ └── tip_use_frameworks.png ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── app │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── arthenica │ │ │ │ └── flutter │ │ │ │ └── ffmpeg │ │ │ │ └── flutterffmpegexample │ │ │ │ └── MainActivity.java │ │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── assets │ ├── colosseum.jpg │ ├── doppioone_regular.ttf │ ├── pyramid.jpg │ ├── sil_open_font_license.txt │ ├── subtitle.srt │ ├── tajmahal.jpg │ └── truenorg.otf ├── clean.sh ├── ios │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── Runner │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── main.m ├── lib │ ├── abstract.dart │ ├── audio_tab.dart │ ├── command_tab.dart │ ├── concurrent_execution_tab.dart │ ├── decoration.dart │ ├── flutter_ffmpeg_api_wrapper.dart │ ├── https_tab.dart │ ├── main.dart │ ├── pipe_tab.dart │ ├── player.dart │ ├── popup.dart │ ├── progress_modal.dart │ ├── subtitle_tab.dart │ ├── test_api.dart │ ├── tooltip.dart │ ├── util.dart │ ├── vid_stab_tab.dart │ ├── video_tab.dart │ └── video_util.dart ├── pubspec.lock └── pubspec.yaml ├── flutter_ffmpeg.iml ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── EmptyLogDelegate.h │ ├── EmptyLogDelegate.m │ ├── FlutterExecuteDelegate.h │ ├── FlutterExecuteDelegate.m │ ├── FlutterFFmpegPlugin.h │ └── FlutterFFmpegPlugin.m └── flutter_ffmpeg.podspec ├── lib ├── completed_ffmpeg_execution.dart ├── ffmpeg_execution.dart ├── flutter_ffmpeg.dart ├── log.dart ├── log_level.dart ├── media_information.dart ├── statistics.dart └── stream_information.dart └── pubspec.yaml /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | android/src/main/java/* linguist-vendored 3 | example/android/app/src/main/java/* linguist-vendored 4 | ios/* linguist-vendored 5 | example/ios/* linguist-vendored 6 | packages/* linguist-vendored 7 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: tanersener 2 | custom: ['https://buymeacoff.ee/tanersener'] 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue Template 3 | about: Create an issue to help us improve 4 | 5 | --- 6 | 7 | **Description** 8 | Description of what the issue is about. 9 | 10 | **Expected behavior** 11 | What you expected to happen. 12 | 13 | **Current behavior** 14 | What happened. 15 | 16 | **Screenshots** 17 | If applicable, add screenshots to help explain your problem. 18 | 19 | **Logs** 20 | Post logs here or paste them to [Ghostbin](https://ghostbin.co) and insert the link here. 21 | 22 | **Environment** 23 | Flutter doctor output 24 | 25 | **Other** 26 | Add any other context about the problem here. 27 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Mark stale issues and pull requests 2 | 3 | on: 4 | schedule: 5 | - cron: "30 1 * * *" 6 | 7 | jobs: 8 | stale: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/stale@v1 14 | with: 15 | repo-token: ${{ secrets.GITHUB_TOKEN }} 16 | stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.' 17 | stale-pr-message: 'This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.' 18 | stale-issue-label: 'no-issue-activity' 19 | stale-pr-label: 'no-pr-activity' 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .DS_Store 3 | .dart_tool/ 4 | .pub/ 5 | build/ 6 | /pubspec.lock 7 | /.packages 8 | /.gradle/ 9 | 10 | .settings/ 11 | .project 12 | .classpath 13 | 14 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 5391447fae6209bb21a89e6a5a6583cac1af9b4b 8 | channel: stable 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.4.2 2 | - Support Android v1 and v2 embeddings 3 | 4 | ## 0.4.1 5 | - Migrated the plugin to Android v2 embedding 6 | 7 | ## 0.4.0 8 | - Migrated to nullsafety 9 | - Fixes issue #246 and #266 10 | - Adds closeFFmpegPipe method to close pipes 11 | - Updated example application 12 | 13 | ## 0.3.1 14 | - Adds mavenCentral() repository for Android 15 | - Minor updates in the test application 16 | 17 | ## 0.3.0 18 | - Uses thread pool executor to process Android executions 19 | - Adds listExecutions API method 20 | - Implements async FFmpeg execution methods 21 | - LogCallback and StatisticsCallback functions updated with executionId 22 | - Updates getMediaInformation implementation 23 | - Adds setEnvironmentVariable API method 24 | - Depends on mobile-ffmpeg v4.4 25 | - Allows modifying mobile-ffmpeg version for android 26 | - Includes an updated example application 27 | - Fixes issue #115, #120, #157, #159, #170, #178 and #202 28 | 29 | ## 0.2.10 30 | - Fixes issue #94 31 | 32 | ## 0.2.9 33 | - Implements FFprobe 34 | - Adds concurrent execution support 35 | - Re-organises plugin classes 36 | - iOS releases depend on iconv system library instead of iconv external library 37 | 38 | ## 0.2.8 39 | - Uses ffmpeg v4.3 40 | - Implements registerNewFFmpegPipe API method 41 | 42 | ## 0.2.7 43 | - Uses new package selection mechanism 44 | - Fixes issue #52 45 | 46 | ## 0.2.6 47 | - Adds support for single quotes and double quotes in command strings 48 | 49 | ## 0.2.5 50 | - Implements side data information parsing 51 | 52 | ## 0.2.4 53 | - Adds support for Android devices with API Level 16+ 54 | - Fixes issues #21 and #36 55 | - Removes conflicting attributes from AndroidManifest.xml 56 | - Includes ProGuard configuration file inside 57 | 58 | ## 0.2.3 59 | - Fixed flutter v1.6 compatibility errors on packages 60 | 61 | ## 0.2.2 62 | - Fixed flutter v1.6 compatibility errors 63 | 64 | ## 0.2.1 65 | - Fixed documentation errors 66 | - Updated package description 67 | 68 | ## 0.2.0 69 | - Added AndroidX support 70 | - Removed app icons for Android 71 | - Fixes issues #13 and #14 72 | 73 | ## 0.1.1 74 | - LTS release instructions added 75 | - Documentation updated 76 | 77 | ## 0.1.0 78 | - First release 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | static String safePackageName(String prop) { 2 | prop.replace("-lts", "") 3 | } 4 | 5 | static String safePackageVersion(String prop, String version, String ltsVersion) { 6 | prop.contains("-lts") ? ltsVersion + ".LTS" : version 7 | } 8 | 9 | String safeExtGet(String prop, String fallback) { 10 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 11 | } 12 | 13 | group 'com.arthenica.flutter.ffmpeg' 14 | version '0.4.2' 15 | 16 | buildscript { 17 | repositories { 18 | google() 19 | jcenter() 20 | } 21 | 22 | dependencies { 23 | classpath 'com.android.tools.build:gradle:4.1.3' 24 | } 25 | } 26 | 27 | rootProject.allprojects { 28 | repositories { 29 | mavenCentral() 30 | google() 31 | jcenter() 32 | } 33 | } 34 | 35 | apply plugin: 'com.android.library' 36 | 37 | android { 38 | compileSdkVersion 29 39 | 40 | defaultConfig { 41 | minSdkVersion safeExtGet('flutterFFmpegPackage', 'https').contains("-lts") ? 16 : 24 42 | } 43 | lintOptions { 44 | disable 'InvalidPackage' 45 | } 46 | } 47 | 48 | dependencies { 49 | implementation 'com.arthenica:mobile-ffmpeg-' + safePackageName(safeExtGet('flutterFFmpegPackage', 'https')) + ':' + safePackageVersion(safeExtGet('flutterFFmpegPackage', 'https'), safeExtGet('mobileFFmpegVersion', '4.4'), safeExtGet('mobileFFmpegLTSVersion', '4.4')) 50 | } 51 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | android.enableJetifier=true 2 | android.useAndroidX=true 3 | org.gradle.jvmargs=-Xmx1536M 4 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'flutter_ffmpeg' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/com/arthenica/flutter/ffmpeg/FlutterFFmpegExecuteFFmpegAsyncArgumentsTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019-2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | package com.arthenica.flutter.ffmpeg; 21 | 22 | import android.os.AsyncTask; 23 | import android.util.Log; 24 | 25 | import com.arthenica.mobileffmpeg.FFmpeg; 26 | 27 | import java.util.Arrays; 28 | import java.util.List; 29 | 30 | import io.flutter.plugin.common.MethodChannel; 31 | 32 | /** 33 | * Asynchronous task which performs {@link FFmpeg#execute(String[])} method invocations. 34 | * 35 | * @author Taner Sener 36 | * @since 0.1.0 37 | */ 38 | public class FlutterFFmpegExecuteFFmpegAsyncArgumentsTask extends AsyncTask { 39 | 40 | private final MethodChannel.Result result; 41 | private final List arguments; 42 | private final FlutterFFmpegResultHandler flutterFFmpegResultHandler; 43 | 44 | FlutterFFmpegExecuteFFmpegAsyncArgumentsTask(final List arguments, final FlutterFFmpegResultHandler flutterFFmpegResultHandler, final MethodChannel.Result result) { 45 | this.arguments = arguments; 46 | this.result = result; 47 | this.flutterFFmpegResultHandler = flutterFFmpegResultHandler; 48 | } 49 | 50 | @Override 51 | protected Integer doInBackground(final String... unusedArgs) { 52 | final String[] argumentsArray = arguments.toArray(new String[0]); 53 | 54 | Log.d(FlutterFFmpegPlugin.LIBRARY_NAME, String.format("Running FFmpeg with arguments: %s.", Arrays.toString(argumentsArray))); 55 | 56 | int rc = FFmpeg.execute(argumentsArray); 57 | 58 | Log.d(FlutterFFmpegPlugin.LIBRARY_NAME, String.format("FFmpeg exited with rc: %d", rc)); 59 | 60 | return rc; 61 | } 62 | 63 | @Override 64 | protected void onPostExecute(final Integer rc) { 65 | flutterFFmpegResultHandler.success(result, FlutterFFmpegPlugin.toIntMap(FlutterFFmpegPlugin.KEY_RC, rc)); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /android/src/main/java/com/arthenica/flutter/ffmpeg/FlutterFFmpegExecuteFFprobeAsyncArgumentsTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019-2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | package com.arthenica.flutter.ffmpeg; 21 | 22 | import android.os.AsyncTask; 23 | import android.util.Log; 24 | 25 | import com.arthenica.mobileffmpeg.FFprobe; 26 | 27 | import java.util.Arrays; 28 | import java.util.List; 29 | 30 | import io.flutter.plugin.common.MethodChannel; 31 | 32 | /** 33 | * Asynchronous task which performs {@link FFprobe#execute(String[])} method invocations. 34 | * 35 | * @author Taner Sener 36 | * @since 0.2.9 37 | */ 38 | public class FlutterFFmpegExecuteFFprobeAsyncArgumentsTask extends AsyncTask { 39 | 40 | private final MethodChannel.Result result; 41 | private final List arguments; 42 | private final FlutterFFmpegResultHandler flutterFFmpegResultHandler; 43 | 44 | FlutterFFmpegExecuteFFprobeAsyncArgumentsTask(final List arguments, final FlutterFFmpegResultHandler flutterFFmpegResultHandler, final MethodChannel.Result result) { 45 | this.arguments = arguments; 46 | this.result = result; 47 | this.flutterFFmpegResultHandler = flutterFFmpegResultHandler; 48 | } 49 | 50 | @Override 51 | protected Integer doInBackground(final String... unusedArgs) { 52 | final String[] argumentsArray = arguments.toArray(new String[0]); 53 | 54 | Log.d(FlutterFFmpegPlugin.LIBRARY_NAME, String.format("Running FFprobe with arguments: %s.", Arrays.toString(argumentsArray))); 55 | 56 | int rc = FFprobe.execute(argumentsArray); 57 | 58 | Log.d(FlutterFFmpegPlugin.LIBRARY_NAME, String.format("FFprobe exited with rc: %d", rc)); 59 | 60 | return rc; 61 | } 62 | 63 | @Override 64 | protected void onPostExecute(final Integer rc) { 65 | flutterFFmpegResultHandler.success(result, FlutterFFmpegPlugin.toIntMap(FlutterFFmpegPlugin.KEY_RC, rc)); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /android/src/main/java/com/arthenica/flutter/ffmpeg/FlutterFFmpegGetMediaInformationAsyncTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019-2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | package com.arthenica.flutter.ffmpeg; 21 | 22 | import android.os.AsyncTask; 23 | import android.util.Log; 24 | 25 | import com.arthenica.mobileffmpeg.FFprobe; 26 | import com.arthenica.mobileffmpeg.MediaInformation; 27 | 28 | import io.flutter.plugin.common.MethodChannel; 29 | 30 | /** 31 | * Asynchronous task which performs {@link FFprobe#getMediaInformation(String, Long)} method invocations. 32 | * 33 | * @author Taner Sener 34 | * @since 0.1.0 35 | */ 36 | public class FlutterFFmpegGetMediaInformationAsyncTask extends AsyncTask { 37 | 38 | private final String path; 39 | private final MethodChannel.Result result; 40 | private final FlutterFFmpegResultHandler flutterFFmpegResultHandler; 41 | 42 | FlutterFFmpegGetMediaInformationAsyncTask(final String path, final FlutterFFmpegResultHandler flutterFFmpegResultHandler, final MethodChannel.Result result) { 43 | this.path = path; 44 | this.result = result; 45 | this.flutterFFmpegResultHandler = flutterFFmpegResultHandler; 46 | } 47 | 48 | @Override 49 | protected MediaInformation doInBackground(final String... unusedArgs) { 50 | Log.d(FlutterFFmpegPlugin.LIBRARY_NAME, String.format("Getting media information for %s.", path)); 51 | return FFprobe.getMediaInformation(path); 52 | } 53 | 54 | @Override 55 | protected void onPostExecute(final MediaInformation mediaInformation) { 56 | flutterFFmpegResultHandler.success(result, FlutterFFmpegPlugin.toMediaInformationMap(mediaInformation)); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /android/src/main/java/com/arthenica/flutter/ffmpeg/FlutterFFmpegResultHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019-2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | package com.arthenica.flutter.ffmpeg; 21 | 22 | import android.os.Handler; 23 | import android.os.Looper; 24 | 25 | import io.flutter.plugin.common.EventChannel; 26 | import io.flutter.plugin.common.MethodChannel; 27 | 28 | /** 29 | *

Flutter FFmpeg Result Handler

30 | * 31 | * @author Taner Sener 32 | * @since 0.2.2 33 | */ 34 | class FlutterFFmpegResultHandler { 35 | private final Handler handler; 36 | 37 | FlutterFFmpegResultHandler() { 38 | handler = new Handler(Looper.getMainLooper()); 39 | } 40 | 41 | void notImplemented(final MethodChannel.Result result) { 42 | handler.post(new Runnable() { 43 | 44 | @Override 45 | public void run() { 46 | if (result != null) { 47 | result.notImplemented(); 48 | } 49 | } 50 | }); 51 | } 52 | 53 | void success(final MethodChannel.Result result, final Object object) { 54 | handler.post(new Runnable() { 55 | 56 | @Override 57 | public void run() { 58 | if (result != null) { 59 | result.success(object); 60 | } 61 | } 62 | }); 63 | } 64 | 65 | void success(final EventChannel.EventSink eventSink, final Object object) { 66 | handler.post(new Runnable() { 67 | 68 | @Override 69 | public void run() { 70 | if (eventSink != null) { 71 | eventSink.success(object); 72 | } 73 | } 74 | }); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /doc/assets/flutter-ffmpeg-logo-v2-cropped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/doc/assets/flutter-ffmpeg-logo-v2-cropped.png -------------------------------------------------------------------------------- /doc/assets/flutter-ffmpeg-logo-v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/doc/assets/flutter-ffmpeg-logo-v2.png -------------------------------------------------------------------------------- /doc/assets/flutter_test_app.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/doc/assets/flutter_test_app.gif -------------------------------------------------------------------------------- /doc/assets/tip_png_files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/doc/assets/tip_png_files.png -------------------------------------------------------------------------------- /doc/assets/tip_runner_deployment_target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/doc/assets/tip_runner_deployment_target.png -------------------------------------------------------------------------------- /doc/assets/tip_use_frameworks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/doc/assets/tip_use_frameworks.png -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # Visual Studio Code related 19 | .vscode/ 20 | 21 | # Flutter/Dart/Pub related 22 | **/doc/api/ 23 | .dart_tool/ 24 | .flutter-plugins 25 | .packages 26 | .pub-cache/ 27 | .pub/ 28 | build/ 29 | flutter_*.png 30 | linked_*.ds 31 | unlinked.ds 32 | unlinked_spec.ds 33 | 34 | # Android related 35 | **/android/**/gradle-wrapper.jar 36 | **/android/.gradle 37 | **/android/captures/ 38 | **/android/gradlew 39 | **/android/gradlew.bat 40 | **/android/local.properties 41 | **/android/**/GeneratedPluginRegistrant.java 42 | 43 | # iOS/XCode related 44 | **/ios/**/*.mode1v3 45 | **/ios/**/*.mode2v3 46 | **/ios/**/*.moved-aside 47 | **/ios/**/*.pbxuser 48 | **/ios/**/*.perspectivev3 49 | **/ios/**/*sync/ 50 | **/ios/**/.sconsign.dblite 51 | **/ios/**/.tags* 52 | **/ios/**/.vagrant/ 53 | **/ios/**/DerivedData/ 54 | **/ios/**/Icon? 55 | **/ios/**/Pods/ 56 | **/ios/**/.symlinks/ 57 | **/ios/**/profile 58 | **/ios/**/xcuserdata 59 | **/ios/.generated/ 60 | **/ios/Flutter/App.framework 61 | **/ios/Flutter/Flutter.framework 62 | **/ios/Flutter/Flutter.podspec 63 | **/ios/Flutter/Generated.xcconfig 64 | **/ios/Flutter/app.flx 65 | **/ios/Flutter/app.zip 66 | **/ios/Flutter/flutter_assets/ 67 | **/ios/Flutter/flutter_export_environment.sh 68 | **/ios/ServiceDefinitions.json 69 | **/ios/Runner/GeneratedPluginRegistrant.* 70 | **/ios/Flutter/.last_build_id 71 | 72 | # Exceptions to above rules. 73 | !**/ios/**/default.mode1v3 74 | !**/ios/**/default.mode2v3 75 | !**/ios/**/default.pbxuser 76 | !**/ios/**/default.perspectivev3 77 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 78 | /.flutter-plugins-dependencies 79 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 5391447fae6209bb21a89e6a5a6583cac1af9b4b 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # flutter_ffmpeg_example 2 | 3 | Demonstrates how to use the flutter_ffmpeg plugin. 4 | 5 | ## Getting Started 6 | 7 | 1. Execute synchronous FFmpeg commands. 8 | 9 | - Use execute() method with a single command line 10 | ``` 11 | import 'package:flutter_ffmpeg/flutter_ffmpeg.dart'; 12 | 13 | final FlutterFFmpeg _flutterFFmpeg = new FlutterFFmpeg(); 14 | 15 | _flutterFFmpeg.execute("-i file1.mp4 -c:v mpeg4 file2.mp4").then((rc) => print("FFmpeg process exited with rc $rc")); 16 | ``` 17 | 18 | - Use executeWithArguments() method with an array of arguments 19 | 20 | ``` 21 | import 'package:flutter_ffmpeg/flutter_ffmpeg.dart'; 22 | 23 | final FlutterFFmpeg _flutterFFmpeg = new FlutterFFmpeg(); 24 | 25 | var arguments = ["-i", "file1.mp4", "-c:v", "mpeg4", "file2.mp4"]; 26 | _flutterFFmpeg.executeWithArguments(arguments).then((rc) => print("FFmpeg process exited with rc $rc")); 27 | ``` 28 | 2. Execute asynchronous FFmpeg commands. 29 | 30 | ``` 31 | _flutterFFmpeg.executeAsync(ffmpegCommand, (int executionId, int returnCode) { 32 | print("FFmpeg process for executionId $executionId exited with rc $returnCode"); 33 | }).then((executionId) => print("Async FFmpeg process started with executionId $executionId.")); 34 | ``` 35 | 36 | 3. Execute FFprobe commands. 37 | 38 | - Use execute() method with a single command line 39 | ``` 40 | import 'package:flutter_ffmpeg/flutter_ffmpeg.dart'; 41 | 42 | final FlutterFFprobe _flutterFFprobe = new FlutterFFprobe(); 43 | 44 | _flutterFFprobe.execute("-i file1.mp4").then((rc) => print("FFprobe process exited with rc $rc")); 45 | ``` 46 | 47 | - Use executeWithArguments() method with an array of arguments 48 | 49 | ``` 50 | import 'package:flutter_ffmpeg/flutter_ffmpeg.dart'; 51 | 52 | final FlutterFFprobe _flutterFFprobe = new FlutterFFprobe(); 53 | 54 | var arguments = ["-i", "file1.mp4"]; 55 | _flutterFFprobe.executeWithArguments(arguments).then((rc) => print("FFprobe process exited with rc $rc")); 56 | ``` 57 | 58 | 4. Check execution output. Zero represents successful execution, 255 means user cancel and non-zero values represent failure. 59 | ``` 60 | 61 | final FlutterFFmpegConfig _flutterFFmpegConfig = new FlutterFFmpegConfig(); 62 | 63 | _flutterFFmpegConfig.getLastReturnCode().then((rc) => print("Last rc: $rc")); 64 | 65 | _flutterFFmpegConfig.getLastCommandOutput().then((output) => print("Last command output: $output")); 66 | ``` 67 | 68 | 5. Stop ongoing FFmpeg operations. Note that these two functions do not wait for termination to complete and return immediately. 69 | - Stop all executions 70 | ``` 71 | _flutterFFmpeg.cancel(); 72 | ``` 73 | - Stop a specific execution 74 | ``` 75 | _flutterFFmpeg.cancelExecution(executionId); 76 | ``` 77 | 78 | 6. Get media information for a file. 79 | - Print all fields 80 | ``` 81 | final FlutterFFprobe _flutterFFprobe = new FlutterFFprobe(); 82 | 83 | _flutterFFprobe.getMediaInformation("").then((info) => print(info)); 84 | ``` 85 | - Print selected fields 86 | ``` 87 | final FlutterFFprobe _flutterFFprobe = new FlutterFFprobe(); 88 | 89 | _flutterFFprobe.getMediaInformation("").then((info) { 90 | print("Media Information"); 91 | 92 | print("Path: ${info.getMediaProperties()['filename']}"); 93 | print("Format: ${info.getMediaProperties()['format_name']}"); 94 | print("Duration: ${info.getMediaProperties()['duration']}"); 95 | print("Start time: ${info.getMediaProperties()['start_time']}"); 96 | print("Bitrate: ${info.getMediaProperties()['bit_rate']}"); 97 | Map tags = info.getMediaProperties()['tags']; 98 | if (tags != null) { 99 | tags.forEach((key, value) { 100 | print("Tag: " + key + ":" + value + "\n"); 101 | }); 102 | } 103 | 104 | if (info.getStreams() != null) { 105 | List streams = info.getStreams(); 106 | 107 | if (streams.length > 0) { 108 | for (var stream in streams) { 109 | print("Stream id: ${stream.getAllProperties()['index']}"); 110 | print("Stream type: ${stream.getAllProperties()['codec_type']}"); 111 | print("Stream codec: ${stream.getAllProperties()['codec_name']}"); 112 | print("Stream full codec: ${stream.getAllProperties()['codec_long_name']}"); 113 | print("Stream format: ${stream.getAllProperties()['pix_fmt']}"); 114 | print("Stream width: ${stream.getAllProperties()['width']}"); 115 | print("Stream height: ${stream.getAllProperties()['height']}"); 116 | print("Stream bitrate: ${stream.getAllProperties()['bit_rate']}"); 117 | print("Stream sample rate: ${stream.getAllProperties()['sample_rate']}"); 118 | print("Stream sample format: ${stream.getAllProperties()['sample_fmt']}"); 119 | print("Stream channel layout: ${stream.getAllProperties()['channel_layout']}"); 120 | print("Stream sar: ${stream.getAllProperties()['sample_aspect_ratio']}"); 121 | print("Stream dar: ${stream.getAllProperties()['display_aspect_ratio']}"); 122 | print("Stream average frame rate: ${stream.getAllProperties()['avg_frame_rate']}"); 123 | print("Stream real frame rate: ${stream.getAllProperties()['r_frame_rate']}"); 124 | print("Stream time base: ${stream.getAllProperties()['time_base']}"); 125 | print("Stream codec time base: ${stream.getAllProperties()['codec_time_base']}"); 126 | 127 | Map tags = stream.getAllProperties()['tags']; 128 | if (tags != null) { 129 | tags.forEach((key, value) { 130 | print("Stream tag: " + key + ":" + value + "\n"); 131 | }); 132 | } 133 | } 134 | } 135 | } 136 | }); 137 | ``` 138 | 139 | 7. Enable log callback and redirect all `FFmpeg`/`FFprobe` logs to a console/file/widget. 140 | ``` 141 | void logCallback(Log log) { 142 | print("${log.executionId}:${log.message}"); 143 | } 144 | ... 145 | _flutterFFmpegConfig.enableLogCallback(this.logCallback); 146 | ``` 147 | 148 | 8. Enable statistics callback and follow the progress of an ongoing `FFmpeg` operation. 149 | ``` 150 | void statisticsCallback(Statistics statistics) { 151 | print("Statistics: executionId: ${statistics.executionId}, time: ${statistics.time}, size: ${statistics.size}, bitrate: ${statistics.bitrate}, speed: ${statistics.speed}, videoFrameNumber: ${statistics.videoFrameNumber}, videoQuality: ${statistics.videoQuality}, videoFps: ${statistics.videoFps}"); 152 | } 153 | ... 154 | _flutterFFmpegConfig.enableStatisticsCallback(this.statisticsCallback); 155 | ``` 156 | 157 | 9. Poll statistics without implementing statistics callback. 158 | ``` 159 | _flutterFFmpegConfig.getLastReceivedStatistics().then((stats) => print(stats)); 160 | ``` 161 | 162 | 10. List ongoing executions. 163 | ``` 164 | _flutterFFmpeg.listExecutions().then((ffmpegExecutions) { 165 | ffmpegExecutions.forEach((execution) { 166 | ffprint( 167 | "Execution id:${execution.executionId}, startTime:${execution.command}, command:${execution.startTime}."); 168 | }); 169 | }); 170 | ``` 171 | 172 | 11. Set log level. 173 | ``` 174 | _flutterFFmpegConfig.setLogLevel(LogLevel.AV_LOG_WARNING); 175 | ``` 176 | 177 | 12. Register your own fonts by specifying a custom fonts directory, so they are available to use in `FFmpeg` filters. Please note that this function can not work on relative paths, you need to provide full file system path. 178 | ``` 179 | _flutterFFmpegConfig.setFontDirectory(""); 180 | ``` 181 | 182 | 13. Use your own `fontconfig` configuration. 183 | ``` 184 | _flutterFFmpegConfig.setFontconfigConfigurationPath(""); 185 | ``` 186 | 187 | 14. Disable log functionality of the library. Logs will not be printed to console and log callback will be disabled. 188 | ``` 189 | _flutterFFmpegConfig.disableLogs(); 190 | ``` 191 | 192 | 15. Disable statistics functionality of the library. Statistics callback will be disabled but the last received statistics data will be still available. 193 | ``` 194 | _flutterFFmpegConfig.disableStatistics(); 195 | ``` 196 | 197 | 16. Create new `FFmpeg` pipe. 198 | ``` 199 | _flutterFFmpegConfig.registerNewFFmpegPipe().then((path) { 200 | then((stats) => print("New ffmpeg pipe at $path")); 201 | }); 202 | ``` 203 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 29 29 | ndkVersion "21.4.7075529" 30 | 31 | lintOptions { 32 | disable 'InvalidPackage' 33 | } 34 | 35 | defaultConfig { 36 | applicationId "com.arthenica.flutter.ffmpeg.FlutterFFmpegExample" 37 | minSdkVersion 24 38 | targetSdkVersion 29 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | } 42 | 43 | buildTypes { 44 | debug { 45 | minifyEnabled false 46 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 47 | } 48 | release { 49 | minifyEnabled true 50 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 51 | signingConfig signingConfigs.debug 52 | } 53 | } 54 | } 55 | 56 | flutter { 57 | source '../..' 58 | } 59 | 60 | dependencies { 61 | testImplementation 'junit:junit:4.12' 62 | } 63 | -------------------------------------------------------------------------------- /example/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | #Flutter Wrapper 13 | -keep class io.flutter.app.** { *; } 14 | -keep class io.flutter.plugin.** { *; } 15 | -keep class io.flutter.util.** { *; } 16 | -keep class io.flutter.view.** { *; } 17 | -keep class io.flutter.** { *; } 18 | -keep class io.flutter.plugins.** { *; } 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 11 | 16 | 19 | 20 | 27 | 28 | 29 | 32 | 33 | 34 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/arthenica/flutter/ffmpeg/flutterffmpegexample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.arthenica.flutter.ffmpeg.flutterffmpegexample; 2 | 3 | import io.flutter.embedding.android.FlutterActivity; 4 | 5 | public class MainActivity extends FlutterActivity { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:4.0.1' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | mavenCentral() 15 | google() 16 | jcenter() 17 | } 18 | } 19 | 20 | rootProject.buildDir = '../build' 21 | subprojects { 22 | project.buildDir = "${rootProject.buildDir}/${project.name}" 23 | } 24 | subprojects { 25 | project.evaluationDependsOn(':app') 26 | } 27 | 28 | ext.flutterFFmpegPackage = 'full' 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableJetifier=true 3 | android.useAndroidX=true -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/assets/colosseum.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/assets/colosseum.jpg -------------------------------------------------------------------------------- /example/assets/doppioone_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/assets/doppioone_regular.ttf -------------------------------------------------------------------------------- /example/assets/pyramid.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/assets/pyramid.jpg -------------------------------------------------------------------------------- /example/assets/sil_open_font_license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2012, Julieta Ulanovsky (julieta.ulanovsky@gmail.com), with Reserved Font Names 'Montserrat' 2 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 3 | This license is copied below, and is also available with a FAQ at: 4 | http://scripts.sil.org/OFL 5 | 6 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 7 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL 8 | 9 | ----------------------------------------------------------- 10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 11 | ----------------------------------------------------------- 12 | 13 | PREAMBLE 14 | The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. 15 | 16 | The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. 17 | 18 | DEFINITIONS 19 | "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. 20 | 21 | "Reserved Font Name" refers to any names specified as such after the copyright statement(s). 22 | 23 | "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). 24 | 25 | "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. 26 | 27 | "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. 28 | 29 | PERMISSION & CONDITIONS 30 | Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 31 | 32 | 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 33 | 34 | 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 35 | 36 | 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 37 | 38 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 39 | 40 | 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. 41 | 42 | TERMINATION 43 | This license becomes null and void if any of the above conditions are not met. 44 | 45 | DISCLAIMER 46 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. -------------------------------------------------------------------------------- /example/assets/subtitle.srt: -------------------------------------------------------------------------------- 1 | 1 2 | 00:00:00,021 --> 00:00:04,000 3 | Colosseum or Coliseum is an oval amphitheatre in the centre of the city of Rome, Italy. 4 | 5 | 2 6 | 00:00:04,001 --> 00:00:06,927 7 | The Great Pyramid of Giza is the oldest and largest of the three pyramids in the Giza pyramid complex bordering what is now El Giza, Egypt. 8 | 9 | 3 10 | 00:00:06,928 --> 00:00:09,000 11 | The Taj Mahal is an ivory-white marble mausoleum on the south bank of the Yamuna river in the Indian city of Agra. 12 | -------------------------------------------------------------------------------- /example/assets/tajmahal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/assets/tajmahal.jpg -------------------------------------------------------------------------------- /example/assets/truenorg.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/assets/truenorg.otf -------------------------------------------------------------------------------- /example/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -rf build 4 | rm -rf ios/Pods 5 | rm -rf ios/Flutter/App.framework 6 | rm -rf ios/Flutter/Flutter.framework 7 | rm -rf ios/Flutter/flutter_assets 8 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '9.3' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | def flutter_install_plugin_pods(application_path = nil, relative_symlink_dir, platform) 31 | # defined_in_file is set by CocoaPods and is a Pathname to the Podfile. 32 | application_path ||= File.dirname(defined_in_file.realpath) if self.respond_to?(:defined_in_file) 33 | raise 'Could not find application path' unless application_path 34 | 35 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 36 | # referring to absolute paths on developers' machines. 37 | 38 | symlink_dir = File.expand_path(relative_symlink_dir, application_path) 39 | system('rm', '-rf', symlink_dir) # Avoid the complication of dependencies like FileUtils. 40 | 41 | symlink_plugins_dir = File.expand_path('plugins', symlink_dir) 42 | system('mkdir', '-p', symlink_plugins_dir) 43 | 44 | plugins_file = File.join(application_path, '..', '.flutter-plugins-dependencies') 45 | plugin_pods = flutter_parse_plugins_file(plugins_file, platform) 46 | plugin_pods.each do |plugin_hash| 47 | plugin_name = plugin_hash['name'] 48 | plugin_path = plugin_hash['path'] 49 | if (plugin_name && plugin_path) 50 | symlink = File.join(symlink_plugins_dir, plugin_name) 51 | File.symlink(plugin_path, symlink) 52 | 53 | if plugin_name == 'flutter_ffmpeg' 54 | pod 'flutter_ffmpeg/audio-lts', :path => File.join(relative_symlink_dir, 'plugins', plugin_name, platform) 55 | else 56 | pod plugin_name, :path => File.join(relative_symlink_dir, 'plugins', plugin_name, platform) 57 | end 58 | end 59 | end 60 | end 61 | 62 | target 'Runner' do 63 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 64 | end 65 | 66 | post_install do |installer| 67 | installer.pods_project.targets.each do |target| 68 | flutter_additional_ios_build_settings(target) 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_ffmpeg/audio-lts (0.3.1): 4 | - Flutter 5 | - mobile-ffmpeg-audio (= 4.4.LTS) 6 | - fluttertoast (0.0.2): 7 | - Flutter 8 | - Toast 9 | - mobile-ffmpeg-audio (4.4.LTS) 10 | - path_provider (0.0.1): 11 | - Flutter 12 | - Toast (4.0.0) 13 | - video_player (0.0.1): 14 | - Flutter 15 | 16 | DEPENDENCIES: 17 | - Flutter (from `Flutter`) 18 | - flutter_ffmpeg/audio-lts (from `.symlinks/plugins/flutter_ffmpeg/ios`) 19 | - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) 20 | - path_provider (from `.symlinks/plugins/path_provider/ios`) 21 | - video_player (from `.symlinks/plugins/video_player/ios`) 22 | 23 | SPEC REPOS: 24 | trunk: 25 | - mobile-ffmpeg-audio 26 | - Toast 27 | 28 | EXTERNAL SOURCES: 29 | Flutter: 30 | :path: Flutter 31 | flutter_ffmpeg: 32 | :path: ".symlinks/plugins/flutter_ffmpeg/ios" 33 | fluttertoast: 34 | :path: ".symlinks/plugins/fluttertoast/ios" 35 | path_provider: 36 | :path: ".symlinks/plugins/path_provider/ios" 37 | video_player: 38 | :path: ".symlinks/plugins/video_player/ios" 39 | 40 | SPEC CHECKSUMS: 41 | Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c 42 | flutter_ffmpeg: 582ed04161233e3955540e32e79fd30bcbec272d 43 | fluttertoast: 6122fa75143e992b1d3470f61000f591a798cc58 44 | mobile-ffmpeg-audio: 1e0a053f8a6de57114e50ff48b3a85ff1c60f902 45 | path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c 46 | Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 47 | video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e 48 | 49 | PODFILE CHECKSUM: b4aecb86ca5c092d82af4346073e49c3e007970b 50 | 51 | COCOAPODS: 1.10.1 52 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_ffmpeg_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | NSAppTransportSecurity 45 | 46 | NSAllowsArbitraryLoads 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /example/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/lib/abstract.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | class Refreshable { 21 | void refresh() {} 22 | } 23 | 24 | class DialogFactory { 25 | void dialogShow(String message) {} 26 | 27 | void dialogShowCancellable(String message, Function cancelFunction) {} 28 | 29 | void dialogUpdate(String message) {} 30 | 31 | void dialogHide() {} 32 | } 33 | 34 | class RefreshablePlayerDialogFactory implements Refreshable, DialogFactory { 35 | @override 36 | void dialogHide() {} 37 | 38 | @override 39 | void dialogUpdate(String message) {} 40 | 41 | @override 42 | void refresh() {} 43 | 44 | @override 45 | void dialogShow(String message) {} 46 | 47 | @override 48 | void dialogShowCancellable(String message, Function cancelFunction) {} 49 | } 50 | -------------------------------------------------------------------------------- /example/lib/audio_tab.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | import 'dart:async'; 21 | import 'dart:io'; 22 | 23 | import 'package:flutter/material.dart'; 24 | import 'package:flutter_ffmpeg/completed_ffmpeg_execution.dart'; 25 | import 'package:flutter_ffmpeg/log.dart'; 26 | import 'package:flutter_ffmpeg_example/abstract.dart'; 27 | import 'package:flutter_ffmpeg_example/flutter_ffmpeg_api_wrapper.dart'; 28 | import 'package:flutter_ffmpeg_example/popup.dart'; 29 | import 'package:flutter_ffmpeg_example/test_api.dart'; 30 | import 'package:flutter_ffmpeg_example/tooltip.dart'; 31 | import 'package:flutter_ffmpeg_example/video_util.dart'; 32 | 33 | import 'util.dart'; 34 | 35 | class AudioTab { 36 | late RefreshablePlayerDialogFactory _refreshablePlayerDialogFactory; 37 | late String _selectedCodec; 38 | String _outputText = ""; 39 | 40 | void init(RefreshablePlayerDialogFactory refreshablePlayerDialogFactory) { 41 | _refreshablePlayerDialogFactory = refreshablePlayerDialogFactory; 42 | List> videoCodecList = getAudioCodecList(); 43 | _selectedCodec = videoCodecList[0].value!; 44 | clearLog(); 45 | } 46 | 47 | void setActive() { 48 | print("Audio Tab Activated"); 49 | enableLogCallback(null); 50 | _createAudioSample(); 51 | enableStatisticsCallback(null); 52 | showPopup(AUDIO_TEST_TOOLTIP_TEXT); 53 | } 54 | 55 | void logCallback(Log log) { 56 | appendLog(log.message); 57 | _refreshablePlayerDialogFactory.refresh(); 58 | } 59 | 60 | void appendLog(String logMessage) { 61 | _outputText += logMessage; 62 | } 63 | 64 | void clearLog() { 65 | _outputText = ""; 66 | } 67 | 68 | void changedAudioCodec(String? selectedCodec) { 69 | _selectedCodec = selectedCodec!; 70 | _refreshablePlayerDialogFactory.refresh(); 71 | } 72 | 73 | void encodeAudio() { 74 | getAudioOutputFile().then((audioOutputFile) { 75 | try { 76 | audioOutputFile.delete().catchError((_) {}); 77 | } on Exception catch (_) {} 78 | 79 | final String audioCodec = _selectedCodec; 80 | 81 | ffprint("Testing AUDIO encoding with '$audioCodec' codec"); 82 | 83 | generateAudioEncodeScript().then((ffmpegCommand) { 84 | showProgressDialog(); 85 | 86 | clearLog(); 87 | 88 | executeAsyncFFmpeg(ffmpegCommand, (CompletedFFmpegExecution execution) { 89 | hideProgressDialog(); 90 | 91 | if (execution.returnCode == 0) { 92 | showPopup("Encode completed successfully."); 93 | ffprint("Encode completed successfully."); 94 | } else { 95 | showPopup("Encode failed. Please check log for the details."); 96 | ffprint("Encode failed with rc=${execution.returnCode}."); 97 | } 98 | 99 | ffprint("Testing post execution commands."); 100 | Test.testPostExecutionCommands(); 101 | }).then((executionId) { 102 | ffprint( 103 | "Async FFmpeg process started with arguments '$ffmpegCommand' and executionId $executionId."); 104 | }); 105 | }); 106 | }); 107 | } 108 | 109 | void _createAudioSample() { 110 | getAudioSampleFile().then((audioSampleFile) { 111 | ffprint("Creating AUDIO sample before the test."); 112 | 113 | try { 114 | audioSampleFile.delete().catchError((_) {}); 115 | } on Exception catch (_) {} 116 | 117 | String ffmpegCommand = 118 | "-hide_banner -y -f lavfi -i sine=frequency=1000:duration=5 -c:a pcm_s16le ${audioSampleFile.path}"; 119 | 120 | ffprint("Creating audio sample with '$ffmpegCommand'."); 121 | 122 | executeFFmpeg(ffmpegCommand).then((result) { 123 | if (result == 0) { 124 | ffprint("AUDIO sample created"); 125 | } else { 126 | ffprint("Creating AUDIO sample failed with rc=$result."); 127 | showPopup( 128 | "Creating AUDIO sample failed. Please check log for the details."); 129 | } 130 | enableLogCallback(logCallback); 131 | }); 132 | }); 133 | } 134 | 135 | Future getAudioOutputFile() async { 136 | String audioCodec = _selectedCodec; 137 | 138 | String extension; 139 | switch (audioCodec) { 140 | case "mp2 (twolame)": 141 | extension = "mpg"; 142 | break; 143 | case "mp3 (liblame)": 144 | case "mp3 (libshine)": 145 | extension = "mp3"; 146 | break; 147 | case "vorbis": 148 | extension = "ogg"; 149 | break; 150 | case "opus": 151 | extension = "opus"; 152 | break; 153 | case "amr-nb": 154 | extension = "amr"; 155 | break; 156 | case "amr-wb": 157 | extension = "amr"; 158 | break; 159 | case "ilbc": 160 | extension = "lbc"; 161 | break; 162 | case "speex": 163 | extension = "spx"; 164 | break; 165 | case "wavpack": 166 | extension = "wv"; 167 | break; 168 | default: 169 | // soxr 170 | extension = "wav"; 171 | break; 172 | } 173 | 174 | final String audio = "audio." + extension; 175 | Directory documentsDirectory = await VideoUtil.documentsDirectory; 176 | return new File("${documentsDirectory.path}/$audio"); 177 | } 178 | 179 | Future getAudioSampleFile() async { 180 | Directory documentsDirectory = await VideoUtil.documentsDirectory; 181 | return new File("${documentsDirectory.path}/audio-sample.wav"); 182 | } 183 | 184 | void showProgressDialog() { 185 | _refreshablePlayerDialogFactory.dialogShow("Encoding audio"); 186 | } 187 | 188 | void hideProgressDialog() { 189 | _refreshablePlayerDialogFactory.dialogHide(); 190 | } 191 | 192 | Future generateAudioEncodeScript() async { 193 | String audioCodec = _selectedCodec; 194 | String audioSampleFile = (await getAudioSampleFile()).path; 195 | String audioOutputFile = (await getAudioOutputFile()).path; 196 | 197 | switch (audioCodec) { 198 | case "mp2 (twolame)": 199 | return "-hide_banner -y -i $audioSampleFile -c:a mp2 -b:a 192k $audioOutputFile"; 200 | case "mp3 (liblame)": 201 | return "-hide_banner -y -i $audioSampleFile -c:a libmp3lame -qscale:a 2 $audioOutputFile"; 202 | case "mp3 (libshine)": 203 | return "-hide_banner -y -i $audioSampleFile -c:a libshine -qscale:a 2 $audioOutputFile"; 204 | case "vorbis": 205 | return "-hide_banner -y -i $audioSampleFile -c:a libvorbis -b:a 64k $audioOutputFile"; 206 | case "opus": 207 | return "-hide_banner -y -i $audioSampleFile -c:a libopus -b:a 64k -vbr on -compression_level 10 $audioOutputFile"; 208 | case "amr-nb": 209 | return "-hide_banner -y -i $audioSampleFile -ar 8000 -ab 12.2k -c:a libopencore_amrnb $audioOutputFile"; 210 | case "amr-wb": 211 | return "-hide_banner -y -i $audioSampleFile -ar 8000 -ab 12.2k -c:a libvo_amrwbenc -strict experimental $audioOutputFile"; 212 | case "ilbc": 213 | return "-hide_banner -y -i $audioSampleFile -c:a ilbc -ar 8000 -b:a 15200 $audioOutputFile"; 214 | case "speex": 215 | return "-hide_banner -y -i $audioSampleFile -c:a libspeex -ar 16000 $audioOutputFile"; 216 | case "wavpack": 217 | return "-hide_banner -y -i $audioSampleFile -c:a wavpack -b:a 64k $audioOutputFile"; 218 | default: 219 | // soxr 220 | return "-hide_banner -y -i $audioSampleFile -af aresample=resampler=soxr -ar 44100 $audioOutputFile"; 221 | } 222 | } 223 | 224 | List> getAudioCodecList() { 225 | List> list = List.empty(growable: true); 226 | 227 | list.add(new DropdownMenuItem( 228 | value: "mp2 (twolame)", 229 | child: SizedBox( 230 | width: 100, child: Center(child: new Text("mp2 (twolame)"))))); 231 | list.add(new DropdownMenuItem( 232 | value: "mp3 (liblame)", 233 | child: SizedBox( 234 | width: 100, child: Center(child: new Text("mp3 (liblame)"))))); 235 | list.add(new DropdownMenuItem( 236 | value: "mp3 (libshine)", 237 | child: SizedBox( 238 | width: 100, child: Center(child: new Text("mp3 (libshine)"))))); 239 | list.add(new DropdownMenuItem( 240 | value: "vorbis", 241 | child: SizedBox(width: 100, child: Center(child: new Text("vorbis"))))); 242 | list.add(new DropdownMenuItem( 243 | value: "opus", 244 | child: SizedBox(width: 100, child: Center(child: new Text("opus"))))); 245 | list.add(new DropdownMenuItem( 246 | value: "amr-nb", 247 | child: SizedBox(width: 100, child: Center(child: new Text("amr-nb"))))); 248 | list.add(new DropdownMenuItem( 249 | value: "amr-wb", 250 | child: SizedBox(width: 100, child: Center(child: new Text("amr-wb"))))); 251 | list.add(new DropdownMenuItem( 252 | value: "ilbc", 253 | child: SizedBox(width: 100, child: Center(child: new Text("ilbc"))))); 254 | list.add(new DropdownMenuItem( 255 | value: "soxr", 256 | child: SizedBox(width: 100, child: Center(child: new Text("soxr"))))); 257 | list.add(new DropdownMenuItem( 258 | value: "speex", 259 | child: SizedBox(width: 100, child: Center(child: new Text("speex"))))); 260 | list.add(new DropdownMenuItem( 261 | value: "wavpack", 262 | child: 263 | SizedBox(width: 100, child: Center(child: new Text("wavpack"))))); 264 | 265 | return list; 266 | } 267 | 268 | String getOutputText() => _outputText; 269 | 270 | String getSelectedCodec() => _selectedCodec; 271 | } 272 | -------------------------------------------------------------------------------- /example/lib/command_tab.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | import 'package:flutter/material.dart'; 21 | import 'package:flutter_ffmpeg/log.dart'; 22 | import 'package:flutter_ffmpeg_example/abstract.dart'; 23 | import 'package:flutter_ffmpeg_example/popup.dart'; 24 | import 'package:flutter_ffmpeg_example/tooltip.dart'; 25 | 26 | import 'flutter_ffmpeg_api_wrapper.dart'; 27 | import 'util.dart'; 28 | 29 | class CommandTab { 30 | late Refreshable _refreshable; 31 | late TextEditingController _commandText; 32 | String _outputText = ""; 33 | 34 | void init(Refreshable refreshable) { 35 | _refreshable = refreshable; 36 | _commandText = TextEditingController(); 37 | clearLog(); 38 | 39 | // COMMAND TAB IS SELECTED BY DEFAULT 40 | setActive(); 41 | } 42 | 43 | void setActive() { 44 | print("Command Tab Activated"); 45 | enableLogCallback(logCallback); 46 | enableStatisticsCallback(null); 47 | showPopup(COMMAND_TEST_TOOLTIP_TEXT); 48 | } 49 | 50 | void logCallback(Log log) { 51 | appendLog(log.message); 52 | _refreshable.refresh(); 53 | } 54 | 55 | void appendLog(String logMessage) { 56 | _outputText += logMessage; 57 | } 58 | 59 | void clearLog() { 60 | _outputText = ""; 61 | } 62 | 63 | void runFFmpeg() { 64 | clearLog(); 65 | 66 | final String ffmpegCommand = _commandText.text; 67 | 68 | getLogLevel() 69 | .then((logLevel) => ffprint("Current log level is $logLevel.")); 70 | 71 | ffprint("Testing FFmpeg COMMAND synchronously."); 72 | 73 | ffprint("FFmpeg process started with arguments\n\'$ffmpegCommand\'"); 74 | 75 | executeFFmpeg(ffmpegCommand).then((result) { 76 | ffprint("FFmpeg process exited with rc $result."); 77 | if (result != 0) { 78 | showPopup("Command failed. Please check output for the details."); 79 | } 80 | }); 81 | } 82 | 83 | void runFFprobe() { 84 | clearLog(); 85 | 86 | final String ffprobeCommand = _commandText.text; 87 | 88 | ffprint("Testing FFprobe COMMAND synchronously."); 89 | 90 | ffprint("FFprobe process started with arguments\n\'$ffprobeCommand\'"); 91 | 92 | executeFFprobe(ffprobeCommand).then((result) { 93 | ffprint("FFprobe process exited with rc $result."); 94 | if (result != 0) { 95 | showPopup("Command failed. Please check output for the details."); 96 | } 97 | }); 98 | } 99 | 100 | String getOutputText() => _outputText; 101 | 102 | TextEditingController getCommandText() => _commandText; 103 | 104 | void dispose() { 105 | _commandText.dispose(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /example/lib/concurrent_execution_tab.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | import 'dart:io'; 21 | 22 | import 'package:flutter_ffmpeg/completed_ffmpeg_execution.dart'; 23 | import 'package:flutter_ffmpeg/log.dart'; 24 | import 'package:flutter_ffmpeg_example/abstract.dart'; 25 | import 'package:flutter_ffmpeg_example/flutter_ffmpeg_api_wrapper.dart'; 26 | import 'package:flutter_ffmpeg_example/popup.dart'; 27 | import 'package:flutter_ffmpeg_example/tooltip.dart'; 28 | import 'package:flutter_ffmpeg_example/video_util.dart'; 29 | 30 | import 'util.dart'; 31 | 32 | class ConcurrentExecutionTab { 33 | late Refreshable _refreshable; 34 | String _outputText = ""; 35 | late int _executionId1; 36 | late int _executionId2; 37 | late int _executionId3; 38 | 39 | void init(Refreshable refreshable) { 40 | _refreshable = refreshable; 41 | clearLog(); 42 | 43 | _executionId1 = 0; 44 | _executionId2 = 0; 45 | _executionId3 = 0; 46 | } 47 | 48 | void setActive() { 49 | print("Concurrent Execution Tab Activated"); 50 | enableLogCallback(logCallback); 51 | enableStatisticsCallback(null); 52 | showPopup(CONCURRENT_EXECUTION_TEST_TOOLTIP_TEXT); 53 | } 54 | 55 | void logCallback(Log log) { 56 | appendLog("${log.executionId}:${log.message}"); 57 | _refreshable.refresh(); 58 | } 59 | 60 | void appendLog(String logMessage) { 61 | _outputText += logMessage; 62 | } 63 | 64 | void clearLog() { 65 | _outputText = ""; 66 | } 67 | 68 | void encodeVideo(int buttonNumber) { 69 | VideoUtil.assetPath(VideoUtil.ASSET_1).then((image1Path) { 70 | VideoUtil.assetPath(VideoUtil.ASSET_2).then((image2Path) { 71 | VideoUtil.assetPath(VideoUtil.ASSET_3).then((image3Path) { 72 | getVideoFile(buttonNumber).then((videoFile) { 73 | ffprint("Testing CONCURRENT EXECUTION for button $buttonNumber."); 74 | 75 | final ffmpegCommand = VideoUtil.generateEncodeVideoScript( 76 | image1Path, 77 | image2Path, 78 | image3Path, 79 | videoFile.path, 80 | "mpeg4", 81 | ""); 82 | 83 | executeAsyncFFmpeg(ffmpegCommand, 84 | (CompletedFFmpegExecution execution) { 85 | if (execution.returnCode == 255) { 86 | ffprint( 87 | "FFmpeg process ended with cancel for button $buttonNumber with executionId ${execution.executionId}."); 88 | } else { 89 | ffprint( 90 | "FFmpeg process ended with rc ${execution.returnCode} for button $buttonNumber with executionId ${execution.executionId}."); 91 | } 92 | }).then((executionId) { 93 | ffprint( 94 | "Async FFmpeg process started for button $buttonNumber with arguments '$ffmpegCommand' and executionId $executionId."); 95 | 96 | switch (buttonNumber) { 97 | case 1: 98 | { 99 | _executionId1 = executionId; 100 | } 101 | break; 102 | case 2: 103 | { 104 | _executionId2 = executionId; 105 | } 106 | break; 107 | default: 108 | { 109 | _executionId3 = executionId; 110 | } 111 | } 112 | 113 | runListFFmpegExecutions(); 114 | }); 115 | }); 116 | }); 117 | }); 118 | }); 119 | } 120 | 121 | Future getVideoFile(int buttonNumber) async { 122 | final String video = "video$buttonNumber.mp4"; 123 | Directory documentsDirectory = await VideoUtil.documentsDirectory; 124 | return new File("${documentsDirectory.path}/$video"); 125 | } 126 | 127 | void runListFFmpegExecutions() { 128 | listFFmpegExecutions().then((ffmpegExecutions) { 129 | ffprint("Listing ongoing FFmpeg executions."); 130 | int i = 0; 131 | ffmpegExecutions.forEach((execution) { 132 | i++; 133 | ffprint( 134 | "Execution $i = id:${execution.executionId}, startTime:${execution.command}, command:${execution.startTime}."); 135 | }); 136 | ffprint("Listed ongoing FFmpeg executions."); 137 | }); 138 | } 139 | 140 | void runCancel(final int buttonNumber) { 141 | int executionId = 0; 142 | 143 | switch (buttonNumber) { 144 | case 1: 145 | { 146 | executionId = _executionId1; 147 | } 148 | break; 149 | case 2: 150 | { 151 | executionId = _executionId2; 152 | } 153 | break; 154 | case 3: 155 | { 156 | executionId = _executionId3; 157 | } 158 | } 159 | 160 | ffprint( 161 | "Cancelling FFmpeg process for button $buttonNumber with executionId $executionId."); 162 | 163 | if (executionId == 0) { 164 | cancel(); 165 | } else { 166 | cancelExecution(executionId); 167 | } 168 | } 169 | 170 | String getOutputText() => _outputText; 171 | } 172 | -------------------------------------------------------------------------------- /example/lib/decoration.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | import 'package:flutter/material.dart'; 21 | 22 | final appThemeData = ThemeData( 23 | primaryColor: Color(0xFFF46842), 24 | ); 25 | 26 | final buttonDecoration = new BoxDecoration( 27 | color: Color.fromRGBO(46, 204, 113, 1.0), 28 | borderRadius: new BorderRadius.circular(5), 29 | border: Border.all(color: Color.fromRGBO(39, 174, 96, 1.0))); 30 | 31 | final videoPlayerFrameDecoration = BoxDecoration( 32 | color: Color.fromRGBO(236, 240, 241, 1.0), 33 | border: Border.all(color: Color.fromRGBO(185, 195, 199, 1.0), width: 1.0), 34 | ); 35 | 36 | final dropdownButtonDecoration = BoxDecoration( 37 | color: Color.fromRGBO(155, 89, 182, 1.0), 38 | borderRadius: BorderRadius.circular(5), 39 | border: Border.all(color: Color.fromRGBO(142, 68, 173, 1.0))); 40 | 41 | final outputDecoration = new BoxDecoration( 42 | borderRadius: BorderRadius.all(new Radius.circular(5)), 43 | color: Color.fromRGBO(241, 196, 15, 1.0), 44 | border: Border.all(color: Color.fromRGBO(243, 156, 18, 1.0))); 45 | 46 | final buttonTextStyle = new TextStyle( 47 | fontSize: 14.0, fontWeight: FontWeight.bold, color: Colors.white); 48 | 49 | final buttonSmallTextStyle = new TextStyle( 50 | fontSize: 10.0, fontWeight: FontWeight.bold, color: Colors.white); 51 | 52 | final hintTextStyle = new TextStyle(fontSize: 14, color: Colors.grey[400]); 53 | 54 | final selectedTabColor = Color(0xFF1e90ff); 55 | final unSelectedTabColor = Color(0xFF808080); 56 | 57 | final tabBarDecoration = BoxDecoration( 58 | border: Border( 59 | top: BorderSide( 60 | color: unSelectedTabColor, 61 | width: 1.0, 62 | ), 63 | bottom: BorderSide( 64 | width: 0.0, 65 | ), 66 | ), 67 | ); 68 | 69 | final textFieldStyle = new TextStyle(fontSize: 14, color: Colors.black); 70 | 71 | final dropdownButtonTextStyle = new TextStyle( 72 | fontSize: 14, 73 | color: Colors.black, 74 | ); 75 | 76 | InputDecoration inputDecoration(String hintText) { 77 | return InputDecoration( 78 | border: const OutlineInputBorder( 79 | borderSide: const BorderSide(color: Color.fromRGBO(52, 152, 219, 1.0)), 80 | borderRadius: const BorderRadius.all( 81 | const Radius.circular(5), 82 | ), 83 | ), 84 | focusedBorder: const OutlineInputBorder( 85 | borderSide: const BorderSide(color: Color.fromRGBO(52, 152, 219, 1.0)), 86 | borderRadius: const BorderRadius.all( 87 | const Radius.circular(5), 88 | ), 89 | ), 90 | enabledBorder: const OutlineInputBorder( 91 | borderSide: const BorderSide(color: Color.fromRGBO(52, 152, 219, 1.0)), 92 | borderRadius: const BorderRadius.all( 93 | const Radius.circular(5), 94 | ), 95 | ), 96 | contentPadding: EdgeInsets.fromLTRB(8, 12, 8, 12), 97 | hintStyle: hintTextStyle, 98 | hintText: hintText); 99 | } 100 | -------------------------------------------------------------------------------- /example/lib/flutter_ffmpeg_api_wrapper.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | import 'dart:async'; 21 | 22 | import 'package:flutter_ffmpeg/ffmpeg_execution.dart'; 23 | import 'package:flutter_ffmpeg/flutter_ffmpeg.dart'; 24 | import 'package:flutter_ffmpeg/media_information.dart'; 25 | import 'package:flutter_ffmpeg/statistics.dart'; 26 | 27 | final FlutterFFmpegConfig _flutterFFmpegConfig = new FlutterFFmpegConfig(); 28 | final FlutterFFmpeg _flutterFFmpeg = new FlutterFFmpeg(); 29 | final FlutterFFprobe _flutterFFprobe = new FlutterFFprobe(); 30 | 31 | void enableLogCallback(LogCallback? callback) { 32 | _flutterFFmpegConfig.enableLogCallback(callback); 33 | } 34 | 35 | void enableStatisticsCallback(StatisticsCallback? callback) { 36 | _flutterFFmpegConfig.enableStatisticsCallback(callback); 37 | } 38 | 39 | Future getFFmpegVersion() async { 40 | return await _flutterFFmpegConfig.getFFmpegVersion(); 41 | } 42 | 43 | Future getPlatform() async { 44 | return await _flutterFFmpegConfig.getPlatform(); 45 | } 46 | 47 | Future executeFFmpegWithArguments(List arguments) async { 48 | return await _flutterFFmpeg.executeWithArguments(arguments); 49 | } 50 | 51 | Future executeFFmpeg(String command) async { 52 | return await _flutterFFmpeg.execute(command); 53 | } 54 | 55 | Future executeAsyncFFmpeg( 56 | String command, ExecuteCallback executeCallback) async { 57 | return await _flutterFFmpeg.executeAsync(command, executeCallback); 58 | } 59 | 60 | Future executeFFprobeWithArguments(List arguments) async { 61 | return await _flutterFFprobe.executeWithArguments(arguments); 62 | } 63 | 64 | Future executeFFprobe(String command) async { 65 | return await _flutterFFprobe.execute(command); 66 | } 67 | 68 | Future cancel() async { 69 | return await _flutterFFmpeg.cancel(); 70 | } 71 | 72 | Future cancelExecution(int executionId) async { 73 | return await _flutterFFmpeg.cancelExecution(executionId); 74 | } 75 | 76 | Future disableRedirection() async { 77 | return await _flutterFFmpegConfig.disableRedirection(); 78 | } 79 | 80 | Future getLogLevel() async { 81 | return await _flutterFFmpegConfig.getLogLevel(); 82 | } 83 | 84 | Future setLogLevel(int logLevel) async { 85 | return await _flutterFFmpegConfig.setLogLevel(logLevel); 86 | } 87 | 88 | Future enableLogs() async { 89 | return await _flutterFFmpegConfig.enableLogs(); 90 | } 91 | 92 | Future disableLogs() async { 93 | return await _flutterFFmpegConfig.disableLogs(); 94 | } 95 | 96 | Future enableStatistics() async { 97 | return await _flutterFFmpegConfig.enableStatistics(); 98 | } 99 | 100 | Future disableStatistics() async { 101 | return await _flutterFFmpegConfig.disableStatistics(); 102 | } 103 | 104 | Future getLastReceivedStatistics() async { 105 | return await _flutterFFmpegConfig.getLastReceivedStatistics(); 106 | } 107 | 108 | Future resetStatistics() async { 109 | return await _flutterFFmpegConfig.resetStatistics(); 110 | } 111 | 112 | Future setFontconfigConfigurationPath(String path) async { 113 | return await _flutterFFmpegConfig.setFontconfigConfigurationPath(path); 114 | } 115 | 116 | Future setFontDirectory( 117 | String fontDirectory, Map fontNameMap) async { 118 | return await _flutterFFmpegConfig.setFontDirectory( 119 | fontDirectory, fontNameMap); 120 | } 121 | 122 | Future getPackageName() async { 123 | return await _flutterFFmpegConfig.getPackageName(); 124 | } 125 | 126 | Future> getExternalLibraries() async { 127 | return await _flutterFFmpegConfig.getExternalLibraries(); 128 | } 129 | 130 | Future getLastReturnCode() async { 131 | return await _flutterFFmpegConfig.getLastReturnCode(); 132 | } 133 | 134 | Future getLastCommandOutput() async { 135 | return await _flutterFFmpegConfig.getLastCommandOutput(); 136 | } 137 | 138 | Future getMediaInformation(String path) async { 139 | return await _flutterFFprobe.getMediaInformation(path); 140 | } 141 | 142 | Future registerNewFFmpegPipe() async { 143 | return await _flutterFFmpegConfig.registerNewFFmpegPipe(); 144 | } 145 | 146 | Future closeFFmpegPipe(String ffmpegPipePath) async { 147 | return await _flutterFFmpegConfig.closeFFmpegPipe(ffmpegPipePath); 148 | } 149 | 150 | Future setEnvironmentVariable( 151 | String variableName, String variableValue) async { 152 | return await _flutterFFmpegConfig.setEnvironmentVariable( 153 | variableName, variableValue); 154 | } 155 | 156 | Future> listFFmpegExecutions() async { 157 | return await _flutterFFmpeg.listExecutions(); 158 | } 159 | 160 | List? parseArguments(command) { 161 | return FlutterFFmpeg.parseArguments(command); 162 | } 163 | -------------------------------------------------------------------------------- /example/lib/https_tab.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | import 'package:flutter/material.dart'; 21 | import 'package:flutter_ffmpeg/log.dart'; 22 | import 'package:flutter_ffmpeg/stream_information.dart'; 23 | import 'package:flutter_ffmpeg_example/abstract.dart'; 24 | import 'package:flutter_ffmpeg_example/popup.dart'; 25 | import 'package:flutter_ffmpeg_example/tooltip.dart'; 26 | 27 | import 'flutter_ffmpeg_api_wrapper.dart'; 28 | import 'util.dart'; 29 | 30 | class HttpsTab { 31 | static const String HTTPS_TEST_DEFAULT_URL = 32 | "https://download.blender.org/peach/trailer/trailer_1080p.ogg"; 33 | 34 | late Refreshable _refreshable; 35 | late TextEditingController _urlText; 36 | String _outputText = ""; 37 | 38 | void init(Refreshable refreshable) { 39 | _refreshable = refreshable; 40 | _urlText = TextEditingController(); 41 | clearLog(); 42 | } 43 | 44 | void setActive() { 45 | print("Https Tab Activated"); 46 | enableLogCallback(logCallback); 47 | enableStatisticsCallback(null); 48 | showPopup(HTTPS_TEST_TOOLTIP_TEXT); 49 | } 50 | 51 | void logCallback(Log log) { 52 | appendLog(log.message); 53 | _refreshable.refresh(); 54 | } 55 | 56 | void appendLog(String logMessage) { 57 | _outputText += logMessage; 58 | } 59 | 60 | void clearLog() { 61 | _outputText = ""; 62 | } 63 | 64 | void runGetMediaInformation() { 65 | clearLog(); 66 | 67 | String testUrl = _urlText.text; 68 | if (testUrl.isEmpty) { 69 | testUrl = HTTPS_TEST_DEFAULT_URL; 70 | _urlText.text = testUrl; 71 | ffprint( 72 | "Testing HTTPS with default url '$testUrl'.", 73 | ); 74 | } else { 75 | ffprint("Testing HTTPS with url '$testUrl'."); 76 | } 77 | 78 | // HTTPS COMMAND ARGUMENTS 79 | getMediaInformation(testUrl).then((information) { 80 | if (information.getMediaProperties() != null) { 81 | ffprint("---"); 82 | Map mediaProperties = 83 | information.getMediaProperties()!; 84 | if (mediaProperties.containsKey('filename')) { 85 | ffprint('Path: ${mediaProperties['filename']}'); 86 | } 87 | if (mediaProperties.containsKey('format_name')) { 88 | ffprint("Format: " + mediaProperties['format_name']); 89 | } 90 | if (mediaProperties.containsKey('bit_rate')) { 91 | ffprint("Bitrate: " + mediaProperties['bit_rate']); 92 | } 93 | if (mediaProperties.containsKey('duration')) { 94 | ffprint("Duration: " + mediaProperties['duration']); 95 | } 96 | if (mediaProperties.containsKey('start_time')) { 97 | ffprint("Start time: " + mediaProperties['start_time']); 98 | } 99 | if (mediaProperties.containsKey('nb_streams')) { 100 | ffprint( 101 | "Number of streams: " + mediaProperties['nb_streams'].toString()); 102 | } 103 | Map? tags = mediaProperties['tags']; 104 | if (tags != null) { 105 | tags.forEach((key, value) { 106 | ffprint("Tag: " + key + ":" + value); 107 | }); 108 | } 109 | 110 | List? streams = information.getStreams(); 111 | if (streams != null) { 112 | for (var i = 0; i < streams.length; ++i) { 113 | StreamInformation stream = streams[i]; 114 | ffprint("---"); 115 | Map streamProperties = stream.getAllProperties(); 116 | if (streamProperties.containsKey('index')) { 117 | ffprint("Stream index: " + streamProperties['index'].toString()); 118 | } 119 | if (streamProperties.containsKey('codec_type')) { 120 | ffprint("Stream type: " + streamProperties['codec_type']); 121 | } 122 | if (streamProperties.containsKey('codec_name')) { 123 | ffprint("Stream codec: " + streamProperties['codec_name']); 124 | } 125 | if (streamProperties.containsKey('codec_long_name')) { 126 | ffprint( 127 | "Stream full codec: " + streamProperties['codec_long_name']); 128 | } 129 | if (streamProperties.containsKey('pix_fmt')) { 130 | ffprint("Stream format: " + streamProperties['pix_fmt']); 131 | } 132 | if (streamProperties.containsKey('width')) { 133 | ffprint("Stream width: " + streamProperties['width'].toString()); 134 | } 135 | if (streamProperties.containsKey('height')) { 136 | ffprint( 137 | "Stream height: " + streamProperties['height'].toString()); 138 | } 139 | if (streamProperties.containsKey('bit_rate')) { 140 | ffprint("Stream bitrate: " + streamProperties['bit_rate']); 141 | } 142 | if (streamProperties.containsKey('sample_rate')) { 143 | ffprint("Stream sample rate: " + streamProperties['sample_rate']); 144 | } 145 | if (streamProperties.containsKey('sample_fmt')) { 146 | ffprint( 147 | "Stream sample format: " + streamProperties['sample_fmt']); 148 | } 149 | if (streamProperties.containsKey('channel_layout')) { 150 | ffprint("Stream channel layout: " + 151 | streamProperties['channel_layout']); 152 | } 153 | if (streamProperties.containsKey('sample_aspect_ratio')) { 154 | ffprint("Stream sample aspect ratio: " + 155 | streamProperties['sample_aspect_ratio']); 156 | } 157 | if (streamProperties.containsKey('display_aspect_ratio')) { 158 | ffprint("Stream display aspect ratio: " + 159 | streamProperties['display_aspect_ratio']); 160 | } 161 | if (streamProperties.containsKey('avg_frame_rate')) { 162 | ffprint("Stream average frame rate: " + 163 | streamProperties['avg_frame_rate']); 164 | } 165 | if (streamProperties.containsKey('r_frame_rate')) { 166 | ffprint("Stream real frame rate: " + 167 | streamProperties['r_frame_rate']); 168 | } 169 | if (streamProperties.containsKey('time_base')) { 170 | ffprint("Stream time base: " + streamProperties['time_base']); 171 | } 172 | if (streamProperties.containsKey('codec_time_base')) { 173 | ffprint("Stream codec time base: " + 174 | streamProperties['codec_time_base']); 175 | } 176 | 177 | Map? tags = streamProperties['tags']; 178 | if (tags != null) { 179 | tags.forEach((key, value) { 180 | ffprint("Stream tag: " + key + ":" + value); 181 | }); 182 | } 183 | } 184 | } 185 | } 186 | }); 187 | _refreshable.refresh(); 188 | } 189 | 190 | String getOutputText() => _outputText; 191 | 192 | TextEditingController getUrlText() => _urlText; 193 | 194 | void dispose() { 195 | _urlText.dispose(); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /example/lib/pipe_tab.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | import 'dart:io'; 21 | 22 | import 'package:flutter_ffmpeg/completed_ffmpeg_execution.dart'; 23 | import 'package:flutter_ffmpeg/log.dart'; 24 | import 'package:flutter_ffmpeg/statistics.dart'; 25 | import 'package:flutter_ffmpeg_example/abstract.dart'; 26 | import 'package:flutter_ffmpeg_example/flutter_ffmpeg_api_wrapper.dart'; 27 | import 'package:flutter_ffmpeg_example/player.dart'; 28 | import 'package:flutter_ffmpeg_example/popup.dart'; 29 | import 'package:flutter_ffmpeg_example/tooltip.dart'; 30 | import 'package:flutter_ffmpeg_example/video_util.dart'; 31 | import 'package:video_player/video_player.dart'; 32 | 33 | import 'util.dart'; 34 | 35 | class PipeTab implements PlayerTab { 36 | VideoPlayerController? _videoPlayerController; 37 | late RefreshablePlayerDialogFactory _refreshablePlayerDialogFactory; 38 | Statistics? _statistics; 39 | 40 | void init(RefreshablePlayerDialogFactory refreshablePlayerDialogFactory) { 41 | _refreshablePlayerDialogFactory = refreshablePlayerDialogFactory; 42 | _statistics = null; 43 | } 44 | 45 | void setActive() { 46 | print("Pipe Tab Activated"); 47 | enableLogCallback(logCallback); 48 | enableStatisticsCallback(statisticsCallback); 49 | showPopup(PIPE_TEST_TOOLTIP_TEXT); 50 | } 51 | 52 | void logCallback(Log log) { 53 | ffprint(log.message); 54 | _refreshablePlayerDialogFactory.refresh(); 55 | } 56 | 57 | void statisticsCallback(Statistics statistics) { 58 | this._statistics = statistics; 59 | updateProgressDialog(); 60 | } 61 | 62 | void asyncAssetWriteToPipe(String assetName, String pipePath) { 63 | VideoUtil.writeAssetToPipeAndClose(assetName, pipePath); 64 | } 65 | 66 | void createVideo() { 67 | getVideoFile().then((videoFile) { 68 | registerNewFFmpegPipe().then((pipe1) { 69 | registerNewFFmpegPipe().then((pipe2) { 70 | registerNewFFmpegPipe().then((pipe3) { 71 | // IF VIDEO IS PLAYING STOP PLAYBACK 72 | pause(); 73 | 74 | try { 75 | videoFile.delete().catchError((_) {}); 76 | } on Exception catch (_) {} 77 | 78 | ffprint("Testing PIPE with 'mpeg4' codec"); 79 | 80 | showProgressDialog(); 81 | 82 | final ffmpegCommand = VideoUtil.generateCreateVideoWithPipesScript( 83 | pipe1, pipe2, pipe3, videoFile.path); 84 | 85 | executeAsyncFFmpeg(ffmpegCommand, 86 | (CompletedFFmpegExecution execution) { 87 | ffprint("FFmpeg process exited with rc ${execution.returnCode}."); 88 | 89 | hideProgressDialog(); 90 | 91 | if (execution.returnCode == 0) { 92 | ffprint("Create completed successfully; playing video."); 93 | playVideo(); 94 | } else { 95 | showPopup("Create failed. Please check log for the details."); 96 | ffprint("Create failed with rc=${execution.returnCode}."); 97 | } 98 | }).then((executionId) { 99 | ffprint( 100 | "Async FFmpeg process started with arguments '$ffmpegCommand' and executionId $executionId."); 101 | }); 102 | 103 | asyncAssetWriteToPipe("pyramid.jpg", pipe1); 104 | asyncAssetWriteToPipe("colosseum.jpg", pipe2); 105 | asyncAssetWriteToPipe("tajmahal.jpg", pipe3); 106 | }); 107 | }); 108 | }); 109 | }); 110 | } 111 | 112 | Future playVideo() async { 113 | if (_videoPlayerController != null) { 114 | await _videoPlayerController!.initialize(); 115 | await _videoPlayerController!.play(); 116 | } 117 | _refreshablePlayerDialogFactory.refresh(); 118 | } 119 | 120 | Future pause() async { 121 | if (_videoPlayerController != null) { 122 | await _videoPlayerController!.pause(); 123 | } 124 | _refreshablePlayerDialogFactory.refresh(); 125 | } 126 | 127 | Future getVideoFile() async { 128 | final String video = "video.mp4"; 129 | Directory documentsDirectory = await VideoUtil.documentsDirectory; 130 | return new File("${documentsDirectory.path}/$video"); 131 | } 132 | 133 | void showProgressDialog() { 134 | // CLEAN STATISTICS 135 | _statistics = null; 136 | resetStatistics(); 137 | _refreshablePlayerDialogFactory.dialogShow("Creating video"); 138 | } 139 | 140 | void updateProgressDialog() { 141 | if (_statistics == null) { 142 | return; 143 | } 144 | 145 | int timeInMilliseconds = this._statistics!.time; 146 | if (timeInMilliseconds > 0) { 147 | int totalVideoDuration = 9000; 148 | 149 | int completePercentage = (timeInMilliseconds * 100) ~/ totalVideoDuration; 150 | 151 | _refreshablePlayerDialogFactory 152 | .dialogUpdate("Creating video % $completePercentage"); 153 | _refreshablePlayerDialogFactory.refresh(); 154 | } 155 | } 156 | 157 | void hideProgressDialog() { 158 | _refreshablePlayerDialogFactory.dialogHide(); 159 | } 160 | 161 | @override 162 | void setController(VideoPlayerController controller) { 163 | _videoPlayerController = controller; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /example/lib/player.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | import 'dart:async'; 21 | import 'dart:io'; 22 | 23 | import 'package:flutter/cupertino.dart'; 24 | import 'package:flutter/material.dart'; 25 | import 'package:flutter_ffmpeg_example/decoration.dart'; 26 | import 'package:video_player/video_player.dart'; 27 | 28 | class PlayerTab { 29 | void setController(VideoPlayerController controller) {} 30 | } 31 | 32 | class EmbeddedPlayer extends StatefulWidget { 33 | final String _filePath; 34 | final PlayerTab _playerTab; 35 | 36 | EmbeddedPlayer(this._filePath, this._playerTab); 37 | 38 | @override 39 | _EmbeddedPlayerState createState() => 40 | _EmbeddedPlayerState(new File(_filePath), _playerTab); 41 | } 42 | 43 | class _EmbeddedPlayerState extends State { 44 | final PlayerTab _playerTab; 45 | final File _file; 46 | VideoPlayerController? _videoPlayerController; 47 | bool startedPlaying = false; 48 | 49 | _EmbeddedPlayerState(this._file, this._playerTab); 50 | 51 | @override 52 | void initState() { 53 | super.initState(); 54 | 55 | _videoPlayerController = VideoPlayerController.file(_file); 56 | _playerTab.setController(_videoPlayerController!); 57 | } 58 | 59 | @override 60 | void dispose() { 61 | _videoPlayerController!.dispose(); 62 | super.dispose(); 63 | } 64 | 65 | @override 66 | Widget build(BuildContext context) { 67 | return Material( 68 | elevation: 0, 69 | child: Center( 70 | child: FutureBuilder( 71 | future: Future.value(_videoPlayerController!.value.isInitialized), 72 | builder: (BuildContext context, AsyncSnapshot snapshot) { 73 | if (snapshot.data == true) { 74 | return Container( 75 | alignment: Alignment(0.0, 0.0), 76 | child: VideoPlayer(_videoPlayerController!), 77 | ); 78 | } else { 79 | return Container( 80 | alignment: Alignment(0.0, 0.0), 81 | decoration: videoPlayerFrameDecoration, 82 | ); 83 | } 84 | }, 85 | ), 86 | )); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /example/lib/popup.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | import 'package:flutter/material.dart'; 21 | import 'package:fluttertoast/fluttertoast.dart'; 22 | 23 | void showPopup(String text) { 24 | Fluttertoast.showToast( 25 | msg: text, 26 | toastLength: Toast.LENGTH_SHORT, 27 | gravity: ToastGravity.CENTER, 28 | timeInSecForIosWeb: 2, 29 | backgroundColor: Colors.white70, 30 | textColor: Colors.black87, 31 | fontSize: 16.0); 32 | } 33 | -------------------------------------------------------------------------------- /example/lib/progress_modal.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | import 'package:flutter/material.dart'; 21 | 22 | class ProgressModal { 23 | _Progress? _progress; 24 | late BuildContext _context; 25 | BuildContext? _cancelContext; 26 | late bool displayed; 27 | 28 | ProgressModal(BuildContext context) { 29 | _context = context; 30 | displayed = false; 31 | } 32 | 33 | void show(String message, {Function? cancelFunction}) { 34 | if (displayed) { 35 | return; 36 | } 37 | if (cancelFunction == null) { 38 | _progress = new _Progress(message); 39 | } else { 40 | _progress = new _Progress(message, cancelFunction: () { 41 | cancelFunction(); 42 | hide(); 43 | }); 44 | } 45 | 46 | showDialog( 47 | context: _context, 48 | barrierDismissible: cancelFunction != null, 49 | builder: (BuildContext context) { 50 | _cancelContext = context; 51 | return new WillPopScope( 52 | onWillPop: () async => cancelFunction != null, 53 | child: Dialog( 54 | backgroundColor: Colors.white, 55 | insetAnimationCurve: Curves.easeInOut, 56 | insetAnimationDuration: Duration(milliseconds: 100), 57 | elevation: 10.0, 58 | shape: RoundedRectangleBorder( 59 | borderRadius: BorderRadius.all(Radius.circular(10.0))), 60 | child: _progress)); 61 | }); 62 | 63 | displayed = true; 64 | } 65 | 66 | void update({String? message}) { 67 | if (displayed) { 68 | _progress!.update(message: message); 69 | } 70 | } 71 | 72 | void hide() { 73 | if (displayed) { 74 | Navigator.of(_cancelContext!).pop(); 75 | displayed = false; 76 | } 77 | } 78 | } 79 | 80 | class _Progress extends StatefulWidget { 81 | final _ProgressState _progressState; 82 | 83 | _Progress(String message, {Function? cancelFunction}) 84 | : _progressState = 85 | _ProgressState(message, cancelFunction: cancelFunction); 86 | 87 | update({String? message}) { 88 | _progressState.update(message: message); 89 | } 90 | 91 | @override 92 | State createState() { 93 | return _progressState; 94 | } 95 | } 96 | 97 | class _ProgressState extends State<_Progress> { 98 | String _message; 99 | Function? _cancelFunction; 100 | 101 | _ProgressState(this._message, {Function? cancelFunction}) 102 | : _cancelFunction = cancelFunction; 103 | 104 | update({String? message}) { 105 | if (message != null) { 106 | _message = message; 107 | } 108 | setState(() {}); 109 | } 110 | 111 | @override 112 | void dispose() { 113 | super.dispose(); 114 | } 115 | 116 | @override 117 | Widget build(BuildContext context) { 118 | final text = Expanded( 119 | child: Padding( 120 | padding: const EdgeInsets.fromLTRB(8.0, 8.0, 0, 8.0), 121 | child: Column( 122 | mainAxisSize: MainAxisSize.min, 123 | children: [ 124 | Row( 125 | children: [ 126 | Expanded( 127 | child: Text( 128 | _message, 129 | style: TextStyle( 130 | color: Colors.black, 131 | fontSize: 16.0, 132 | fontWeight: FontWeight.w600), 133 | textDirection: TextDirection.ltr, 134 | )), 135 | ], 136 | ), 137 | ], 138 | ), 139 | ), 140 | ); 141 | 142 | var cancelRow = Row(mainAxisSize: MainAxisSize.min, children: [ 143 | Container( 144 | padding: const EdgeInsets.only(top: 10), 145 | child: new InkWell( 146 | onTap: () => _cancelFunction!(), 147 | child: new Container( 148 | width: 100, 149 | height: 38, 150 | decoration: new BoxDecoration( 151 | color: Colors.grey[400], 152 | borderRadius: new BorderRadius.circular(5), 153 | ), 154 | child: new Center( 155 | child: new Text( 156 | 'CANCEL', 157 | style: new TextStyle( 158 | fontSize: 14.0, 159 | fontWeight: FontWeight.bold, 160 | color: Colors.white), 161 | ), 162 | ), 163 | ), 164 | ), 165 | ) 166 | ]); 167 | 168 | var widgets = _cancelFunction == null 169 | ? [ 170 | Row( 171 | mainAxisSize: MainAxisSize.min, 172 | children: [ 173 | const SizedBox(width: 10.0), 174 | SizedBox( 175 | width: 40.0, 176 | height: 40.0, 177 | child: CircularProgressIndicator(), 178 | ), 179 | const SizedBox(width: 20.0), 180 | text, 181 | const SizedBox(width: 20.0) 182 | ], 183 | ) 184 | ] 185 | : [ 186 | Row( 187 | mainAxisSize: MainAxisSize.min, 188 | children: [ 189 | const SizedBox(width: 10.0), 190 | SizedBox( 191 | width: 40.0, 192 | height: 40.0, 193 | child: CircularProgressIndicator(), 194 | ), 195 | const SizedBox(width: 20.0), 196 | text, 197 | const SizedBox(width: 20.0) 198 | ], 199 | ), 200 | cancelRow 201 | ]; 202 | 203 | return Container( 204 | padding: const EdgeInsets.all(20.0), 205 | child: Column( 206 | mainAxisSize: MainAxisSize.min, children: widgets) // row body 207 | ); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /example/lib/subtitle_tab.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | import 'dart:io'; 21 | 22 | import 'package:flutter_ffmpeg/completed_ffmpeg_execution.dart'; 23 | import 'package:flutter_ffmpeg/log.dart'; 24 | import 'package:flutter_ffmpeg/statistics.dart'; 25 | import 'package:flutter_ffmpeg_example/abstract.dart'; 26 | import 'package:flutter_ffmpeg_example/flutter_ffmpeg_api_wrapper.dart'; 27 | import 'package:flutter_ffmpeg_example/player.dart'; 28 | import 'package:flutter_ffmpeg_example/popup.dart'; 29 | import 'package:flutter_ffmpeg_example/tooltip.dart'; 30 | import 'package:flutter_ffmpeg_example/video_util.dart'; 31 | import 'package:video_player/video_player.dart'; 32 | 33 | import 'util.dart'; 34 | 35 | enum _State { IDLE, CREATING, BURNING } 36 | 37 | class SubtitleTab implements PlayerTab { 38 | VideoPlayerController? _videoPlayerController; 39 | late RefreshablePlayerDialogFactory _refreshablePlayerDialogFactory; 40 | late Statistics? _statistics; 41 | late _State _state; 42 | late int _executionId; 43 | 44 | void init(RefreshablePlayerDialogFactory refreshablePlayerDialogFactory) { 45 | _refreshablePlayerDialogFactory = refreshablePlayerDialogFactory; 46 | _statistics = null; 47 | _state = _State.IDLE; 48 | _executionId = 0; 49 | } 50 | 51 | void setActive() { 52 | print("Subtitle Tab Activated"); 53 | enableLogCallback(logCallback); 54 | enableStatisticsCallback(statisticsCallback); 55 | showPopup(SUBTITLE_TEST_TOOLTIP_TEXT); 56 | } 57 | 58 | void logCallback(Log log) { 59 | ffprint(log.message); 60 | _refreshablePlayerDialogFactory.refresh(); 61 | } 62 | 63 | void statisticsCallback(Statistics statistics) { 64 | this._statistics = statistics; 65 | updateProgressDialog(); 66 | } 67 | 68 | void burnSubtitles() { 69 | VideoUtil.assetPath(VideoUtil.ASSET_1).then((image1Path) { 70 | VideoUtil.assetPath(VideoUtil.ASSET_2).then((image2Path) { 71 | VideoUtil.assetPath(VideoUtil.ASSET_3).then((image3Path) { 72 | VideoUtil.assetPath(VideoUtil.SUBTITLE_ASSET).then((subtitlePath) { 73 | getVideoFile().then((videoFile) { 74 | getVideoWithSubtitlesFile().then((videoWithSubtitlesFile) { 75 | // IF VIDEO IS PLAYING STOP PLAYBACK 76 | pause(); 77 | 78 | try { 79 | videoFile.delete().catchError((_) {}); 80 | } on Exception catch (_) {} 81 | 82 | try { 83 | videoWithSubtitlesFile.delete().catchError((_) {}); 84 | } on Exception catch (_) {} 85 | 86 | ffprint("Testing SUBTITLE burning"); 87 | 88 | showCreateProgressDialog(); 89 | 90 | final ffmpegCommand = VideoUtil.generateEncodeVideoScript( 91 | image1Path, 92 | image2Path, 93 | image3Path, 94 | videoFile.path, 95 | "mpeg4", 96 | ""); 97 | 98 | _state = _State.CREATING; 99 | 100 | executeAsyncFFmpeg(ffmpegCommand, 101 | (CompletedFFmpegExecution execution) { 102 | ffprint( 103 | "FFmpeg process exited with rc ${execution.returnCode}."); 104 | 105 | hideProgressDialog(); 106 | 107 | if (execution.returnCode == 0) { 108 | ffprint( 109 | "Create completed successfully; burning subtitles."); 110 | 111 | String burnSubtitlesCommand = 112 | "-y -i ${videoFile.path} -vf subtitles=$subtitlePath:force_style='Fontname=Trueno' -c:v mpeg4 ${videoWithSubtitlesFile.path}"; 113 | 114 | showBurnProgressDialog(); 115 | 116 | ffprint( 117 | "FFmpeg process started with arguments\n\'$burnSubtitlesCommand\'."); 118 | 119 | _state = _State.BURNING; 120 | 121 | executeAsyncFFmpeg(burnSubtitlesCommand, 122 | (CompletedFFmpegExecution secondExecution) { 123 | ffprint( 124 | "FFmpeg process exited with rc ${secondExecution.returnCode}."); 125 | hideProgressDialog(); 126 | 127 | if (secondExecution.returnCode == 0) { 128 | ffprint( 129 | "Burn subtitles completed successfully; playing video."); 130 | playVideo(); 131 | } else if (secondExecution.returnCode == 255) { 132 | showPopup("Burn subtitles operation cancelled."); 133 | ffprint("Burn subtitles operation cancelled"); 134 | } else { 135 | showPopup( 136 | "Burn subtitles failed. Please check log for the details."); 137 | ffprint( 138 | "Burn subtitles failed with rc=${secondExecution.returnCode}."); 139 | } 140 | }).then((executionId) { 141 | _executionId = executionId; 142 | ffprint( 143 | "Async FFmpeg process started with arguments '$burnSubtitlesCommand' and executionId $executionId."); 144 | }); 145 | } 146 | }).then((executionId) { 147 | _executionId = executionId; 148 | ffprint( 149 | "Async FFmpeg process started with arguments '$ffmpegCommand' and executionId $executionId."); 150 | }); 151 | }); 152 | }); 153 | }); 154 | }); 155 | }); 156 | }); 157 | } 158 | 159 | Future playVideo() async { 160 | if (_videoPlayerController != null) { 161 | await _videoPlayerController!.initialize(); 162 | await _videoPlayerController!.play(); 163 | } 164 | _refreshablePlayerDialogFactory.refresh(); 165 | } 166 | 167 | Future pause() async { 168 | if (_videoPlayerController != null) { 169 | await _videoPlayerController!.pause(); 170 | } 171 | _refreshablePlayerDialogFactory.refresh(); 172 | } 173 | 174 | Future getVideoFile() async { 175 | final String video = "video.mp4"; 176 | Directory documentsDirectory = await VideoUtil.documentsDirectory; 177 | return new File("${documentsDirectory.path}/$video"); 178 | } 179 | 180 | Future getVideoWithSubtitlesFile() async { 181 | final String video = "video-with-subtitles.mp4"; 182 | Directory documentsDirectory = await VideoUtil.documentsDirectory; 183 | return new File("${documentsDirectory.path}/$video"); 184 | } 185 | 186 | void showCreateProgressDialog() { 187 | // CLEAN STATISTICS 188 | _statistics = null; 189 | resetStatistics(); 190 | _refreshablePlayerDialogFactory.dialogShowCancellable( 191 | "Creating video", () => cancelExecution(_executionId)); 192 | } 193 | 194 | void showBurnProgressDialog() { 195 | // CLEAN STATISTICS 196 | _statistics = null; 197 | resetStatistics(); 198 | _refreshablePlayerDialogFactory.dialogShowCancellable( 199 | "Burning subtitles", () => cancelExecution(_executionId)); 200 | } 201 | 202 | void updateProgressDialog() { 203 | if (_statistics == null) { 204 | return; 205 | } 206 | 207 | int timeInMilliseconds = this._statistics!.time; 208 | if (timeInMilliseconds > 0) { 209 | int totalVideoDuration = 9000; 210 | 211 | int completePercentage = (timeInMilliseconds * 100) ~/ totalVideoDuration; 212 | 213 | if (_state == _State.CREATING) { 214 | _refreshablePlayerDialogFactory 215 | .dialogUpdate("Creating video % $completePercentage"); 216 | } else if (_state == _State.BURNING) { 217 | _refreshablePlayerDialogFactory 218 | .dialogUpdate("Burning subtitles % $completePercentage"); 219 | } 220 | _refreshablePlayerDialogFactory.refresh(); 221 | } 222 | } 223 | 224 | void hideProgressDialog() { 225 | _refreshablePlayerDialogFactory.dialogHide(); 226 | } 227 | 228 | @override 229 | void setController(VideoPlayerController controller) { 230 | _videoPlayerController = controller; 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /example/lib/test_api.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | import 'package:flutter_ffmpeg/log_level.dart'; 21 | 22 | import 'flutter_ffmpeg_api_wrapper.dart'; 23 | import 'util.dart'; 24 | 25 | class Test { 26 | static void testCommonApiMethods() { 27 | ffprint("Testing common api methods."); 28 | 29 | getFFmpegVersion().then((version) => ffprint("FFmpeg version: $version")); 30 | getPlatform().then((platform) => ffprint("Platform: $platform")); 31 | getLogLevel().then( 32 | (level) => ffprint("Old log level: " + LogLevel.levelToString(level))); 33 | setLogLevel(LogLevel.AV_LOG_INFO); 34 | getLogLevel().then( 35 | (level) => ffprint("New log level: " + LogLevel.levelToString(level))); 36 | getPackageName() 37 | .then((packageName) => ffprint("Package name: $packageName")); 38 | getExternalLibraries().then((packageList) { 39 | packageList.forEach((value) => ffprint("External library: $value")); 40 | }); 41 | } 42 | 43 | static void testParseArguments() { 44 | ffprint("Testing parseArguments."); 45 | 46 | _testParseSimpleCommand(); 47 | _testParseSingleQuotesInCommand(); 48 | _testParseDoubleQuotesInCommand(); 49 | _testParseDoubleQuotesAndEscapesInCommand(); 50 | } 51 | 52 | static void testPostExecutionCommands() { 53 | getLastCommandOutput() 54 | .then((output) => ffprint("Last command output: $output")); 55 | getLastReturnCode() 56 | .then((returnCode) => ffprint("Last return code: $returnCode")); 57 | getLastReceivedStatistics().then((statistics) => ffprint( 58 | "Last received statistics: executionId: ${statistics.executionId}, " 59 | "video frame number: ${statistics.videoFrameNumber}, video fps: ${statistics.videoFps}, " 60 | "video quality: ${statistics.videoQuality}, size: ${statistics.size}, time: ${statistics.time}, " 61 | "bitrate: ${statistics.bitrate}, speed: ${statistics.speed}")); 62 | } 63 | 64 | static void _testParseSimpleCommand() { 65 | var argumentArray = parseArguments( 66 | "-hide_banner -loop 1 -i file.jpg -filter_complex [0:v]setpts=PTS-STARTPTS[video] -map [video] -vsync 2 -async 1 video.mp4"); 67 | 68 | assert(argumentArray != null); 69 | assert(argumentArray!.length == 14); 70 | 71 | assert("-hide_banner" == argumentArray![0]); 72 | assert("-loop" == argumentArray![1]); 73 | assert("1" == argumentArray![2]); 74 | assert("-i" == argumentArray![3]); 75 | assert("file.jpg" == argumentArray![4]); 76 | assert("-filter_complex" == argumentArray![5]); 77 | assert("[0:v]setpts=PTS-STARTPTS[video]" == argumentArray![6]); 78 | assert("-map" == argumentArray![7]); 79 | assert("[video]" == argumentArray![8]); 80 | assert("-vsync" == argumentArray![9]); 81 | assert("2" == argumentArray![10]); 82 | assert("-async" == argumentArray![11]); 83 | assert("1" == argumentArray![12]); 84 | assert("video.mp4" == argumentArray![13]); 85 | } 86 | 87 | static void _testParseSingleQuotesInCommand() { 88 | var argumentArray = parseArguments( 89 | "-loop 1 'file one.jpg' -filter_complex '[0:v]setpts=PTS-STARTPTS[video]' -map [video] video.mp4 "); 90 | 91 | assert(argumentArray != null); 92 | assert(argumentArray!.length == 8); 93 | 94 | assert("-loop" == argumentArray![0]); 95 | assert("1" == argumentArray![1]); 96 | assert("file one.jpg" == argumentArray![2]); 97 | assert("-filter_complex" == argumentArray![3]); 98 | assert("[0:v]setpts=PTS-STARTPTS[video]" == argumentArray![4]); 99 | assert("-map" == argumentArray![5]); 100 | assert("[video]" == argumentArray![6]); 101 | assert("video.mp4" == argumentArray![7]); 102 | } 103 | 104 | static void _testParseDoubleQuotesInCommand() { 105 | var argumentArray = parseArguments( 106 | "-loop 1 \"file one.jpg\" -filter_complex \"[0:v]setpts=PTS-STARTPTS[video]\" -map [video] video.mp4 "); 107 | 108 | assert(argumentArray != null); 109 | assert(argumentArray!.length == 8); 110 | 111 | assert("-loop" == argumentArray![0]); 112 | assert("1" == argumentArray![1]); 113 | assert("file one.jpg" == argumentArray![2]); 114 | assert("-filter_complex" == argumentArray![3]); 115 | assert("[0:v]setpts=PTS-STARTPTS[video]" == argumentArray![4]); 116 | assert("-map" == argumentArray![5]); 117 | assert("[video]" == argumentArray![6]); 118 | assert("video.mp4" == argumentArray![7]); 119 | 120 | argumentArray = parseArguments( 121 | " -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"); 122 | 123 | assert(argumentArray != null); 124 | assert(argumentArray!.length == 13); 125 | 126 | assert("-i" == argumentArray![0]); 127 | assert("file:///tmp/input.mp4" == argumentArray![1]); 128 | assert("-vcodec" == argumentArray![2]); 129 | assert("libx264" == argumentArray![3]); 130 | assert("-vf" == argumentArray![4]); 131 | assert("scale=1024:1024,pad=width=1024:height=1024:x=0:y=0:color=black" == 132 | argumentArray![5]); 133 | assert("-acodec" == argumentArray![6]); 134 | assert("copy" == argumentArray![7]); 135 | assert("-q:v" == argumentArray![8]); 136 | assert("0" == argumentArray![9]); 137 | assert("-q:a" == argumentArray![10]); 138 | assert("0" == argumentArray![11]); 139 | assert("video.mp4" == argumentArray![12]); 140 | } 141 | 142 | static void _testParseDoubleQuotesAndEscapesInCommand() { 143 | var argumentArray = parseArguments( 144 | " -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"); 145 | 146 | assert(argumentArray != null); 147 | assert(argumentArray!.length == 13); 148 | 149 | assert("-i" == argumentArray![0]); 150 | assert("file:///tmp/input.mp4" == argumentArray![1]); 151 | assert("-vf" == argumentArray![2]); 152 | assert( 153 | "subtitles=file:///tmp/subtitles.srt:force_style='FontSize=16,PrimaryColour=&HFFFFFF&'" == 154 | argumentArray![3]); 155 | assert("-vcodec" == argumentArray![4]); 156 | assert("libx264" == argumentArray![5]); 157 | assert("-acodec" == argumentArray![6]); 158 | assert("copy" == argumentArray![7]); 159 | assert("-q:v" == argumentArray![8]); 160 | assert("0" == argumentArray![9]); 161 | assert("-q:a" == argumentArray![10]); 162 | assert("0" == argumentArray![11]); 163 | assert("video.mp4" == argumentArray![12]); 164 | 165 | argumentArray = parseArguments( 166 | " -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"); 167 | 168 | assert(argumentArray != null); 169 | assert(argumentArray!.length == 13); 170 | 171 | assert("-i" == argumentArray![0]); 172 | assert("file:///tmp/input.mp4" == argumentArray![1]); 173 | assert("-vf" == argumentArray![2]); 174 | assert( 175 | "subtitles=file:///tmp/subtitles.srt:force_style=\\\"FontSize=16,PrimaryColour=&HFFFFFF&\\\"" == 176 | argumentArray![3]); 177 | assert("-vcodec" == argumentArray![4]); 178 | assert("libx264" == argumentArray![5]); 179 | assert("-acodec" == argumentArray![6]); 180 | assert("copy" == argumentArray![7]); 181 | assert("-q:v" == argumentArray![8]); 182 | assert("0" == argumentArray![9]); 183 | assert("-q:a" == argumentArray![10]); 184 | assert("0" == argumentArray![11]); 185 | assert("video.mp4" == argumentArray![12]); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /example/lib/tooltip.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | const String COMMAND_TEST_TOOLTIP_TEXT = 21 | "Enter an FFmpeg command without 'ffmpeg' at the beginning and click one of the RUN buttons"; 22 | 23 | const String VIDEO_TEST_TOOLTIP_TEXT = 24 | "Select a video codec and press ENCODE button"; 25 | 26 | const String HTTPS_TEST_TOOLTIP_TEXT = 27 | "Enter the https url of a media file and click the button"; 28 | 29 | const String AUDIO_TEST_TOOLTIP_TEXT = 30 | "Select an audio codec and press ENCODE button"; 31 | 32 | const String SUBTITLE_TEST_TOOLTIP_TEXT = 33 | "Click the button to burn subtitles. Created video will play inside the frame below"; 34 | 35 | const String VIDSTAB_TEST_TOOLTIP_TEXT = 36 | "Click the button to stabilize video. Original video will play above and stabilized video will play below"; 37 | 38 | const String PIPE_TEST_TOOLTIP_TEXT = 39 | "Click the button to create a video using pipe redirection. Created video will play inside the frame below"; 40 | 41 | const String CONCURRENT_EXECUTION_TEST_TOOLTIP_TEXT = 42 | " Use ENCODE and CANCEL buttons to start/stop multiple execution"; 43 | -------------------------------------------------------------------------------- /example/lib/util.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | String today() { 21 | var now = new DateTime.now(); 22 | return "${now.year}-${now.month}-${now.day}"; 23 | } 24 | 25 | String now() { 26 | var now = new DateTime.now(); 27 | return "${now.year}-${now.month}-${now.day} ${now.hour}:${now.minute}:${now.second}.${now.millisecond}"; 28 | } 29 | 30 | void ffprint(String text) { 31 | final pattern = new RegExp('.{1,900}'); 32 | var nowString = now(); 33 | pattern 34 | .allMatches(text) 35 | .forEach((match) => print("$nowString - " + match.group(0)!)); 36 | } 37 | -------------------------------------------------------------------------------- /example/lib/vid_stab_tab.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | import 'dart:io'; 21 | 22 | import 'package:flutter_ffmpeg/completed_ffmpeg_execution.dart'; 23 | import 'package:flutter_ffmpeg/log.dart'; 24 | import 'package:flutter_ffmpeg_example/abstract.dart'; 25 | import 'package:flutter_ffmpeg_example/flutter_ffmpeg_api_wrapper.dart'; 26 | import 'package:flutter_ffmpeg_example/player.dart'; 27 | import 'package:flutter_ffmpeg_example/popup.dart'; 28 | import 'package:flutter_ffmpeg_example/tooltip.dart'; 29 | import 'package:flutter_ffmpeg_example/video_util.dart'; 30 | import 'package:video_player/video_player.dart'; 31 | 32 | import 'util.dart'; 33 | 34 | class _ControllerWrapper implements PlayerTab { 35 | late VideoPlayerController? _controller; 36 | 37 | @override 38 | void setController(VideoPlayerController controller) { 39 | _controller = controller; 40 | } 41 | } 42 | 43 | class VidStabTab { 44 | late _ControllerWrapper videoController; 45 | late _ControllerWrapper stabilizedVideoController; 46 | late RefreshablePlayerDialogFactory _refreshablePlayerDialogFactory; 47 | 48 | void init(RefreshablePlayerDialogFactory refreshablePlayerDialogFactory) { 49 | _refreshablePlayerDialogFactory = refreshablePlayerDialogFactory; 50 | videoController = _ControllerWrapper(); 51 | stabilizedVideoController = _ControllerWrapper(); 52 | } 53 | 54 | void setActive() { 55 | print("VidStab Tab Activated"); 56 | enableLogCallback(logCallback); 57 | enableStatisticsCallback(null); 58 | showPopup(VIDSTAB_TEST_TOOLTIP_TEXT); 59 | } 60 | 61 | void logCallback(Log log) { 62 | ffprint(log.message); 63 | _refreshablePlayerDialogFactory.refresh(); 64 | } 65 | 66 | void stabilizeVideo() { 67 | VideoUtil.assetPath(VideoUtil.ASSET_1).then((image1Path) { 68 | VideoUtil.assetPath(VideoUtil.ASSET_2).then((image2Path) { 69 | VideoUtil.assetPath(VideoUtil.ASSET_3).then((image3Path) { 70 | getShakeResultsFile().then((shakeResultsFile) { 71 | getVideoFile().then((videoFile) { 72 | getStabilizedVideoFile().then((stabilizedVideoFile) { 73 | // IF VIDEO IS PLAYING STOP PLAYBACK 74 | pauseVideo(); 75 | pauseStabilizedVideo(); 76 | 77 | try { 78 | shakeResultsFile.delete().catchError((_) {}); 79 | } on Exception catch (_) {} 80 | 81 | try { 82 | videoFile.delete().catchError((_) {}); 83 | } on Exception catch (_) {} 84 | 85 | try { 86 | stabilizedVideoFile.delete().catchError((_) {}); 87 | } on Exception catch (_) {} 88 | 89 | ffprint("Testing VID.STAB"); 90 | 91 | showCreateProgressDialog(); 92 | 93 | final ffmpegCommand = VideoUtil.generateShakingVideoScript( 94 | image1Path, image2Path, image3Path, videoFile.path); 95 | 96 | executeAsyncFFmpeg(ffmpegCommand, 97 | (CompletedFFmpegExecution execution) { 98 | ffprint( 99 | "FFmpeg process exited with rc ${execution.returnCode}."); 100 | 101 | hideProgressDialog(); 102 | 103 | if (execution.returnCode == 0) { 104 | ffprint( 105 | "Create completed successfully; stabilizing video."); 106 | 107 | String analyzeVideoCommand = 108 | "-y -i ${videoFile.path} -vf vidstabdetect=shakiness=10:accuracy=15:result=${shakeResultsFile.path} -f null -"; 109 | 110 | showStabilizeProgressDialog(); 111 | 112 | executeAsyncFFmpeg(analyzeVideoCommand, 113 | (CompletedFFmpegExecution secondExecution) { 114 | ffprint( 115 | "FFmpeg process exited with rc ${secondExecution.returnCode}."); 116 | 117 | final String stabilizeVideoCommand = 118 | "-y -i ${videoFile.path} -vf vidstabtransform=smoothing=30:input=${shakeResultsFile.path} -c:v mpeg4 ${stabilizedVideoFile.path}"; 119 | 120 | //@TODO check return code before starting the third execution 121 | 122 | executeAsyncFFmpeg(stabilizeVideoCommand, 123 | (CompletedFFmpegExecution thirdExecution) { 124 | hideProgressDialog(); 125 | 126 | if (thirdExecution.returnCode == 0) { 127 | ffprint( 128 | "Stabilize video completed successfully; playing videos."); 129 | playVideo(); 130 | playStabilizedVideo(); 131 | } else { 132 | showPopup( 133 | "Stabilize video failed. Please check log for the details."); 134 | ffprint( 135 | "Stabilize video failed with rc=${thirdExecution.returnCode}."); 136 | } 137 | }).then((executionId) { 138 | ffprint( 139 | "Async FFmpeg process started with arguments '$stabilizeVideoCommand' and executionId $executionId."); 140 | }); 141 | }).then((executionId) { 142 | ffprint( 143 | "Async FFmpeg process started with arguments '$analyzeVideoCommand' and executionId $executionId."); 144 | }); 145 | } 146 | }).then((executionId) { 147 | ffprint( 148 | "Async FFmpeg process started with arguments '$ffmpegCommand' and executionId $executionId."); 149 | }); 150 | }); 151 | }); 152 | }); 153 | }); 154 | }); 155 | }); 156 | } 157 | 158 | Future playVideo() async { 159 | if (videoController._controller != null) { 160 | await videoController._controller!.initialize(); 161 | await videoController._controller!.play(); 162 | } 163 | _refreshablePlayerDialogFactory.refresh(); 164 | } 165 | 166 | Future pauseVideo() async { 167 | if (videoController._controller != null) { 168 | await videoController._controller!.pause(); 169 | } 170 | _refreshablePlayerDialogFactory.refresh(); 171 | } 172 | 173 | Future playStabilizedVideo() async { 174 | if (stabilizedVideoController._controller != null) { 175 | await stabilizedVideoController._controller!.initialize(); 176 | await stabilizedVideoController._controller!.play(); 177 | } 178 | _refreshablePlayerDialogFactory.refresh(); 179 | } 180 | 181 | Future pauseStabilizedVideo() async { 182 | if (stabilizedVideoController._controller != null) { 183 | await stabilizedVideoController._controller!.pause(); 184 | } 185 | _refreshablePlayerDialogFactory.refresh(); 186 | } 187 | 188 | Future getShakeResultsFile() async { 189 | final String subtitle = "transforms.trf"; 190 | Directory documentsDirectory = await VideoUtil.tempDirectory; 191 | return new File("${documentsDirectory.path}/$subtitle"); 192 | } 193 | 194 | Future getVideoFile() async { 195 | final String video = "video-shaking.mp4"; 196 | Directory documentsDirectory = await VideoUtil.documentsDirectory; 197 | return new File("${documentsDirectory.path}/$video"); 198 | } 199 | 200 | Future getStabilizedVideoFile() async { 201 | final String video = "video-stabilized.mp4"; 202 | Directory documentsDirectory = await VideoUtil.documentsDirectory; 203 | return new File("${documentsDirectory.path}/$video"); 204 | } 205 | 206 | void showCreateProgressDialog() { 207 | _refreshablePlayerDialogFactory.dialogShow("Creating video"); 208 | } 209 | 210 | void showStabilizeProgressDialog() { 211 | _refreshablePlayerDialogFactory.dialogShow("Stabilizing video"); 212 | } 213 | 214 | void hideProgressDialog() { 215 | _refreshablePlayerDialogFactory.dialogHide(); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /example/lib/video_tab.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | import 'dart:async'; 21 | import 'dart:io'; 22 | 23 | import 'package:flutter/material.dart'; 24 | import 'package:flutter_ffmpeg/completed_ffmpeg_execution.dart'; 25 | import 'package:flutter_ffmpeg/log.dart'; 26 | import 'package:flutter_ffmpeg/statistics.dart'; 27 | import 'package:flutter_ffmpeg_example/abstract.dart'; 28 | import 'package:flutter_ffmpeg_example/player.dart'; 29 | import 'package:flutter_ffmpeg_example/popup.dart'; 30 | import 'package:flutter_ffmpeg_example/test_api.dart'; 31 | import 'package:flutter_ffmpeg_example/tooltip.dart'; 32 | import 'package:flutter_ffmpeg_example/util.dart'; 33 | import 'package:flutter_ffmpeg_example/video_util.dart'; 34 | import 'package:video_player/video_player.dart'; 35 | 36 | import 'flutter_ffmpeg_api_wrapper.dart'; 37 | 38 | class VideoTab implements PlayerTab { 39 | VideoPlayerController? _videoPlayerController; 40 | late RefreshablePlayerDialogFactory _refreshablePlayerDialogFactory; 41 | late String _selectedCodec; 42 | late Statistics? _statistics; 43 | 44 | void init(RefreshablePlayerDialogFactory refreshablePlayerDialogFactory) { 45 | _refreshablePlayerDialogFactory = refreshablePlayerDialogFactory; 46 | List> videoCodecList = getVideoCodecList(); 47 | _selectedCodec = videoCodecList[0].value!; 48 | _statistics = null; 49 | } 50 | 51 | void setActive() { 52 | print("Video Tab Activated"); 53 | enableLogCallback(logCallback); 54 | enableStatisticsCallback(statisticsCallback); 55 | showPopup(VIDEO_TEST_TOOLTIP_TEXT); 56 | } 57 | 58 | void logCallback(Log log) { 59 | ffprint(log.message); 60 | } 61 | 62 | void statisticsCallback(Statistics statistics) { 63 | this._statistics = statistics; 64 | updateProgressDialog(); 65 | } 66 | 67 | void changedVideoCodec(String? selectedCodec) { 68 | _selectedCodec = selectedCodec!; 69 | _refreshablePlayerDialogFactory.refresh(); 70 | } 71 | 72 | void encodeVideo() { 73 | ffprint( 74 | "Testing post execution commands before starting the new encoding."); 75 | Test.testPostExecutionCommands(); 76 | 77 | VideoUtil.assetPath(VideoUtil.ASSET_1).then((image1Path) { 78 | VideoUtil.assetPath(VideoUtil.ASSET_2).then((image2Path) { 79 | VideoUtil.assetPath(VideoUtil.ASSET_3).then((image3Path) { 80 | getVideoFile().then((videoFile) { 81 | // IF VIDEO IS PLAYING STOP PLAYBACK 82 | pause(); 83 | 84 | try { 85 | videoFile.delete().catchError((_) {}); 86 | } on Exception catch (_) {} 87 | 88 | final String videoCodec = getSelectedVideoCodec(); 89 | 90 | ffprint("Testing VIDEO encoding with '$videoCodec' codec"); 91 | 92 | hideProgressDialog(); 93 | showProgressDialog(); 94 | 95 | final ffmpegCommand = VideoUtil.generateEncodeVideoScript( 96 | image1Path, 97 | image2Path, 98 | image3Path, 99 | videoFile.path, 100 | videoCodec, 101 | getCustomOptions()); 102 | 103 | executeAsyncFFmpeg(ffmpegCommand, 104 | (CompletedFFmpegExecution execution) { 105 | hideProgressDialog(); 106 | 107 | if (execution.returnCode == 0) { 108 | ffprint("Encode completed successfully; playing video."); 109 | playVideo(); 110 | } else { 111 | ffprint("Encode failed with rc=${execution.returnCode}."); 112 | showPopup("Encode failed. Please check log for the details."); 113 | } 114 | }).then((executionId) { 115 | ffprint( 116 | "Async FFmpeg process started with arguments '$ffmpegCommand' and executionId $executionId."); 117 | }); 118 | }); 119 | }); 120 | }); 121 | }); 122 | } 123 | 124 | Future playVideo() async { 125 | if (_videoPlayerController != null) { 126 | await _videoPlayerController!.initialize(); 127 | await _videoPlayerController!.play(); 128 | } 129 | _refreshablePlayerDialogFactory.refresh(); 130 | } 131 | 132 | Future pause() async { 133 | if (_videoPlayerController != null) { 134 | await _videoPlayerController!.pause(); 135 | } 136 | _refreshablePlayerDialogFactory.refresh(); 137 | } 138 | 139 | String getSelectedVideoCodec() { 140 | String videoCodec = _selectedCodec; 141 | 142 | // VIDEO CODEC MENU HAS BASIC NAMES, FFMPEG NEEDS LONGER LIBRARY NAMES. 143 | // APPLYING NECESSARY TRANSFORMATION HERE 144 | switch (videoCodec) { 145 | case "x264": 146 | videoCodec = "libx264"; 147 | break; 148 | case "openh264": 149 | videoCodec = "libopenh264"; 150 | break; 151 | case "x265": 152 | videoCodec = "libx265"; 153 | break; 154 | case "xvid": 155 | videoCodec = "libxvid"; 156 | break; 157 | case "vp8": 158 | videoCodec = "libvpx"; 159 | break; 160 | case "vp9": 161 | videoCodec = "libvpx-vp9"; 162 | break; 163 | case "aom": 164 | videoCodec = "libaom-av1"; 165 | break; 166 | case "kvazaar": 167 | videoCodec = "libkvazaar"; 168 | break; 169 | case "theora": 170 | videoCodec = "libtheora"; 171 | break; 172 | } 173 | 174 | return videoCodec; 175 | } 176 | 177 | Future getVideoFile() async { 178 | String videoCodec = _selectedCodec; 179 | 180 | String extension; 181 | switch (videoCodec) { 182 | case "vp8": 183 | case "vp9": 184 | extension = "webm"; 185 | break; 186 | case "aom": 187 | extension = "mkv"; 188 | break; 189 | case "theora": 190 | extension = "ogv"; 191 | break; 192 | case "hap": 193 | extension = "mov"; 194 | break; 195 | default: 196 | // mpeg4, x264, x265, xvid, kvazaar 197 | extension = "mp4"; 198 | break; 199 | } 200 | 201 | final String video = "video." + extension; 202 | Directory documentsDirectory = await VideoUtil.documentsDirectory; 203 | return new File("${documentsDirectory.path}/$video"); 204 | } 205 | 206 | String getCustomOptions() { 207 | String videoCodec = _selectedCodec; 208 | 209 | switch (videoCodec) { 210 | case "x265": 211 | return "-crf 28 -preset fast "; 212 | case "vp8": 213 | return "-b:v 1M -crf 10 "; 214 | case "vp9": 215 | return "-b:v 2M "; 216 | case "aom": 217 | return "-crf 30 -strict experimental "; 218 | case "theora": 219 | return "-qscale:v 7 "; 220 | case "hap": 221 | return "-format hap_q "; 222 | default: 223 | // kvazaar, mpeg4, x264, xvid 224 | return ""; 225 | } 226 | } 227 | 228 | List> getVideoCodecList() { 229 | List> list = List.empty(growable: true); 230 | 231 | list.add(new DropdownMenuItem( 232 | value: "mpeg4", 233 | child: SizedBox(width: 100, child: Center(child: new Text("mpeg4"))))); 234 | list.add(new DropdownMenuItem( 235 | value: "x264", 236 | child: SizedBox(width: 100, child: Center(child: new Text("x264"))))); 237 | list.add(new DropdownMenuItem( 238 | value: "openh264", 239 | child: 240 | SizedBox(width: 100, child: Center(child: new Text("openh264"))))); 241 | list.add(new DropdownMenuItem( 242 | value: "x265", 243 | child: SizedBox(width: 100, child: Center(child: new Text("x265"))))); 244 | list.add(new DropdownMenuItem( 245 | value: "xvid", 246 | child: SizedBox(width: 100, child: Center(child: new Text("xvid"))))); 247 | list.add(new DropdownMenuItem( 248 | value: "vp8", 249 | child: SizedBox(width: 100, child: Center(child: new Text("vp8"))))); 250 | list.add(new DropdownMenuItem( 251 | value: "vp9", 252 | child: SizedBox(width: 100, child: Center(child: new Text("vp9"))))); 253 | list.add(new DropdownMenuItem( 254 | value: "aom", 255 | child: SizedBox(width: 100, child: Center(child: new Text("aom"))))); 256 | list.add(new DropdownMenuItem( 257 | value: "kvazaar", 258 | child: 259 | SizedBox(width: 100, child: Center(child: new Text("kvazaar"))))); 260 | list.add(new DropdownMenuItem( 261 | value: "theora", 262 | child: SizedBox(width: 100, child: Center(child: new Text("theora"))))); 263 | list.add(new DropdownMenuItem( 264 | value: "hap", 265 | child: SizedBox(width: 100, child: Center(child: new Text("hap"))))); 266 | 267 | return list; 268 | } 269 | 270 | void showProgressDialog() { 271 | // CLEAN STATISTICS 272 | _statistics = null; 273 | resetStatistics(); 274 | _refreshablePlayerDialogFactory.dialogShow("Encoding video"); 275 | } 276 | 277 | void updateProgressDialog() { 278 | var statistics = this._statistics; 279 | if (statistics == null) { 280 | return; 281 | } 282 | 283 | int timeInMilliseconds = statistics.time; 284 | if (timeInMilliseconds > 0) { 285 | int totalVideoDuration = 9000; 286 | 287 | int completePercentage = (timeInMilliseconds * 100) ~/ totalVideoDuration; 288 | 289 | _refreshablePlayerDialogFactory 290 | .dialogUpdate("Encoding video % $completePercentage"); 291 | _refreshablePlayerDialogFactory.refresh(); 292 | } 293 | } 294 | 295 | void hideProgressDialog() { 296 | _refreshablePlayerDialogFactory.dialogHide(); 297 | } 298 | 299 | String getSelectedCodec() => _selectedCodec; 300 | 301 | @override 302 | void setController(VideoPlayerController controller) { 303 | _videoPlayerController = controller; 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /example/lib/video_util.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | import 'dart:async'; 21 | import 'dart:io'; 22 | 23 | import 'package:flutter/services.dart'; 24 | import 'package:path/path.dart'; 25 | import 'package:path_provider/path_provider.dart'; 26 | 27 | import 'flutter_ffmpeg_api_wrapper.dart'; 28 | import 'util.dart'; 29 | 30 | class VideoUtil { 31 | static const String ASSET_1 = "pyramid.jpg"; 32 | static const String ASSET_2 = "colosseum.jpg"; 33 | static const String ASSET_3 = "tajmahal.jpg"; 34 | static const String SUBTITLE_ASSET = "subtitle.srt"; 35 | static const String FONT_ASSET_1 = "doppioone_regular.ttf"; 36 | static const String FONT_ASSET_2 = "truenorg.otf"; 37 | 38 | static void registerAppFont() { 39 | var fontNameMapping = Map(); 40 | fontNameMapping["MyFontName"] = "Doppio One"; 41 | VideoUtil.tempDirectory.then((tempDirectory) { 42 | setFontDirectory(tempDirectory.path, fontNameMapping); 43 | setEnvironmentVariable( 44 | "FFREPORT", 45 | "file=" + 46 | new File(tempDirectory.path + "/" + today() + "-ffreport.txt") 47 | .path); 48 | }); 49 | } 50 | 51 | static void prepareAssets() async { 52 | await VideoUtil.assetToFile(ASSET_1); 53 | await VideoUtil.assetToFile(ASSET_2); 54 | await VideoUtil.assetToFile(ASSET_3); 55 | await VideoUtil.assetToFile(SUBTITLE_ASSET); 56 | await VideoUtil.assetToFile(FONT_ASSET_1); 57 | await VideoUtil.assetToFile(FONT_ASSET_2); 58 | } 59 | 60 | static Future assetToFile(String assetName) async { 61 | final ByteData assetByteData = await rootBundle.load('assets/$assetName'); 62 | 63 | final List byteList = assetByteData.buffer 64 | .asUint8List(assetByteData.offsetInBytes, assetByteData.lengthInBytes); 65 | 66 | final String fullTemporaryPath = 67 | join((await tempDirectory).path, assetName); 68 | 69 | Future fileFuture = new File(fullTemporaryPath) 70 | .writeAsBytes(byteList, mode: FileMode.writeOnly, flush: true); 71 | 72 | ffprint('assets/$assetName saved to file at $fullTemporaryPath.'); 73 | 74 | return fileFuture; 75 | } 76 | 77 | static void writeAssetToPipeAndClose( 78 | String assetName, String pipePath) async { 79 | final ByteData byteData = await rootBundle.load('assets/$assetName'); 80 | 81 | var pipeFile = new File(pipePath); 82 | await pipeFile.writeAsBytes( 83 | byteData.buffer 84 | .asUint8List(byteData.offsetInBytes, byteData.lengthInBytes), 85 | mode: FileMode.writeOnly, 86 | flush: false); 87 | 88 | ffprint('assets/$assetName saved to pipe at $pipePath.'); 89 | 90 | closeFFmpegPipe(pipePath); 91 | } 92 | 93 | static Future assetPath(String assetName) async { 94 | return join((await tempDirectory).path, assetName); 95 | } 96 | 97 | static Future get documentsDirectory async { 98 | return await getApplicationDocumentsDirectory(); 99 | } 100 | 101 | static Future get tempDirectory async { 102 | return await getTemporaryDirectory(); 103 | } 104 | 105 | static String generateEncodeVideoScript( 106 | String image1Path, 107 | String image2Path, 108 | String image3Path, 109 | String videoFilePath, 110 | String videoCodec, 111 | String customOptions) { 112 | return "-hide_banner -y -loop 1 -i '" + 113 | image1Path + 114 | "' " + 115 | "-loop 1 -i \"" + 116 | image2Path + 117 | "\" " + 118 | "-loop 1 -i \"" + 119 | image3Path + 120 | "\" " + 121 | "-filter_complex " + 122 | "\"[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];" + 123 | "[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];" + 124 | "[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];" + 125 | "[stream1out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=3,select=lte(n\\,90)[stream1overlaid];" + 126 | "[stream1out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30)[stream1ending];" + 127 | "[stream2out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=2,select=lte(n\\,60)[stream2overlaid];" + 128 | "[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];" + 129 | "[stream3out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=2,select=lte(n\\,60)[stream3overlaid];" + 130 | "[stream3out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30)[stream3starting];" + 131 | "[stream2starting][stream1ending]blend=all_expr='if(gte(X,(W/2)*T/1)*lte(X,W-(W/2)*T/1),B,A)':shortest=1[stream2blended];" + 132 | "[stream3starting][stream2ending]blend=all_expr='if(gte(X,(W/2)*T/1)*lte(X,W-(W/2)*T/1),B,A)':shortest=1[stream3blended];" + 133 | "[stream1overlaid][stream2blended][stream2overlaid][stream3blended][stream3overlaid]concat=n=5:v=1:a=0,scale=w=640:h=424,format=yuv420p[video]\"" + 134 | " -map [video] -vsync 2 -async 1 " + 135 | customOptions + 136 | "-c:v " + 137 | videoCodec + 138 | " -r 30 " + 139 | videoFilePath; 140 | } 141 | 142 | static String generateShakingVideoScript( 143 | final String image1Path, 144 | final String image2Path, 145 | final String image3Path, 146 | final String videoFilePath) { 147 | return "-hide_banner -y -loop 1 -i \"" + 148 | image1Path + 149 | "\" " + 150 | "-loop 1 -i '" + 151 | image2Path + 152 | "' " + 153 | "-loop 1 -i " + 154 | image3Path + 155 | " " + 156 | "-f lavfi -i color=black:s=640x427 " + 157 | "-filter_complex \"" + 158 | "[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[stream1out];" + 159 | "[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[stream2out];" + 160 | "[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[stream3out];" + 161 | "[stream1out]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=3[stream1overlaid];" + 162 | "[stream2out]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=3[stream2overlaid];" + 163 | "[stream3out]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=3[stream3overlaid];" + 164 | "[3:v][stream1overlaid]overlay=x=\'2*mod(n,4)\':y=\'2*mod(n,2)\',trim=duration=3[stream1shaking];" + 165 | "[3:v][stream2overlaid]overlay=x=\'2*mod(n,4)\':y=\'2*mod(n,2)\',trim=duration=3[stream2shaking];" + 166 | "[3:v][stream3overlaid]overlay=x=\'2*mod(n,4)\':y=\'2*mod(n,2)\',trim=duration=3[stream3shaking];" + 167 | "[stream1shaking][stream2shaking][stream3shaking]concat=n=3:v=1:a=0,scale=w=640:h=424,format=yuv420p[video]\"" + 168 | " -map [video] -vsync 2 -async 1 -c:v mpeg4 -r 30 " + 169 | videoFilePath; 170 | } 171 | 172 | static String generateCreateVideoWithPipesScript( 173 | final String image1Pipe, 174 | final String image2Pipe, 175 | final String image3Pipe, 176 | final String videoFilePath) { 177 | return "-hide_banner -y -i \"" + 178 | image1Pipe + 179 | "\" " + 180 | "-i '" + 181 | image2Pipe + 182 | "' " + 183 | "-i " + 184 | image3Pipe + 185 | " " + 186 | "-filter_complex \"" + 187 | "[0:v]loop=loop=-1:size=1:start=0,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];" + 188 | "[1:v]loop=loop=-1:size=1:start=0,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];" + 189 | "[2:v]loop=loop=-1:size=1:start=0,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];" + 190 | "[stream1out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=3,select=lte(n\\,90)[stream1overlaid];" + 191 | "[stream1out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30)[stream1ending];" + 192 | "[stream2out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=2,select=lte(n\\,60)[stream2overlaid];" + 193 | "[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];" + 194 | "[stream3out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=2,select=lte(n\\,60)[stream3overlaid];" + 195 | "[stream3out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30)[stream3starting];" + 196 | "[stream2starting][stream1ending]blend=all_expr=\'if(gte(X,(W/2)*T/1)*lte(X,W-(W/2)*T/1),B,A)\':shortest=1[stream2blended];" + 197 | "[stream3starting][stream2ending]blend=all_expr=\'if(gte(X,(W/2)*T/1)*lte(X,W-(W/2)*T/1),B,A)\':shortest=1[stream3blended];" + 198 | "[stream1overlaid][stream2blended][stream2overlaid][stream3blended][stream3overlaid]concat=n=5:v=1:a=0,scale=w=640:h=424,format=yuv420p[video]\"" + 199 | " -map [video] -vsync 2 -async 1 -c:v mpeg4 -r 30 " + 200 | videoFilePath; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.6.1" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.2.0" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.15.0" 46 | cupertino_icons: 47 | dependency: "direct main" 48 | description: 49 | name: cupertino_icons 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.0.3" 53 | fake_async: 54 | dependency: transitive 55 | description: 56 | name: fake_async 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.2.0" 60 | ffi: 61 | dependency: transitive 62 | description: 63 | name: ffi 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.0.0" 67 | file: 68 | dependency: transitive 69 | description: 70 | name: file 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "6.1.0" 74 | flutter: 75 | dependency: "direct main" 76 | description: flutter 77 | source: sdk 78 | version: "0.0.0" 79 | flutter_ffmpeg: 80 | dependency: "direct main" 81 | description: 82 | name: flutter_ffmpeg 83 | url: "https://pub.dartlang.org" 84 | source: hosted 85 | version: "0.4.2" 86 | flutter_test: 87 | dependency: "direct dev" 88 | description: flutter 89 | source: sdk 90 | version: "0.0.0" 91 | flutter_web_plugins: 92 | dependency: transitive 93 | description: flutter 94 | source: sdk 95 | version: "0.0.0" 96 | fluttertoast: 97 | dependency: "direct main" 98 | description: 99 | name: fluttertoast 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "8.0.8" 103 | js: 104 | dependency: transitive 105 | description: 106 | name: js 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "0.6.3" 110 | matcher: 111 | dependency: transitive 112 | description: 113 | name: matcher 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "0.12.10" 117 | meta: 118 | dependency: transitive 119 | description: 120 | name: meta 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "1.3.0" 124 | path: 125 | dependency: "direct main" 126 | description: 127 | name: path 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "1.8.0" 131 | path_provider: 132 | dependency: "direct main" 133 | description: 134 | name: path_provider 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "2.0.2" 138 | path_provider_linux: 139 | dependency: transitive 140 | description: 141 | name: path_provider_linux 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "2.0.0" 145 | path_provider_macos: 146 | dependency: transitive 147 | description: 148 | name: path_provider_macos 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "2.0.0" 152 | path_provider_platform_interface: 153 | dependency: transitive 154 | description: 155 | name: path_provider_platform_interface 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "2.0.1" 159 | path_provider_windows: 160 | dependency: transitive 161 | description: 162 | name: path_provider_windows 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "2.0.0" 166 | platform: 167 | dependency: transitive 168 | description: 169 | name: platform 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "3.0.0" 173 | plugin_platform_interface: 174 | dependency: transitive 175 | description: 176 | name: plugin_platform_interface 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "2.0.0" 180 | process: 181 | dependency: transitive 182 | description: 183 | name: process 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "4.1.0" 187 | sky_engine: 188 | dependency: transitive 189 | description: flutter 190 | source: sdk 191 | version: "0.0.99" 192 | source_span: 193 | dependency: transitive 194 | description: 195 | name: source_span 196 | url: "https://pub.dartlang.org" 197 | source: hosted 198 | version: "1.8.1" 199 | stack_trace: 200 | dependency: transitive 201 | description: 202 | name: stack_trace 203 | url: "https://pub.dartlang.org" 204 | source: hosted 205 | version: "1.10.0" 206 | stream_channel: 207 | dependency: transitive 208 | description: 209 | name: stream_channel 210 | url: "https://pub.dartlang.org" 211 | source: hosted 212 | version: "2.1.0" 213 | string_scanner: 214 | dependency: transitive 215 | description: 216 | name: string_scanner 217 | url: "https://pub.dartlang.org" 218 | source: hosted 219 | version: "1.1.0" 220 | term_glyph: 221 | dependency: transitive 222 | description: 223 | name: term_glyph 224 | url: "https://pub.dartlang.org" 225 | source: hosted 226 | version: "1.2.0" 227 | test_api: 228 | dependency: transitive 229 | description: 230 | name: test_api 231 | url: "https://pub.dartlang.org" 232 | source: hosted 233 | version: "0.3.0" 234 | typed_data: 235 | dependency: transitive 236 | description: 237 | name: typed_data 238 | url: "https://pub.dartlang.org" 239 | source: hosted 240 | version: "1.3.0" 241 | vector_math: 242 | dependency: transitive 243 | description: 244 | name: vector_math 245 | url: "https://pub.dartlang.org" 246 | source: hosted 247 | version: "2.1.0" 248 | video_player: 249 | dependency: "direct main" 250 | description: 251 | name: video_player 252 | url: "https://pub.dartlang.org" 253 | source: hosted 254 | version: "2.1.12" 255 | video_player_platform_interface: 256 | dependency: transitive 257 | description: 258 | name: video_player_platform_interface 259 | url: "https://pub.dartlang.org" 260 | source: hosted 261 | version: "4.1.0" 262 | video_player_web: 263 | dependency: transitive 264 | description: 265 | name: video_player_web 266 | url: "https://pub.dartlang.org" 267 | source: hosted 268 | version: "2.0.0" 269 | win32: 270 | dependency: transitive 271 | description: 272 | name: win32 273 | url: "https://pub.dartlang.org" 274 | source: hosted 275 | version: "2.0.0" 276 | xdg_directories: 277 | dependency: transitive 278 | description: 279 | name: xdg_directories 280 | url: "https://pub.dartlang.org" 281 | source: hosted 282 | version: "0.2.0" 283 | sdks: 284 | dart: ">=2.12.0 <3.0.0" 285 | flutter: ">=2.0.0" 286 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_ffmpeg_example 2 | description: Demonstrates how to use the flutter_ffmpeg plugin. 3 | version: 0.4.2 4 | publish_to: 'none' 5 | 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | cupertino_icons: ^1.0.3 13 | path_provider: ^2.0.2 14 | path: ^1.8.0 15 | fluttertoast: ^8.0.8 16 | video_player: ^2.1.12 17 | flutter_ffmpeg: 0.4.2 18 | 19 | dev_dependencies: 20 | flutter_test: 21 | sdk: flutter 22 | 23 | flutter: 24 | uses-material-design: true 25 | assets: 26 | - assets/ 27 | -------------------------------------------------------------------------------- /flutter_ffmpeg.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tanersener/flutter-ffmpeg/fca32a79bc3ce7bb5fc76f5de0f5af5de9de3b61/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/EmptyLogDelegate.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | #include 21 | 22 | /** 23 | * Empty log delegate. 24 | */ 25 | @interface EmptyLogDelegate : NSObject 26 | @end 27 | -------------------------------------------------------------------------------- /ios/Classes/EmptyLogDelegate.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | #include "EmptyLogDelegate.h" 21 | 22 | /** 23 | * Empty log delegate. 24 | */ 25 | @implementation EmptyLogDelegate 26 | 27 | - (void)logCallback: (int)level :(NSString*)message { 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /ios/Classes/FlutterExecuteDelegate.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | #import 21 | #import 22 | 23 | /** 24 | * Execute delegate for async executions. 25 | */ 26 | @interface FlutterExecuteDelegate : NSObject 27 | 28 | - (instancetype)initWithEventSink:(FlutterEventSink)eventSink; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /ios/Classes/FlutterExecuteDelegate.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | #import "FlutterExecuteDelegate.h" 21 | 22 | static NSString *const EVENT_EXECUTE = @"FlutterFFmpegExecuteCallback"; 23 | 24 | /** 25 | * Execute delegate for async executions. 26 | */ 27 | @implementation FlutterExecuteDelegate { 28 | FlutterEventSink _eventSink; 29 | } 30 | 31 | - (instancetype)initWithEventSink:(FlutterEventSink)eventSink { 32 | self = [super init]; 33 | if (self) { 34 | _eventSink = eventSink; 35 | } 36 | 37 | return self; 38 | } 39 | 40 | - (void)executeCallback:(long)executionId :(int)returnCode { 41 | NSMutableDictionary *executeDictionary = [[NSMutableDictionary alloc] init]; 42 | executeDictionary[@"executionId"] = [NSNumber numberWithLong: executionId]; 43 | executeDictionary[@"returnCode"] = [NSNumber numberWithInt: returnCode]; 44 | 45 | NSMutableDictionary *eventDictionary = [[NSMutableDictionary alloc] init]; 46 | eventDictionary[EVENT_EXECUTE] = executeDictionary; 47 | 48 | _eventSink(eventDictionary); 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /ios/Classes/FlutterFFmpegPlugin.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019-2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | #import 21 | #import 22 | 23 | /** 24 | * Flutter FFmpeg Plugin 25 | */ 26 | @interface FlutterFFmpegPlugin : NSObject 27 | @end 28 | -------------------------------------------------------------------------------- /ios/flutter_ffmpeg.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'flutter_ffmpeg' 3 | s.version = '0.4.2' 4 | s.summary = 'FFmpeg plugin for Flutter.' 5 | s.description = 'FFmpeg plugin based on mobile-ffmpeg for Flutter.' 6 | s.homepage = 'https://github.com/tanersener/flutter-ffmpeg' 7 | 8 | s.author = { 'Taner Sener' => 'tanersener@gmail.com' } 9 | s.license = { :file => '../LICENSE' } 10 | 11 | s.requires_arc = true 12 | s.static_framework = true 13 | 14 | s.source = { :path => '.' } 15 | s.source_files = 'Classes/**/*' 16 | s.public_header_files = 'Classes/**/*.h' 17 | 18 | s.default_subspec = 'https' 19 | 20 | s.dependency 'Flutter' 21 | 22 | s.subspec 'min' do |ss| 23 | ss.source_files = 'Classes/**/*' 24 | ss.public_header_files = 'Classes/**/*.h' 25 | 26 | ss.dependency 'mobile-ffmpeg-min', '4.4' 27 | ss.ios.deployment_target = '11.0' 28 | end 29 | 30 | s.subspec 'min-lts' do |ss| 31 | ss.source_files = 'Classes/**/*' 32 | ss.public_header_files = 'Classes/**/*.h' 33 | 34 | ss.dependency 'mobile-ffmpeg-min', '4.4.LTS' 35 | ss.ios.deployment_target = '9.3' 36 | end 37 | 38 | s.subspec 'min-gpl' do |ss| 39 | ss.source_files = 'Classes/**/*' 40 | ss.public_header_files = 'Classes/**/*.h' 41 | 42 | ss.dependency 'mobile-ffmpeg-min-gpl', '4.4' 43 | ss.ios.deployment_target = '11.0' 44 | end 45 | 46 | s.subspec 'min-gpl-lts' do |ss| 47 | ss.source_files = 'Classes/**/*' 48 | ss.public_header_files = 'Classes/**/*.h' 49 | 50 | ss.dependency 'mobile-ffmpeg-min-gpl', '4.4.LTS' 51 | ss.ios.deployment_target = '9.3' 52 | end 53 | 54 | s.subspec 'https' do |ss| 55 | ss.source_files = 'Classes/**/*' 56 | ss.public_header_files = 'Classes/**/*.h' 57 | 58 | ss.dependency 'mobile-ffmpeg-https', '4.4' 59 | ss.ios.deployment_target = '11.0' 60 | end 61 | 62 | s.subspec 'https-lts' do |ss| 63 | ss.source_files = 'Classes/**/*' 64 | ss.public_header_files = 'Classes/**/*.h' 65 | 66 | ss.dependency 'mobile-ffmpeg-https', '4.4.LTS' 67 | ss.ios.deployment_target = '9.3' 68 | end 69 | 70 | s.subspec 'https-gpl' do |ss| 71 | ss.source_files = 'Classes/**/*' 72 | ss.public_header_files = 'Classes/**/*.h' 73 | 74 | ss.dependency 'mobile-ffmpeg-https-gpl', '4.4' 75 | ss.ios.deployment_target = '11.0' 76 | end 77 | 78 | s.subspec 'https-gpl-lts' do |ss| 79 | ss.source_files = 'Classes/**/*' 80 | ss.public_header_files = 'Classes/**/*.h' 81 | 82 | ss.dependency 'mobile-ffmpeg-https-gpl', '4.4.LTS' 83 | ss.ios.deployment_target = '9.3' 84 | end 85 | 86 | s.subspec 'audio' do |ss| 87 | ss.source_files = 'Classes/**/*' 88 | ss.public_header_files = 'Classes/**/*.h' 89 | 90 | ss.dependency 'mobile-ffmpeg-audio', '4.4' 91 | ss.ios.deployment_target = '11.0' 92 | end 93 | 94 | s.subspec 'audio-lts' do |ss| 95 | ss.source_files = 'Classes/**/*' 96 | ss.public_header_files = 'Classes/**/*.h' 97 | 98 | ss.dependency 'mobile-ffmpeg-audio', '4.4.LTS' 99 | ss.ios.deployment_target = '9.3' 100 | end 101 | 102 | s.subspec 'video' do |ss| 103 | ss.source_files = 'Classes/**/*' 104 | ss.public_header_files = 'Classes/**/*.h' 105 | 106 | ss.dependency 'mobile-ffmpeg-video', '4.4' 107 | ss.ios.deployment_target = '11.0' 108 | end 109 | 110 | s.subspec 'video-lts' do |ss| 111 | ss.source_files = 'Classes/**/*' 112 | ss.public_header_files = 'Classes/**/*.h' 113 | 114 | ss.dependency 'mobile-ffmpeg-video', '4.4.LTS' 115 | ss.ios.deployment_target = '9.3' 116 | end 117 | 118 | s.subspec 'full' do |ss| 119 | ss.source_files = 'Classes/**/*' 120 | ss.public_header_files = 'Classes/**/*.h' 121 | 122 | ss.dependency 'mobile-ffmpeg-full', '4.4' 123 | ss.ios.deployment_target = '11.0' 124 | end 125 | 126 | s.subspec 'full-lts' do |ss| 127 | ss.source_files = 'Classes/**/*' 128 | ss.public_header_files = 'Classes/**/*.h' 129 | 130 | ss.dependency 'mobile-ffmpeg-full', '4.4.LTS' 131 | ss.ios.deployment_target = '9.3' 132 | end 133 | 134 | s.subspec 'full-gpl' do |ss| 135 | ss.source_files = 'Classes/**/*' 136 | ss.public_header_files = 'Classes/**/*.h' 137 | 138 | ss.dependency 'mobile-ffmpeg-full-gpl', '4.4' 139 | ss.ios.deployment_target = '11.0' 140 | end 141 | 142 | s.subspec 'full-gpl-lts' do |ss| 143 | ss.source_files = 'Classes/**/*' 144 | ss.public_header_files = 'Classes/**/*.h' 145 | 146 | ss.dependency 'mobile-ffmpeg-full-gpl', '4.4.LTS' 147 | ss.ios.deployment_target = '9.3' 148 | end 149 | 150 | end 151 | -------------------------------------------------------------------------------- /lib/completed_ffmpeg_execution.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | /// Represents a completed FFmpeg execution. 21 | class CompletedFFmpegExecution { 22 | int executionId; 23 | int returnCode; 24 | 25 | CompletedFFmpegExecution(this.executionId, this.returnCode); 26 | } 27 | -------------------------------------------------------------------------------- /lib/ffmpeg_execution.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | /// Represents an ongoing FFmpeg execution. 21 | class FFmpegExecution { 22 | int executionId; 23 | DateTime startTime; 24 | String command; 25 | 26 | FFmpegExecution( 27 | {required this.command, 28 | required this.executionId, 29 | required this.startTime}); 30 | } 31 | -------------------------------------------------------------------------------- /lib/log.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | class Log { 21 | int executionId; 22 | int level; 23 | String message; 24 | 25 | Log(this.executionId, this.level, this.message); 26 | } 27 | -------------------------------------------------------------------------------- /lib/log_level.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | class LogLevel { 21 | /// 22 | /// This log level is used to specify logs printed to stderr by ffmpeg. 23 | /// Logs that has this level are not filtered and always redirected. 24 | static const int AV_LOG_STDERR = -16; 25 | 26 | /// Print no output. 27 | static const int AV_LOG_QUIET = -8; 28 | 29 | /// Something went really wrong and we will crash now. 30 | static const int AV_LOG_PANIC = 0; 31 | 32 | /// Something went wrong and recovery is not possible. 33 | /// For example, no header was found for a format which depends 34 | /// on headers or an illegal combination of parameters is used. 35 | static const int AV_LOG_FATAL = 8; 36 | 37 | /// Something went wrong and cannot losslessly be recovered. 38 | /// However, not all future data is affected. 39 | static const int AV_LOG_ERROR = 16; 40 | 41 | /// Something somehow does not look correct. This may or may not 42 | /// lead to problems. An example would be the use of '-vstrict -2'. 43 | static const int AV_LOG_WARNING = 24; 44 | 45 | /// int Standard information. 46 | static const int AV_LOG_INFO = 32; 47 | 48 | /// Detailed information. 49 | static const int AV_LOG_VERBOSE = 40; 50 | 51 | /// Stuff which is only useful for libav* developers. 52 | static const int AV_LOG_DEBUG = 48; 53 | 54 | /// Extremely verbose debugging, useful for libav* development. 55 | static const int AV_LOG_TRACE = 56; 56 | 57 | /// Returns log level string from int 58 | static String levelToString(int level) { 59 | switch (level) { 60 | case LogLevel.AV_LOG_TRACE: 61 | return "TRACE"; 62 | case LogLevel.AV_LOG_DEBUG: 63 | return "DEBUG"; 64 | case LogLevel.AV_LOG_VERBOSE: 65 | return "VERBOSE"; 66 | case LogLevel.AV_LOG_INFO: 67 | return "INFO"; 68 | case LogLevel.AV_LOG_WARNING: 69 | return "WARNING"; 70 | case LogLevel.AV_LOG_ERROR: 71 | return "ERROR"; 72 | case LogLevel.AV_LOG_FATAL: 73 | return "FATAL"; 74 | case LogLevel.AV_LOG_PANIC: 75 | return "PANIC"; 76 | case LogLevel.AV_LOG_STDERR: 77 | return "STDERR"; 78 | case LogLevel.AV_LOG_QUIET: 79 | default: 80 | return ""; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/media_information.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | import 'package:flutter_ffmpeg/stream_information.dart'; 21 | 22 | class MediaInformation { 23 | Map? _allProperties; 24 | 25 | /// Creates a new [MediaInformation] instance 26 | MediaInformation(this._allProperties); 27 | 28 | /// Returns all streams 29 | List? getStreams() { 30 | List list = 31 | List.empty(growable: true); 32 | var streamList; 33 | 34 | if (_allProperties == null) { 35 | streamList = List.empty(growable: true); 36 | } else { 37 | streamList = _allProperties!["streams"]; 38 | } 39 | 40 | if (streamList != null) { 41 | streamList.forEach((element) { 42 | list.add(new StreamInformation(element)); 43 | }); 44 | } 45 | 46 | return list; 47 | } 48 | 49 | /// Returns all media properties in a map or null if no media properties are found 50 | Map? getMediaProperties() { 51 | if (_allProperties == null) { 52 | return Map(); 53 | } else { 54 | return _allProperties!["format"]; 55 | } 56 | } 57 | 58 | /// Returns all properties in a map or null if no properties are found 59 | Map getAllProperties() { 60 | return _allProperties!; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/statistics.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | class Statistics { 21 | int executionId; 22 | int videoFrameNumber; 23 | double videoFps; 24 | double videoQuality; 25 | int size; 26 | int time; 27 | double bitrate; 28 | double speed; 29 | 30 | Statistics(this.executionId, this.videoFrameNumber, this.videoFps, 31 | this.videoQuality, this.size, this.time, this.bitrate, this.speed); 32 | } 33 | -------------------------------------------------------------------------------- /lib/stream_information.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Taner Sener 3 | * 4 | * This file is part of FlutterFFmpeg. 5 | * 6 | * FlutterFFmpeg is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * FlutterFFmpeg is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with FlutterFFmpeg. If not, see . 18 | */ 19 | 20 | class StreamInformation { 21 | Map _allProperties; 22 | 23 | /// Creates a new [StreamInformation] instance 24 | StreamInformation(this._allProperties); 25 | 26 | /// Returns all properties in a map or null if no properties are found 27 | Map getAllProperties() { 28 | return _allProperties; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_ffmpeg 2 | description: Flutter plugin to run FFmpeg on mobile platforms. Supports iOS and Android. 3 | version: 0.4.2 4 | homepage: https://github.com/tanersener/flutter-ffmpeg 5 | 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | flutter: ">=1.10.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | dev_dependencies: 15 | path_provider: ^2.0.2 16 | path: ^1.8.0 17 | 18 | flutter: 19 | plugin: 20 | platforms: 21 | android: 22 | package: com.arthenica.flutter.ffmpeg 23 | pluginClass: FlutterFFmpegPlugin 24 | ios: 25 | pluginClass: FlutterFFmpegPlugin 26 | --------------------------------------------------------------------------------