├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── flutter_analysis.yml │ └── stale.yml ├── .gitignore ├── .metadata ├── .pubignore ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── SECURITY.md ├── analysis_options.yaml ├── android ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── AndroidManifest.xml │ └── kotlin │ │ └── ch │ │ └── waio │ │ └── pro_video_editor │ │ ├── ProVideoEditorPlugin.kt │ │ └── src │ │ ├── core │ │ └── constants │ │ │ └── LoggingConstants.kt │ │ └── features │ │ ├── Metadata.kt │ │ ├── ThumbnailGenerator.kt │ │ └── render │ │ ├── RenderVideo.kt │ │ ├── helpers │ │ ├── ApplyAudio.kt │ │ ├── ApplyBitrate.kt │ │ ├── ApplyBlur.kt │ │ ├── ApplyColorMatrix.kt │ │ ├── ApplyCrop.kt │ │ ├── ApplyFlip.kt │ │ ├── ApplyImageLayer.kt │ │ ├── ApplyPlaybackSpeed.kt │ │ ├── ApplyRotation.kt │ │ ├── ApplyScale.kt │ │ └── ApplyTrim.kt │ │ └── utils │ │ ├── RotatedVideoDimensions.kt │ │ └── VideoMimeUtils.kt │ └── test │ └── kotlin │ └── ch │ └── waio │ └── pro_video_editor │ └── ProVideoEditorPluginTest.kt ├── assets ├── logo.jpg ├── preview │ ├── Crop-Rotate-Editor.jpg │ ├── Emoji-Editor.jpg │ ├── Filter-Editor.jpg │ ├── Grounded-Editor.jpg │ ├── Main-Editor.jpg │ ├── Paint-Editor-Grounded.jpg │ ├── Paint-Editor.jpg │ └── Tune-Editor.jpg ├── showcase.jpg └── showcase1.jpg ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ ├── ch │ │ │ │ │ └── waio │ │ │ │ │ │ └── pro_video_editor_example │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── pro_video_editor_example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── 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-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle.kts │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle.kts ├── assets │ ├── demo.mov │ └── demo.mp4 ├── integration_test │ ├── video_metadata_test.dart │ ├── video_render_test.dart │ └── video_thumbnail_test.dart ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ ├── Runner │ │ ├── AppDelegate.swift │ │ ├── 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 │ │ └── Runner-Bridging-Header.h │ └── RunnerTests │ │ └── RunnerTests.swift ├── lib │ ├── core │ │ └── constants │ │ │ ├── example_constants.dart │ │ │ └── example_filters.dart │ ├── features │ │ ├── editor │ │ │ ├── pages │ │ │ │ ├── video_editor_basic_example_page.dart │ │ │ │ └── video_editor_grounded_example_page.dart │ │ │ └── widgets │ │ │ │ ├── demo_build_stickers.dart │ │ │ │ ├── pixel_transparent_painter.dart │ │ │ │ ├── preview_video.dart │ │ │ │ ├── video_initializing_widget.dart │ │ │ │ └── video_progress_alert.dart │ │ ├── metadata │ │ │ └── video_metadata_example_page.dart │ │ ├── render │ │ │ └── video_renderer_page.dart │ │ └── thumbnail │ │ │ └── thumbnail_example_page.dart │ ├── main.dart │ └── shared │ │ ├── utils │ │ ├── bytes_formatter.dart │ │ └── filter_generator │ │ │ ├── filter_addons.dart │ │ │ ├── filter_model.dart │ │ │ └── filter_presets.dart │ │ └── widgets │ │ └── filter_generator.dart ├── linux │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter │ │ ├── CMakeLists.txt │ │ ├── generated_plugin_registrant.cc │ │ ├── generated_plugin_registrant.h │ │ └── generated_plugins.cmake │ └── runner │ │ ├── CMakeLists.txt │ │ ├── main.cc │ │ ├── my_application.cc │ │ └── my_application.h ├── macos │ ├── .gitignore │ ├── Flutter │ │ ├── Flutter-Debug.xcconfig │ │ ├── Flutter-Release.xcconfig │ │ └── GeneratedPluginRegistrant.swift │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── app_icon_1024.png │ │ │ │ ├── app_icon_128.png │ │ │ │ ├── app_icon_16.png │ │ │ │ ├── app_icon_256.png │ │ │ │ ├── app_icon_32.png │ │ │ │ ├── app_icon_512.png │ │ │ │ └── app_icon_64.png │ │ ├── Base.lproj │ │ │ └── MainMenu.xib │ │ ├── Configs │ │ │ ├── AppInfo.xcconfig │ │ │ ├── Debug.xcconfig │ │ │ ├── Release.xcconfig │ │ │ └── Warnings.xcconfig │ │ ├── DebugProfile.entitlements │ │ ├── Info.plist │ │ ├── MainFlutterWindow.swift │ │ └── Release.entitlements │ └── RunnerTests │ │ └── RunnerTests.swift ├── pubspec.yaml ├── test │ └── widget_test.dart ├── web │ ├── favicon.png │ ├── icons │ │ ├── Icon-192.png │ │ ├── Icon-512.png │ │ ├── Icon-maskable-192.png │ │ └── Icon-maskable-512.png │ ├── index.html │ └── manifest.json └── windows │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake │ └── runner │ ├── CMakeLists.txt │ ├── Runner.rc │ ├── flutter_window.cpp │ ├── flutter_window.h │ ├── main.cpp │ ├── resource.h │ ├── resources │ └── app_icon.ico │ ├── runner.exe.manifest │ ├── utils.cpp │ ├── utils.h │ ├── win32_window.cpp │ └── win32_window.h ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── ProVideoEditorPlugin.swift │ └── src │ │ ├── ExportVideo.swift │ │ ├── core │ │ └── constants │ │ │ └── LoggingConstants.swift │ │ └── features │ │ ├── Metadata.swift │ │ ├── ThumbnailGenerator.swift │ │ └── render │ │ ├── RenderVideo.swift │ │ ├── helpers │ │ ├── ApplyAudio.swift │ │ ├── ApplyBitrate.swift │ │ ├── ApplyBlur.swift │ │ ├── ApplyColorMatrix.swift │ │ ├── ApplyCrop.swift │ │ ├── ApplyFlip.swift │ │ ├── ApplyImageLayer.swift │ │ ├── ApplyPlaybackSpeed.swift │ │ ├── ApplyRotation.swift │ │ ├── ApplyScale.swift │ │ └── ApplyTrim.swift │ │ ├── models │ │ └── VideoCompositorConfig.swift │ │ └── utils │ │ ├── VideoCompositor.swift │ │ └── VideoMimeUtils.swift ├── Resources │ └── PrivacyInfo.xcprivacy └── pro_video_editor.podspec ├── lib ├── core │ ├── models │ │ ├── thumbnail │ │ │ ├── key_frames_configs.model.dart │ │ │ ├── thumbnail_base.abstract.dart │ │ │ ├── thumbnail_box_fit.model.dart │ │ │ ├── thumbnail_configs.model.dart │ │ │ └── thumbnail_format.model.dart │ │ └── video │ │ │ ├── editor_video_model.dart │ │ │ ├── export_transform_model.dart │ │ │ ├── progress_model.dart │ │ │ ├── render_video_model.dart │ │ │ └── video_metadata_model.dart │ ├── platform │ │ └── io │ │ │ ├── io_helper.dart │ │ │ └── io_web.dart │ ├── services │ │ └── web │ │ │ ├── web_manager.dart │ │ │ ├── web_meta_data_reader.dart │ │ │ └── web_thumbnail_generator.dart │ └── utils │ │ ├── web_blob_utils.dart │ │ └── web_canvas_utils.dart ├── pro_video_editor.dart ├── pro_video_editor_method_channel.dart ├── pro_video_editor_platform_interface.dart ├── pro_video_editor_web.dart └── shared │ └── utils │ ├── converters.dart │ ├── file_constructor_utils.dart │ └── parser │ ├── double_parser.dart │ ├── int_parser.dart │ └── size_parser.dart ├── linux ├── CMakeLists.txt ├── include │ └── pro_video_editor │ │ └── pro_video_editor_plugin.h ├── pro_video_editor_plugin.cc ├── pro_video_editor_plugin_private.h ├── src │ ├── thumbnail_generator.cc │ ├── thumbnail_generator.h │ ├── video_metadata.cc │ └── video_metadata.h └── test │ └── pro_video_editor_plugin_test.cc ├── macos ├── Classes │ ├── ProVideoEditorPlugin.swift │ └── src │ │ ├── core │ │ └── constants │ │ │ └── LoggingConstants.swift │ │ └── features │ │ ├── Metadata.swift │ │ ├── ThumbnailGenerator.swift │ │ └── render │ │ ├── RenderVideo.swift │ │ ├── helpers │ │ ├── ApplyAudio.swift │ │ ├── ApplyBitrate.swift │ │ ├── ApplyBlur.swift │ │ ├── ApplyColorMatrix.swift │ │ ├── ApplyCrop.swift │ │ ├── ApplyFlip.swift │ │ ├── ApplyImageLayer.swift │ │ ├── ApplyPlaybackSpeed.swift │ │ ├── ApplyRotation.swift │ │ ├── ApplyScale.swift │ │ └── ApplyTrim.swift │ │ ├── models │ │ └── VideoCompositorConfig.swift │ │ └── utils │ │ ├── VideoCompositor.swift │ │ └── VideoMimeUtils.swift ├── Resources │ └── PrivacyInfo.xcprivacy └── pro_video_editor.podspec ├── pubspec.yaml ├── test ├── pro_video_editor_method_channel_test.dart ├── pro_video_editor_method_channel_test.mocks.dart └── shared │ └── utils │ ├── file_constructor_utils_test.dart │ └── parser │ ├── double_parser_test.dart │ ├── int_parser_test.dart │ └── size_parser_test.dart └── windows ├── .gitignore ├── CMakeLists.txt ├── include └── pro_video_editor │ └── pro_video_editor_plugin_c_api.h ├── pro_video_editor_plugin.cpp ├── pro_video_editor_plugin.h ├── pro_video_editor_plugin_c_api.cpp ├── src ├── thumbnail_generator.cpp ├── thumbnail_generator.h ├── video_metadata.cpp └── video_metadata.h └── test └── pro_video_editor_plugin_test.cpp /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [hm21] -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Report a bug to help us improve 3 | title: "[Bug]: " 4 | labels: ["bug"] 5 | body: 6 | - type: input 7 | id: editor-version 8 | attributes: 9 | label: Package Version 10 | description: Version of the package 11 | placeholder: ex. 2.5.2 12 | validations: 13 | required: true 14 | - type: input 15 | id: flutter-version 16 | attributes: 17 | label: Flutter Version 18 | description: Version from flutter 19 | placeholder: "ex. 3.19.5 | Terminal: flutter --version" 20 | validations: 21 | required: true 22 | - type: dropdown 23 | id: platforms 24 | attributes: 25 | label: Platforms 26 | description: Check all that apply 27 | multiple: true 28 | options: 29 | - Android 30 | - iOS 31 | - Web 32 | - Windows 33 | - macOS 34 | - Linux 35 | validations: 36 | required: true 37 | - type: textarea 38 | id: what-happened 39 | attributes: 40 | label: "How to reproduce?" 41 | description: "How to reproduce the issue?" 42 | placeholder: "ex. I found a bug when I press the button X." 43 | validations: 44 | required: true 45 | - type: textarea 46 | id: log 47 | attributes: 48 | label: Logs (optional) 49 | description: "Error log" 50 | placeholder: ex. flutter run --verbose 51 | render: sh 52 | validations: 53 | required: false 54 | - type: textarea 55 | id: example-code 56 | attributes: 57 | label: "Example code (optional)" 58 | description: "If you can, please provide a example code to reproduce the problem." 59 | placeholder: "ex. screenshot.capture(pixelRatio: _pixelRatio)" 60 | render: Dart 61 | validations: 62 | required: false 63 | - type: input 64 | id: device-model 65 | attributes: 66 | label: Device Model (optional) 67 | description: "Device Model (ex. iPhone 12(iOS 14), Galaxy S21(Android 11))" 68 | placeholder: ex. iPhone 12 (iOS 14) 69 | validations: 70 | required: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Question 4 | url: https://github.com/hm21/pro_image_editor/discussions/new?category=q-a 5 | about: The Issues tab is for reporting bugs or suggesting features. For general questions, support, or guidance, the Discussions tab is the right place to go. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "Feature request" 2 | description: "Suggest an idea or enhancement for this project" 3 | title: "[Feature request] " 4 | labels: ["enhancement"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this feature request! 10 | - type: dropdown 11 | id: platforms 12 | attributes: 13 | label: Platforms 14 | description: Check all that apply 15 | multiple: true 16 | options: 17 | - Android 18 | - iOS 19 | - Web 20 | - Windows 21 | - macOS 22 | - Linux 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: description 27 | attributes: 28 | label: Description 29 | description: "Describe the feature you want to be added" 30 | placeholder: "ex. I want to add a new method to get the thumbnail of the asset." 31 | validations: 32 | required: true 33 | - type: textarea 34 | id: why 35 | attributes: 36 | label: Why 37 | description: "Why do you want this feature to be added? What's the use case?" 38 | placeholder: "Thumbnails are very important for picture display and must be available. [some design images]" 39 | validations: 40 | required: false 41 | - type: markdown 42 | attributes: 43 | value: | 44 | Add as many reasons as possible to prioritize the request. -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## PR Checklist (Required) 2 | Please ensure your PR meets the following requirements: 3 | 4 | - [ ] The commit message follows our guidelines: https://github.com/hm21/pro_image_editor/blob/stable/CONTRIBUTING.md#style-guides 5 | - [ ] I have run `dart format .` in the terminal and committed any changes. 6 | - [ ] I have run `dart analyze .` in the terminal and addressed any issues. 7 | - [ ] I have run `flutter test` in the terminal and addressed any issues. 8 | - [ ] I have pulled from the stable branch before committing. 9 | - [ ] I have updated the `CHANGELOG.md` file with my changes (For the version, you can use X.X.X, I will later bump it after it's merged). 10 | - [ ] The PR title follows the conventional commit style (e.g., `feat(paint-editor): add new triangle shape`). 11 | - [ ] If this PR introduces breaking changes, I have verified that there is no way to implement the changes without them. 12 | 13 | ## PR Checklist (Optional) 14 | - [ ] Tests have been added for the changes. 15 | - [ ] Documentation has been added/updated. 16 | 17 | ## PR Type 18 | Please indicate the types of changes this PR introduces by checking the relevant boxes: 19 | 20 | - [ ] Bugfix 21 | - [ ] Feature 22 | - [ ] Performance 23 | - [ ] Refactor (no functional or API changes) 24 | - [ ] Code style updates (e.g., formatting, local variables) 25 | - [ ] Documentation updates 26 | - [ ] CI-related changes 27 | - [ ] Other... Please describe: 28 | 29 | ## Current Behavior 30 | 34 | 35 | Issue Number: N/A 36 | 37 | ## New Behavior 38 | 39 | 40 | ## Breaking Changes? 41 | Does this PR introduce a breaking change? 42 | 43 | - [ ] Yes 44 | - [ ] No 45 | 46 | 47 | 48 | ## Additional Information 49 | -------------------------------------------------------------------------------- /.github/workflows/flutter_analysis.yml: -------------------------------------------------------------------------------- 1 | name: Flutter Analysis 2 | on: 3 | push: 4 | branches: 5 | - stable 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | jobs: 10 | package-analysis: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Clone repository 15 | uses: actions/checkout@v4 16 | 17 | - name: Set up Flutter 18 | uses: subosito/flutter-action@v2 19 | with: 20 | channel: stable 21 | flutter-version: 3.32.0 22 | 23 | - name: Install dependencies 24 | run: flutter pub get 25 | 26 | - name: Check formatting 27 | run: dart format --output=none --set-exit-if-changed . 28 | 29 | - name: Analyze code 30 | run: flutter analyze . 31 | 32 | - name: Run tests 33 | run: flutter test -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close inactive issues 2 | on: 3 | schedule: 4 | - cron: "0 0 * * *" 5 | 6 | jobs: 7 | close-issues: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/stale@v5 14 | with: 15 | days-before-issue-stale: 3 16 | days-before-issue-close: 5 17 | stale-issue-label: "stale" 18 | exempt-issue-labels: "in-progress" 19 | stale-issue-message: "This issue is stale because it has been open for 3 days with no activity." 20 | close-issue-message: "This issue was closed because it has been inactive for 5 days since being marked as stale." 21 | days-before-pr-stale: -1 22 | days-before-pr-close: -1 23 | repo-token: ${{ secrets.GITHUB_TOKEN }} 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | .vs/ 21 | .vscode/ 22 | 23 | # The .vscode folder contains launch configuration and tasks you configure in 24 | # VS Code which you may wish to be included in version control, so this line 25 | # is commented out by default. 26 | #.vscode/ 27 | 28 | # Flutter/Dart/Pub related 29 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 30 | /pubspec.lock 31 | **/doc/api/ 32 | .dart_tool/ 33 | .flutter-plugins 34 | .flutter-plugins-dependencies 35 | build/ 36 | /example/pubspec.lock -------------------------------------------------------------------------------- /.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: "c23637390482d4cf9598c3ce3f2be31aa7332daf" 8 | channel: "stable" 9 | 10 | project_type: plugin 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 17 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 18 | - platform: android 19 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 20 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 21 | - platform: ios 22 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 23 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 24 | - platform: linux 25 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 26 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 27 | - platform: macos 28 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 29 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 30 | - platform: web 31 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 32 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 33 | - platform: windows 34 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 35 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /.pubignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vs/ 3 | .vscode/ 4 | .dart_tool/ 5 | .idea/ 6 | .vs/ 7 | .vscode/ 8 | build/ -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "bbox", 4 | "Bezier", 5 | "Cooldown", 6 | "diegotori", 7 | "dismissibility", 8 | "ellipsize", 9 | "endtemplate", 10 | "fdtbl", 11 | "LTRBR", 12 | "overriden", 13 | "Rgba", 14 | "selectability", 15 | "trackpad", 16 | "unawaited", 17 | "uvac", 18 | "uvdc" 19 | ], 20 | "diffEditor.experimental.showEmptyDecorations": true, 21 | "diffEditor.hideUnchangedRegions.enabled": false, 22 | "editor.minimap.showMarkSectionHeaders": true 23 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.6 2 | - **FIX**(iOS, macOS): Fixed rotation transforms not properly swapping render dimensions for 90°/270° rotations, resolving squeezed video output with black bars 3 | 4 | ## 0.1.5 5 | - **FIX**(window, linux, iOS, macOS): Correct bitrate extraction from metadata. 6 | - **FIX**(android): Remove unsupported WebM output format; Android only supports MP4 generation. 7 | - **TEST**: Add integration tests for all core functionalities. 8 | 9 | ## 0.1.4 10 | - **FIX**(iOS, macOS): Fixed AVFoundation -11841 "Operation Stopped" errors when exporting videos selected via image_picker package 11 | - **FIX**(iOS, macOS): Fixed video rotation metadata not being properly handled, causing incorrect orientation in exported videos 12 | - **FIX**(iOS, macOS): Fixed random video loading failures from image_picker package due to complex transform metadata 13 | - **FIX**(iOS, macOS): Enhanced video composition pipeline to properly process iPhone camera orientation transforms 14 | 15 | ## 0.1.3 16 | - **FIX**(iOS, macOS): Resolved multiple issue where, in some Swift versions, a trailing comma in the constructor caused an error. 17 | 18 | ## 0.1.2 19 | - **FIX**(iOS, macOS): Resolved an issue where, in some Swift versions, a trailing comma in the constructor caused an error. 20 | 21 | ## 0.1.1 22 | - **DOCS**: Updated README with new examples and images. 23 | 24 | ## 0.1.0* 25 | - **FEAT**(iOS): Added render functions for iOS. 26 | - **FEAT**(macOS): Added render functions for macOS. 27 | 28 | ## 0.0.14 29 | - **FIX**: Resolve various crop and rotation issues. 30 | - **REFACTOR**(android): Improve code quality. 31 | - **FEAT**(example): Add video-editor example. 32 | 33 | ## 0.0.13 34 | - **FIX**(crop): Resolve issues that crop not working. 35 | 36 | ## 0.0.12 37 | - **FIX**(layer): Fixed incorrect layer scaling caused by misinterpreted video dimensions. 38 | 39 | ## 0.0.11 40 | - **FIX**(rotation): Resolve various issues when video is rotated. 41 | 42 | ## 0.0.10 43 | - **FEAT**(native-code): Remove the ffmpeg package and start implementing native code. 44 | 45 | ## 0.0.9 46 | - **REFACTOR**(encoding): Export encoding models for easier import from main package 47 | 48 | ## 0.0.8 49 | - **FEAT**(audio): Add enable audio parameter 50 | 51 | ## 0.0.7 52 | - **FEAT**(iOS, macOS): Add video generation support for macOS and iOS 53 | 54 | ## 0.0.6 55 | - **FIX**(crop): Ensure crop dimensions are even to avoid libx264 errors 56 | 57 | ## 0.0.5 58 | - **FEAT**: Add support for color 4x5 matrices 59 | 60 | ## 0.0.4 61 | - **FEAT**: Add video parser functions for android 62 | 63 | ## 0.0.3 64 | - **FIX**: Resolve thumbnail generation on web. 65 | 66 | ## 0.0.2 67 | - **FEAT**: Add `getVideoInformation` and `createVideoThumbnails` for all platforms. 68 | 69 | ## 0.0.1 70 | 71 | - **CHORE**: Initial release. 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2025, Alex Frei 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | We actively support and release security updates for the following versions of `pro_video_editor`: 6 | 7 | | Version | Supported | 8 | |-----------------|--------------------| 9 | | Latest release | ✅ | 10 | | Older versions | ❌ | 11 | 12 | Please update to the latest version to ensure you receive the latest security patches and updates. 13 | 14 | ## Reporting a Vulnerability 15 | 16 | If you discover a security vulnerability in `pro_video_editor`, please report it responsibly to help us maintain a safe and secure project. 17 | 18 | ### Steps to Report: 19 | 1. **Do not open a public issue.** 20 | Instead, email us privately at **[alex.frei@hotmail.ch]** to report the issue. 21 | 22 | 2. **Include the following details in your report:** 23 | - A detailed description of the vulnerability. 24 | - Steps to reproduce the issue. 25 | - The potential impact of the vulnerability. 26 | - Any suggested fixes or mitigations. 27 | 28 | 3. **Response Time:** 29 | We aim to acknowledge security reports within **48 hours** and provide a resolution or mitigation plan within **7 days**, depending on the severity and complexity of the issue. 30 | 31 | 4. **Confidentiality:** 32 | We request you keep the vulnerability confidential until we have released a fix and informed our users. 33 | 34 | ## Thank You 35 | 36 | We appreciate your efforts to responsibly disclose security issues. Your contribution helps make `pro_video_editor` safer for everyone. 37 | 38 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | analyzer: 4 | language: 5 | strict-raw-types: true 6 | 7 | linter: 8 | rules: 9 | - no_logic_in_create_state 10 | - avoid_implementing_value_types 11 | - avoid_type_to_string 12 | - avoid_unnecessary_containers 13 | - do_not_use_environment 14 | - missing_whitespace_between_adjacent_strings 15 | - no_runtimeType_toString 16 | - prefer_const_constructors_in_immutables 17 | - prefer_const_declarations 18 | - prefer_const_literals_to_create_immutables 19 | - prefer_is_not_operator 20 | - prefer_iterable_whereType 21 | - sized_box_for_whitespace 22 | - use_full_hex_values_for_flutter_colors 23 | - use_is_even_rather_than_modulo 24 | - use_key_in_widget_constructors 25 | - unnecessary_library_name 26 | - public_member_api_docs 27 | - annotate_overrides 28 | - avoid_empty_else 29 | - avoid_function_literals_in_foreach_calls 30 | - avoid_init_to_null 31 | - avoid_null_checks_in_equality_operators 32 | - avoid_relative_lib_imports 33 | - avoid_renaming_method_parameters 34 | - avoid_return_types_on_setters 35 | - avoid_types_as_parameter_names 36 | - avoid_unused_constructor_parameters 37 | - await_only_futures 38 | - camel_case_types 39 | - cancel_subscriptions 40 | - cascade_invocations 41 | - constant_identifier_names 42 | - control_flow_in_finally 43 | - directives_ordering 44 | - empty_catches 45 | - empty_constructor_bodies 46 | - empty_statements 47 | - hash_and_equals 48 | - implementation_imports 49 | - collection_methods_unrelated_type 50 | - library_names 51 | - library_prefixes 52 | - lines_longer_than_80_chars 53 | - no_adjacent_strings_in_list 54 | - no_duplicate_case_values 55 | - non_constant_identifier_names 56 | - null_closures 57 | - only_throw_errors 58 | - overridden_fields 59 | - package_names 60 | - package_prefixed_library_names 61 | - prefer_adjacent_string_concatenation 62 | - prefer_collection_literals 63 | - prefer_conditional_assignment 64 | - prefer_const_constructors 65 | - prefer_contains 66 | - prefer_final_fields 67 | - prefer_initializing_formals 68 | - prefer_interpolation_to_compose_strings 69 | - prefer_is_empty 70 | - prefer_is_not_empty 71 | - prefer_single_quotes 72 | - prefer_typing_uninitialized_variables 73 | - recursive_getters 74 | - slash_for_doc_comments 75 | - sort_constructors_first 76 | - test_types_in_equals 77 | - throw_in_finally 78 | - type_init_formals 79 | - unawaited_futures 80 | - unnecessary_brace_in_string_interps 81 | - unnecessary_const 82 | - unnecessary_getters_setters 83 | - unnecessary_lambdas 84 | - unnecessary_new 85 | - unnecessary_null_aware_assignments 86 | - unnecessary_statements 87 | - unnecessary_this 88 | - unrelated_type_equality_checks 89 | - use_rethrow_when_possible 90 | - valid_regexps -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .cxx 10 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group = "ch.waio.pro_video_editor" 2 | version = "1.0-SNAPSHOT" 3 | 4 | buildscript { 5 | ext.kotlin_version = "1.8.22" 6 | repositories { 7 | google() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | classpath("com.android.tools.build:gradle:8.7.0") 13 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | } 22 | } 23 | 24 | apply plugin: "com.android.library" 25 | apply plugin: "kotlin-android" 26 | 27 | android { 28 | namespace = "ch.waio.pro_video_editor" 29 | 30 | compileSdk = 35 31 | 32 | compileOptions { 33 | sourceCompatibility = JavaVersion.VERSION_11 34 | targetCompatibility = JavaVersion.VERSION_11 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = JavaVersion.VERSION_11 39 | } 40 | 41 | sourceSets { 42 | main.java.srcDirs += "src/main/kotlin" 43 | test.java.srcDirs += "src/test/kotlin" 44 | } 45 | 46 | defaultConfig { 47 | minSdk = 24 48 | } 49 | 50 | dependencies { 51 | testImplementation("org.jetbrains.kotlin:kotlin-test") 52 | testImplementation("org.mockito:mockito-core:5.0.0") 53 | 54 | // Media3 dependencies 55 | implementation "androidx.media3:media3-common:1.4.1" 56 | implementation("androidx.media3:media3-transformer:1.4.1") 57 | implementation("androidx.media3:media3-effect:1.4.1") 58 | implementation("androidx.media3:media3-muxer:1.4.1") 59 | 60 | // Coroutines 61 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") 62 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") 63 | } 64 | 65 | testOptions { 66 | unitTests.all { 67 | useJUnitPlatform() 68 | 69 | testLogging { 70 | events "passed", "skipped", "failed", "standardOut", "standardError" 71 | outputs.upToDateWhen {false} 72 | showStandardStreams = true 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'pro_video_editor' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/kotlin/ch/waio/pro_video_editor/src/core/constants/LoggingConstants.kt: -------------------------------------------------------------------------------- 1 | const val PACKAGE_TAG = "ProVideoEditor" 2 | const val RENDER_TAG = "$PACKAGE_TAG-Renderer" 3 | const val THUMBNAIL_TAG = "$PACKAGE_TAG-Thumbnail" 4 | -------------------------------------------------------------------------------- /android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyAudio.kt: -------------------------------------------------------------------------------- 1 | import android.util.Log 2 | import androidx.media3.common.util.UnstableApi 3 | import androidx.media3.transformer.EditedMediaItem 4 | 5 | 6 | @UnstableApi 7 | fun applyAudio( 8 | editedMediaItemBuilder: EditedMediaItem.Builder, 9 | enableAudio: Boolean? 10 | ) { 11 | // Remove Audio 12 | if (enableAudio == false) { 13 | Log.d(RENDER_TAG, "Removing audio from video") 14 | editedMediaItemBuilder.setRemoveAudio(true) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyBitrate.kt: -------------------------------------------------------------------------------- 1 | import android.media.MediaCodecInfo 2 | import android.media.MediaCodecList 3 | import android.util.Log 4 | import androidx.media3.common.util.UnstableApi 5 | import androidx.media3.transformer.DefaultEncoderFactory 6 | import androidx.media3.transformer.VideoEncoderSettings 7 | 8 | 9 | @UnstableApi 10 | fun applyBitrate( 11 | encoderFactoryBuilder: DefaultEncoderFactory.Builder, 12 | mimeType: String?, 13 | bitrate: Int? 14 | ) { 15 | if (bitrate == null) return 16 | Log.d(RENDER_TAG, "Requested Bitrate: $bitrate") 17 | 18 | val codecInfo = MediaCodecList(MediaCodecList.ALL_CODECS) 19 | .codecInfos 20 | .firstOrNull { it.isEncoder && it.supportedTypes.contains(mimeType) } 21 | 22 | if (codecInfo == null) { 23 | Log.e(RENDER_TAG, "No encoder found for $mimeType") 24 | return 25 | } 26 | 27 | val capabilities = codecInfo.getCapabilitiesForType(mimeType) 28 | val bitrateRange = capabilities.videoCapabilities.bitrateRange 29 | val supportsCBR = capabilities.encoderCapabilities 30 | .isBitrateModeSupported(MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR) 31 | 32 | if (!bitrateRange.contains(bitrate)) { 33 | Log.e(RENDER_TAG, "Bitrate $bitrate not in supported range: $bitrateRange") 34 | return 35 | } 36 | 37 | val bitrateMode = if (supportsCBR) { 38 | Log.d(RENDER_TAG, "CBR supported, applying CBR mode") 39 | MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR 40 | } else { 41 | Log.w(RENDER_TAG, "CBR not supported, falling back to VBR") 42 | MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR 43 | } 44 | 45 | val builder = VideoEncoderSettings.Builder() 46 | .setBitrateMode(bitrateMode) 47 | .setBitrate(bitrate) 48 | 49 | encoderFactoryBuilder.setRequestedVideoEncoderSettings(builder.build()) 50 | } 51 | -------------------------------------------------------------------------------- /android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyBlur.kt: -------------------------------------------------------------------------------- 1 | import android.util.Log 2 | import androidx.media3.common.Effect 3 | import androidx.media3.common.util.UnstableApi 4 | import androidx.media3.effect.GaussianBlur 5 | 6 | @UnstableApi 7 | fun applyBlur(videoEffects: MutableList, blur: Double?) { 8 | if (blur == null || blur <= 0.0) return; 9 | 10 | Log.d(RENDER_TAG, "Applying Blur: Sigma: $blur") 11 | 12 | val blurEffect = GaussianBlur(blur.toFloat() * 2.5f) 13 | videoEffects += blurEffect 14 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyColorMatrix.kt: -------------------------------------------------------------------------------- 1 | import android.util.Log 2 | import androidx.media3.common.Effect 3 | import androidx.media3.common.util.UnstableApi 4 | import androidx.media3.effect.SingleColorLut 5 | 6 | @UnstableApi 7 | fun applyColorMatrix( 8 | videoEffects: MutableList, 9 | colorMatrixList: List> 10 | ) { 11 | if (colorMatrixList.isEmpty()) return 12 | 13 | val combinedMatrix = combineColorMatrices(colorMatrixList) 14 | if (combinedMatrix.size == 20) { 15 | // Should be the best lutSize for that case. 16 | val lutSize = 33 17 | val lutData = generateLutFromColorMatrix(combinedMatrix, lutSize) 18 | val singleColorLut = SingleColorLut.createFromCube(lutData) 19 | videoEffects += singleColorLut 20 | } else { 21 | Log.w(RENDER_TAG, "Color matrix must be 4x5 (20 elements), skipping LUT.") 22 | } 23 | } 24 | 25 | // Function to generate 3D LUT data from a 4x5 color matrix 26 | private fun generateLutFromColorMatrix(matrix: List, size: Int): Array> { 27 | val lut = Array(size) { Array(size) { IntArray(size) } } 28 | for (r in 0 until size) { 29 | for (g in 0 until size) { 30 | for (b in 0 until size) { 31 | val rf = r.toDouble() / (size - 1) 32 | val gf = g.toDouble() / (size - 1) 33 | val bf = b.toDouble() / (size - 1) 34 | 35 | val rr = 36 | (matrix[0] * rf + matrix[1] * gf + matrix[2] * bf + matrix[3]) + (matrix[4] / 255.0) 37 | val gg = 38 | (matrix[5] * rf + matrix[6] * gf + matrix[7] * bf + matrix[8]) + (matrix[9] / 255.0) 39 | val bb = 40 | (matrix[10] * rf + matrix[11] * gf + matrix[12] * bf + matrix[13]) + (matrix[14] / 255.0) 41 | 42 | val rInt = (rr.coerceIn(0.0, 1.0) * 255).toInt() 43 | val gInt = (gg.coerceIn(0.0, 1.0) * 255).toInt() 44 | val bInt = (bb.coerceIn(0.0, 1.0) * 255).toInt() 45 | 46 | // Combine RGB into a single ARGB integer 47 | lut[r][g][b] = (0xFF shl 24) or (rInt shl 16) or (gInt shl 8) or bInt 48 | } 49 | } 50 | } 51 | return lut 52 | } 53 | 54 | private fun multiplyColorMatrices(m1: List, m2: List): List { 55 | val result = MutableList(20) { 0.0 } 56 | for (i in 0..3) { 57 | for (j in 0..4) { 58 | result[i * 5 + j] = 59 | m1[i * 5 + 0] * m2[0 + j] + 60 | m1[i * 5 + 1] * m2[5 + j] + 61 | m1[i * 5 + 2] * m2[10 + j] + 62 | m1[i * 5 + 3] * m2[15 + j] + 63 | if (j == 4) m1[i * 5 + 4] else 0.0 64 | } 65 | } 66 | return result 67 | } 68 | 69 | private fun combineColorMatrices(matrices: List>): List { 70 | if (matrices.isEmpty()) return listOf() 71 | var result = matrices[0] 72 | for (i in 1 until matrices.size) { 73 | result = multiplyColorMatrices(matrices[i], result) 74 | } 75 | return result 76 | } 77 | -------------------------------------------------------------------------------- /android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyFlip.kt: -------------------------------------------------------------------------------- 1 | import android.util.Log 2 | import androidx.media3.common.Effect 3 | import androidx.media3.common.util.UnstableApi 4 | import androidx.media3.effect.ScaleAndRotateTransformation 5 | 6 | @UnstableApi 7 | fun applyFlip(videoEffects: MutableList, flipX: Boolean, flipY: Boolean) { 8 | if (!flipX && !flipY) return 9 | 10 | Log.d(RENDER_TAG, "Applying flip: flipX: $flipX, flipY: $flipY") 11 | videoEffects += ScaleAndRotateTransformation.Builder() 12 | .setScale(if (flipX) -1f else 1f, if (flipY) -1f else 1f) 13 | .build() 14 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyImageLayer.kt: -------------------------------------------------------------------------------- 1 | import android.graphics.Bitmap 2 | import android.graphics.BitmapFactory 3 | import android.util.Log 4 | import androidx.media3.common.Effect 5 | import androidx.media3.common.util.UnstableApi 6 | import androidx.media3.effect.BitmapOverlay 7 | import androidx.media3.effect.OverlayEffect 8 | import ch.waio.pro_video_editor.src.features.render.utils.getRotatedVideoDimensions 9 | import com.google.common.collect.ImmutableList 10 | import java.io.File 11 | 12 | 13 | @UnstableApi 14 | fun applyImageLayer( 15 | videoEffects: MutableList, 16 | inputFile: File, 17 | imageBytes: ByteArray?, 18 | rotationDegrees: Float, 19 | cropWidth: Int?, 20 | cropHeight: Int?, 21 | scaleX: Float?, 22 | scaleY: Float?, 23 | ) { 24 | if (imageBytes == null) return; 25 | 26 | var (videoWidth, videoHeight, videoRotation) = getRotatedVideoDimensions( 27 | inputFile, 28 | rotationDegrees 29 | ) 30 | 31 | var isRotated90Deg = videoRotation == 90 || videoRotation == 270; 32 | if (cropWidth != null) { 33 | if (isRotated90Deg) { 34 | videoHeight = cropWidth; 35 | } else { 36 | videoWidth = cropWidth; 37 | } 38 | } 39 | if (cropHeight != null) { 40 | if (isRotated90Deg) { 41 | videoWidth = cropHeight; 42 | } else { 43 | videoHeight = cropHeight; 44 | } 45 | } 46 | 47 | if (scaleX != null) videoWidth = (videoWidth * scaleX).toInt() 48 | if (scaleY != null) videoHeight = (videoHeight * scaleY).toInt() 49 | 50 | Log.d(RENDER_TAG, "Applying Image-Layer: Size $videoWidth x $videoHeight") 51 | 52 | val overlayBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) 53 | val scaledOverlay = 54 | Bitmap.createScaledBitmap(overlayBitmap, videoWidth, videoHeight, true) 55 | 56 | val bitmapOverlay = BitmapOverlay.createStaticBitmapOverlay(scaledOverlay) 57 | val overlayEffect = OverlayEffect(ImmutableList.of(bitmapOverlay)) 58 | 59 | videoEffects += overlayEffect 60 | } 61 | 62 | -------------------------------------------------------------------------------- /android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyPlaybackSpeed.kt: -------------------------------------------------------------------------------- 1 | import android.util.Log 2 | import androidx.media3.common.Effect 3 | import androidx.media3.common.audio.AudioProcessor 4 | import androidx.media3.common.audio.SonicAudioProcessor 5 | import androidx.media3.common.util.UnstableApi 6 | import androidx.media3.effect.SpeedChangeEffect 7 | 8 | @UnstableApi 9 | fun applyPlaybackSpeed( 10 | videoEffects: MutableList, 11 | audioEffects: MutableList, 12 | playbackSpeed: Float? 13 | ) { 14 | if (playbackSpeed == null || playbackSpeed <= 0f) return; 15 | 16 | Log.d(RENDER_TAG, "Applying playback speed: $playbackSpeed×") 17 | videoEffects += SpeedChangeEffect(playbackSpeed) 18 | 19 | val audio = SonicAudioProcessor() 20 | audio.setSpeed(playbackSpeed) 21 | audioEffects += audio 22 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyRotation.kt: -------------------------------------------------------------------------------- 1 | import android.util.Log 2 | import androidx.media3.common.Effect 3 | import androidx.media3.common.util.UnstableApi 4 | import androidx.media3.effect.ScaleAndRotateTransformation 5 | 6 | @UnstableApi 7 | fun applyRotation(videoEffects: MutableList, rotationDegrees: Float) { 8 | if (rotationDegrees % 360f == 0f) return; 9 | 10 | Log.d(RENDER_TAG, "Applying rotation: $rotationDegrees degrees") 11 | videoEffects += ScaleAndRotateTransformation.Builder() 12 | .setRotationDegrees(rotationDegrees) 13 | .build() 14 | } 15 | -------------------------------------------------------------------------------- /android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyScale.kt: -------------------------------------------------------------------------------- 1 | import android.util.Log 2 | import androidx.media3.common.Effect 3 | import androidx.media3.common.util.UnstableApi 4 | import androidx.media3.effect.ScaleAndRotateTransformation 5 | 6 | @UnstableApi 7 | fun applyScale( 8 | videoEffects: MutableList, scaleX: Float?, scaleY: Float?, 9 | ) { 10 | if (scaleX == null && scaleY == null) return; 11 | 12 | Log.d(RENDER_TAG, "Applying scale: scaleX: $scaleX, scaleY: $scaleY") 13 | videoEffects += ScaleAndRotateTransformation.Builder() 14 | .setScale(scaleX ?: 1f, scaleY ?: 1f) 15 | .build() 16 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyTrim.kt: -------------------------------------------------------------------------------- 1 | import android.util.Log 2 | import androidx.media3.common.C 3 | import androidx.media3.common.MediaItem 4 | import androidx.media3.common.util.UnstableApi 5 | 6 | @UnstableApi 7 | fun applyTrim(mediaItemBuilder: MediaItem.Builder, startUs: Long?, endUs: Long?) { 8 | if (startUs == null && endUs == null) return 9 | 10 | val startMs = (startUs ?: 0L) / 1000 11 | val endMs = endUs?.div(1000) ?: C.TIME_END_OF_SOURCE 12 | Log.d(RENDER_TAG, "Applying trim: start=$startMs ms, end=$endMs ms") 13 | 14 | val clippingConfig = MediaItem.ClippingConfiguration.Builder() 15 | .setStartPositionMs(startMs) 16 | .setEndPositionMs(endMs) 17 | .build() 18 | 19 | mediaItemBuilder.setClippingConfiguration(clippingConfig) 20 | } 21 | -------------------------------------------------------------------------------- /android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/utils/RotatedVideoDimensions.kt: -------------------------------------------------------------------------------- 1 | package ch.waio.pro_video_editor.src.features.render.utils 2 | 3 | import android.media.MediaMetadataRetriever 4 | import java.io.File 5 | 6 | fun getRotatedVideoDimensions( 7 | videoFile: File, 8 | rotationDegrees: Float 9 | ): Triple { 10 | val retriever = MediaMetadataRetriever() 11 | return try { 12 | retriever.setDataSource(videoFile.absolutePath) 13 | val widthRaw = 14 | retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH) 15 | ?.toIntOrNull() ?: 0 16 | val heightRaw = 17 | retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT) 18 | ?.toIntOrNull() ?: 0 19 | val rotation = 20 | retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION) 21 | ?.toIntOrNull() ?: 0 22 | 23 | val normalizedRotation = (rotation + rotationDegrees.toInt()) % 360 24 | val (width, height) = if (normalizedRotation == 90 || normalizedRotation == 270) { 25 | heightRaw to widthRaw 26 | } else { 27 | widthRaw to heightRaw 28 | } 29 | 30 | Triple(width, height, normalizedRotation) 31 | } catch (e: Exception) { 32 | Triple(0, 0, 0) 33 | } finally { 34 | retriever.release() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/utils/VideoMimeUtils.kt: -------------------------------------------------------------------------------- 1 | import androidx.media3.common.MimeTypes 2 | import androidx.media3.common.util.UnstableApi 3 | 4 | @UnstableApi 5 | fun mapFormatToMimeType(format: String): String { 6 | return when (format.lowercase()) { 7 | "mp4" -> MimeTypes.VIDEO_H264 // Codec for MP4 8 | // "webm" -> MimeTypes.VIDEO_VP9 // Codec for WebM 9 | "h264" -> MimeTypes.VIDEO_H264 10 | "h265", "hevc" -> MimeTypes.VIDEO_H265 11 | "av1" -> MimeTypes.VIDEO_AV1 12 | else -> MimeTypes.VIDEO_MP4 // fallback default 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /android/src/test/kotlin/ch/waio/pro_video_editor/ProVideoEditorPluginTest.kt: -------------------------------------------------------------------------------- 1 | package ch.waio.pro_video_editor 2 | 3 | import io.flutter.plugin.common.MethodCall 4 | import io.flutter.plugin.common.MethodChannel 5 | import kotlin.test.Test 6 | import org.mockito.Mockito 7 | 8 | /* 9 | * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation. 10 | * 11 | * Once you have built the plugin's example app, you can run these tests from the command 12 | * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or 13 | * you can run them directly from IDEs that support JUnit such as Android Studio. 14 | */ 15 | 16 | internal class ProVideoEditorPluginTest { 17 | @Test 18 | fun onMethodCall_getPlatformVersion_returnsExpectedValue() { 19 | val plugin = ProVideoEditorPlugin() 20 | 21 | val call = MethodCall("getPlatformVersion", null) 22 | val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java) 23 | plugin.onMethodCall(call, mockResult) 24 | 25 | Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /assets/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/assets/logo.jpg -------------------------------------------------------------------------------- /assets/preview/Crop-Rotate-Editor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/assets/preview/Crop-Rotate-Editor.jpg -------------------------------------------------------------------------------- /assets/preview/Emoji-Editor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/assets/preview/Emoji-Editor.jpg -------------------------------------------------------------------------------- /assets/preview/Filter-Editor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/assets/preview/Filter-Editor.jpg -------------------------------------------------------------------------------- /assets/preview/Grounded-Editor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/assets/preview/Grounded-Editor.jpg -------------------------------------------------------------------------------- /assets/preview/Main-Editor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/assets/preview/Main-Editor.jpg -------------------------------------------------------------------------------- /assets/preview/Paint-Editor-Grounded.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/assets/preview/Paint-Editor-Grounded.jpg -------------------------------------------------------------------------------- /assets/preview/Paint-Editor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/assets/preview/Paint-Editor.jpg -------------------------------------------------------------------------------- /assets/preview/Tune-Editor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/assets/preview/Tune-Editor.jpg -------------------------------------------------------------------------------- /assets/showcase.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/assets/showcase.jpg -------------------------------------------------------------------------------- /assets/showcase1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/assets/showcase1.jpg -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .pub-cache/ 33 | .pub/ 34 | /build/ 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Android Studio will place build artifacts here 43 | /android/app/debug 44 | /android/app/profile 45 | /android/app/release 46 | -------------------------------------------------------------------------------- /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: "c23637390482d4cf9598c3ce3f2be31aa7332daf" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 17 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 18 | - platform: android 19 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 20 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 21 | - platform: ios 22 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 23 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 24 | - platform: linux 25 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 26 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 27 | - platform: macos 28 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 29 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 30 | - platform: web 31 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 32 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 33 | - platform: windows 34 | create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 35 | base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # pro_video_editor_example 2 | 3 | Demonstrates how to use the pro_video_editor plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) 13 | 14 | For help getting started with Flutter development, view the 15 | [online documentation](https://docs.flutter.dev/), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | analyzer: 4 | language: 5 | strict-raw-types: true 6 | 7 | linter: 8 | rules: 9 | - no_logic_in_create_state 10 | - avoid_implementing_value_types 11 | - avoid_type_to_string 12 | - avoid_unnecessary_containers 13 | - do_not_use_environment 14 | - missing_whitespace_between_adjacent_strings 15 | - no_runtimeType_toString 16 | - prefer_const_constructors_in_immutables 17 | - prefer_const_declarations 18 | - prefer_const_literals_to_create_immutables 19 | - prefer_is_not_operator 20 | - prefer_iterable_whereType 21 | - sized_box_for_whitespace 22 | - use_full_hex_values_for_flutter_colors 23 | - use_is_even_rather_than_modulo 24 | - use_key_in_widget_constructors 25 | - unnecessary_library_name 26 | - public_member_api_docs 27 | - annotate_overrides 28 | - avoid_empty_else 29 | - avoid_function_literals_in_foreach_calls 30 | - avoid_init_to_null 31 | - avoid_null_checks_in_equality_operators 32 | - avoid_relative_lib_imports 33 | - avoid_renaming_method_parameters 34 | - avoid_return_types_on_setters 35 | - avoid_types_as_parameter_names 36 | - avoid_unused_constructor_parameters 37 | - await_only_futures 38 | - camel_case_types 39 | - cancel_subscriptions 40 | - cascade_invocations 41 | - constant_identifier_names 42 | - control_flow_in_finally 43 | - directives_ordering 44 | - empty_catches 45 | - empty_constructor_bodies 46 | - empty_statements 47 | - hash_and_equals 48 | - implementation_imports 49 | - collection_methods_unrelated_type 50 | - library_names 51 | - library_prefixes 52 | - lines_longer_than_80_chars 53 | - no_adjacent_strings_in_list 54 | - no_duplicate_case_values 55 | - non_constant_identifier_names 56 | - null_closures 57 | - only_throw_errors 58 | - overridden_fields 59 | - package_names 60 | - package_prefixed_library_names 61 | - prefer_adjacent_string_concatenation 62 | - prefer_collection_literals 63 | - prefer_conditional_assignment 64 | - prefer_const_constructors 65 | - prefer_contains 66 | - prefer_final_fields 67 | - prefer_initializing_formals 68 | - prefer_interpolation_to_compose_strings 69 | - prefer_is_empty 70 | - prefer_is_not_empty 71 | - prefer_single_quotes 72 | - prefer_typing_uninitialized_variables 73 | - recursive_getters 74 | - slash_for_doc_comments 75 | - sort_constructors_first 76 | - test_types_in_equals 77 | - throw_in_finally 78 | - type_init_formals 79 | - unawaited_futures 80 | - unnecessary_brace_in_string_interps 81 | - unnecessary_const 82 | - unnecessary_getters_setters 83 | - unnecessary_lambdas 84 | - unnecessary_new 85 | - unnecessary_null_aware_assignments 86 | - unnecessary_statements 87 | - unnecessary_this 88 | - unrelated_type_equality_checks 89 | - use_rethrow_when_possible 90 | - valid_regexps -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | .cxx/ 9 | 10 | # Remember to never publicly share your keystore. 11 | # See https://flutter.dev/to/reference-keystore 12 | key.properties 13 | **/*.keystore 14 | **/*.jks 15 | -------------------------------------------------------------------------------- /example/android/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("kotlin-android") 4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 5 | id("dev.flutter.flutter-gradle-plugin") 6 | } 7 | 8 | android { 9 | namespace = "com.example.pro_video_editor_example" 10 | compileSdk = flutter.compileSdkVersion 11 | ndkVersion = "27.0.12077973" 12 | 13 | compileOptions { 14 | sourceCompatibility = JavaVersion.VERSION_11 15 | targetCompatibility = JavaVersion.VERSION_11 16 | } 17 | 18 | kotlinOptions { 19 | jvmTarget = JavaVersion.VERSION_11.toString() 20 | } 21 | 22 | defaultConfig { 23 | applicationId = "com.example.pro_video_editor_example" 24 | // You can update the following values to match your application needs. 25 | // For more information, see: https://flutter.dev/to/review-gradle-config. 26 | minSdk = 24 27 | targetSdk = flutter.targetSdkVersion 28 | versionCode = flutter.versionCode 29 | versionName = flutter.versionName 30 | } 31 | 32 | buildTypes { 33 | release { 34 | // Signing with the debug keys for now, so `flutter run --release` works. 35 | signingConfig = signingConfigs.getByName("debug") 36 | } 37 | } 38 | } 39 | 40 | flutter { 41 | source = "../.." 42 | } 43 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/ch/waio/pro_video_editor_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package ch.waio.pro_video_editor_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity : FlutterActivity() 6 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/pro_video_editor_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.pro_video_editor_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity : FlutterActivity() 6 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /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/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/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/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/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/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/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/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/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/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle.kts: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() 9 | rootProject.layout.buildDirectory.value(newBuildDir) 10 | 11 | subprojects { 12 | val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) 13 | project.layout.buildDirectory.value(newSubprojectBuildDir) 14 | } 15 | subprojects { 16 | project.evaluationDependsOn(":app") 17 | } 18 | 19 | tasks.register("clean") { 20 | delete(rootProject.layout.buildDirectory) 21 | } 22 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip 6 | -------------------------------------------------------------------------------- /example/android/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | val flutterSdkPath = run { 3 | val properties = java.util.Properties() 4 | file("local.properties").inputStream().use { properties.load(it) } 5 | val flutterSdkPath = properties.getProperty("flutter.sdk") 6 | require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } 7 | flutterSdkPath 8 | } 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id("dev.flutter.flutter-plugin-loader") version "1.0.0" 21 | id("com.android.application") version "8.7.0" apply false 22 | id("org.jetbrains.kotlin.android") version "1.8.22" apply false 23 | } 24 | 25 | include(":app") 26 | -------------------------------------------------------------------------------- /example/assets/demo.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/example/assets/demo.mov -------------------------------------------------------------------------------- /example/assets/demo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/example/assets/demo.mp4 -------------------------------------------------------------------------------- /example/integration_test/video_metadata_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:integration_test/integration_test.dart'; 5 | import 'package:pro_video_editor/core/models/video/editor_video_model.dart'; 6 | import 'package:pro_video_editor/pro_video_editor_platform_interface.dart'; 7 | import 'package:pro_video_editor_example/core/constants/example_constants.dart'; 8 | 9 | void main() { 10 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 11 | 12 | final isIOS = defaultTargetPlatform == TargetPlatform.iOS; 13 | final isMacOS = defaultTargetPlatform == TargetPlatform.macOS; 14 | 15 | testWidgets('plugin getMetadata returns correct values', (tester) async { 16 | final video = EditorVideo.asset(kVideoEditorExampleAssetPath); 17 | 18 | // Ensure bytes are preloaded to exclude loading overhead from the trace 19 | await video.safeByteArray(); 20 | 21 | final metadata = await ProVideoEditor.instance.getMetadata(video); 22 | 23 | expect(metadata.duration.inSeconds, equals(29)); 24 | expect(metadata.resolution, equals(const Size(1280.0, 720.0))); 25 | expect(metadata.extension, equals('mp4')); 26 | expect(metadata.rotation, equals(0)); 27 | expect(metadata.fileSize, equals(5253880)); 28 | 29 | if (isIOS || isMacOS) { 30 | /// AVFoundation can't return the exact duration in milliseconds, so we 31 | /// can't get the exact bitrate but very near to it. 32 | expect(metadata.bitrate, closeTo(1421504, 500)); 33 | } else { 34 | expect(metadata.bitrate, equals(1421504)); 35 | } 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /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 | 12.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, '13.0' 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 | target 'Runner' do 31 | use_frameworks! 32 | 33 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 34 | target 'RunnerTests' do 35 | inherit! :search_paths 36 | end 37 | end 38 | 39 | post_install do |installer| 40 | installer.pods_project.targets.each do |target| 41 | flutter_additional_ios_build_settings(target) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/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/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/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/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/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/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/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/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/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/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/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/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/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/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/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/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/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/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/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/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/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/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/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/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/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/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/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/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/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/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/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 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Pro Video Editor 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | pro_video_editor_example 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | 6 | @testable import pro_video_editor 7 | 8 | // This demonstrates a simple unit test of the Swift portion of this plugin's implementation. 9 | // 10 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 11 | 12 | class RunnerTests: XCTestCase { 13 | 14 | func testGetPlatformVersion() { 15 | let plugin = ProVideoEditorPlugin() 16 | 17 | let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: []) 18 | 19 | let resultExpectation = expectation(description: "result block must be called.") 20 | plugin.handle(call) { result in 21 | XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion) 22 | resultExpectation.fulfill() 23 | } 24 | waitForExpectations(timeout: 1) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /example/lib/core/constants/example_constants.dart: -------------------------------------------------------------------------------- 1 | /// A URL to a demo image hosted on a remote server. 2 | const String kVideoEditorExampleAssetPath = 'assets/demo.mp4'; 3 | -------------------------------------------------------------------------------- /example/lib/features/editor/widgets/pixel_transparent_painter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | /// A custom painter that creates a pixelated transparent pattern. 4 | /// 5 | /// The [PixelTransparentPainter] widget is a [CustomPainter] that paints a 6 | /// checkered grid pattern using two alternating colors. This is often used 7 | /// to represent transparency in image editing applications. 8 | /// 9 | /// The grid is made up of square cells, with the size of each cell controlled 10 | /// by the [cellSize] constant. 11 | /// 12 | /// Example usage: 13 | /// ```dart 14 | /// PixelTransparentPainter( 15 | /// primary: Colors.white, 16 | /// secondary: Colors.grey, 17 | /// ); 18 | /// ``` 19 | class PixelTransparentPainter extends CustomPainter { 20 | /// Creates a new [PixelTransparentPainter] with the given colors. 21 | /// 22 | /// The [primary] and [secondary] colors are used to alternate between the 23 | /// cells in the grid. 24 | const PixelTransparentPainter({ 25 | required this.primary, 26 | required this.secondary, 27 | }); 28 | 29 | /// The primary color used for alternating cells in the grid. 30 | final Color primary; 31 | 32 | /// The secondary color used for alternating cells in the grid. 33 | final Color secondary; 34 | 35 | @override 36 | void paint(Canvas canvas, Size size) { 37 | const cellSize = 22.0; // Size of each square 38 | final numCellsX = size.width / cellSize; 39 | final numCellsY = size.height / cellSize; 40 | 41 | for (int row = 0; row < numCellsY; row++) { 42 | for (int col = 0; col < numCellsX; col++) { 43 | final color = ((row + col) % 2).isEven ? primary : secondary; 44 | canvas.drawRect( 45 | Rect.fromLTWH(col * cellSize, row * cellSize, cellSize, cellSize), 46 | Paint()..color = color, 47 | ); 48 | } 49 | } 50 | } 51 | 52 | @override 53 | bool shouldRepaint(covariant CustomPainter oldDelegate) { 54 | return false; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /example/lib/features/editor/widgets/video_initializing_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// A widget that displays a initializing screen when the video editor startup. 4 | class VideoInitializingWidget extends StatelessWidget { 5 | /// Creates a [VideoInitializingWidget] widget. 6 | const VideoInitializingWidget({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Scaffold( 11 | body: Container( 12 | decoration: BoxDecoration( 13 | gradient: LinearGradient( 14 | colors: [ 15 | Colors.blueGrey.shade900, 16 | Colors.black87, 17 | ], 18 | begin: Alignment.topLeft, 19 | end: Alignment.bottomRight, 20 | ), 21 | ), 22 | child: const Center( 23 | child: Column( 24 | mainAxisSize: MainAxisSize.min, 25 | spacing: 30, 26 | children: [ 27 | Icon( 28 | Icons.video_camera_back_rounded, 29 | size: 80, 30 | color: Colors.white70, 31 | ), 32 | Text( 33 | 'Initializing Video-Editor...', 34 | style: TextStyle( 35 | fontSize: 18, 36 | color: Colors.white70, 37 | fontWeight: FontWeight.w500, 38 | ), 39 | ), 40 | SizedBox( 41 | width: 60, 42 | height: 60, 43 | child: CircularProgressIndicator( 44 | color: Colors.white70, 45 | strokeWidth: 3, 46 | ), 47 | ), 48 | ], 49 | ), 50 | ), 51 | ), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /example/lib/features/editor/widgets/video_progress_alert.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:pro_image_editor/pro_image_editor.dart'; 4 | import 'package:pro_video_editor/pro_video_editor.dart'; 5 | 6 | /// A dialog that displays real-time export progress for video generation. 7 | /// 8 | /// Listens to the [VideoUtilsService.progressStream] and shows a 9 | /// circular progress indicator with percentage text. 10 | class VideoProgressAlert extends StatelessWidget { 11 | /// Creates a [VideoProgressAlert] widget. 12 | const VideoProgressAlert({ 13 | super.key, 14 | this.taskId = '', 15 | }); 16 | 17 | /// Optional taskId of the progress stream. 18 | final String taskId; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return Stack( 23 | children: [ 24 | ModalBarrier( 25 | onDismiss: kDebugMode ? LoadingDialog.instance.hide : null, 26 | color: Colors.black54, 27 | dismissible: kDebugMode, 28 | ), 29 | Center( 30 | child: Theme( 31 | data: Theme.of(context), 32 | child: AlertDialog( 33 | contentPadding: 34 | const EdgeInsets.symmetric(vertical: 16, horizontal: 20), 35 | content: ConstrainedBox( 36 | constraints: const BoxConstraints(maxWidth: 500), 37 | child: Padding( 38 | padding: const EdgeInsets.only(top: 3.0), 39 | child: _buildProgressBody(), 40 | ), 41 | ), 42 | ), 43 | ), 44 | ), 45 | ], 46 | ); 47 | } 48 | 49 | Widget _buildProgressBody() { 50 | return StreamBuilder( 51 | stream: ProVideoEditor.instance.progressStreamById(taskId), 52 | builder: (context, snapshot) { 53 | var progress = snapshot.data?.progress ?? 0; 54 | return TweenAnimationBuilder( 55 | tween: Tween(begin: 0, end: progress), 56 | duration: const Duration(milliseconds: 300), 57 | builder: (context, animatedValue, _) { 58 | return Row( 59 | crossAxisAlignment: CrossAxisAlignment.center, 60 | mainAxisAlignment: MainAxisAlignment.start, 61 | spacing: 10, 62 | children: [ 63 | CircularProgressIndicator( 64 | value: animatedValue, 65 | // ignore: deprecated_member_use 66 | year2023: false, 67 | ), 68 | Text( 69 | '${(animatedValue * 100).toStringAsFixed(1)} / 100', 70 | style: const TextStyle( 71 | fontSize: 20, 72 | fontWeight: FontWeight.w500, 73 | ), 74 | ) 75 | ], 76 | ); 77 | }); 78 | }); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /example/lib/shared/utils/bytes_formatter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | /// Formats a given number of bytes into a human-readable string with 4 | /// appropriate size units (e.g., B, KB, MB, GB, TB). 5 | /// 6 | /// The function converts the input `bytes` into a string representation with 7 | /// the specified number of decimal places, defaulting to 2. 8 | /// 9 | /// - Parameters: 10 | /// - bytes: The size in bytes to be formatted. Must be a non-negative 11 | /// integer. 12 | /// - decimals: The number of decimal places to include in the formatted 13 | /// output. Defaults to 2. 14 | /// 15 | /// - Returns: 16 | /// A string representing the formatted size with the appropriate unit. 17 | /// 18 | /// - Example: 19 | /// ```dart 20 | /// formatBytes(1024); // Returns "1.00 KB" 21 | /// formatBytes(1048576, 1); // Returns "1.0 MB" 22 | /// formatBytes(0); // Returns "0 B" 23 | /// ``` 24 | String formatBytes(int bytes, [int decimals = 2]) { 25 | if (bytes <= 0) return '0 B'; 26 | const suffixes = ['B', 'KB', 'MB', 'GB', 'TB']; 27 | int i = (log(bytes) / log(1024)).floor(); 28 | double size = bytes / pow(1024, i); 29 | return '${size.toStringAsFixed(decimals)} ${suffixes[i]}'; 30 | } 31 | -------------------------------------------------------------------------------- /example/lib/shared/utils/filter_generator/filter_model.dart: -------------------------------------------------------------------------------- 1 | /// A model class that represents a filter with a name and associated filter 2 | /// matrix. 3 | class FilterModel { 4 | /// Constructs a [FilterModel] instance with the specified [name] and 5 | /// [filters]. 6 | /// 7 | /// The [name] parameter is required and represents the name of the filter. 8 | /// The [filters] parameter is required and represents the filter matrix to 9 | /// be applied. 10 | const FilterModel({ 11 | required this.name, 12 | required this.filters, 13 | }); 14 | 15 | /// The name of the filter. 16 | final String name; 17 | 18 | /// The filter matrix associated with this filter. 19 | final List> filters; 20 | } 21 | -------------------------------------------------------------------------------- /example/linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /example/linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.10) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # https://github.com/flutter/flutter/issues/57146. 10 | 11 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 12 | # which isn't available in 3.10. 13 | function(list_prepend LIST_NAME PREFIX) 14 | set(NEW_LIST "") 15 | foreach(element ${${LIST_NAME}}) 16 | list(APPEND NEW_LIST "${PREFIX}${element}") 17 | endforeach(element) 18 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 19 | endfunction() 20 | 21 | # === Flutter Library === 22 | # System-level dependencies. 23 | find_package(PkgConfig REQUIRED) 24 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 25 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 26 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 27 | 28 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 29 | 30 | # Published to parent scope for install step. 31 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 32 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 33 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 34 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 35 | 36 | list(APPEND FLUTTER_LIBRARY_HEADERS 37 | "fl_basic_message_channel.h" 38 | "fl_binary_codec.h" 39 | "fl_binary_messenger.h" 40 | "fl_dart_project.h" 41 | "fl_engine.h" 42 | "fl_json_message_codec.h" 43 | "fl_json_method_codec.h" 44 | "fl_message_codec.h" 45 | "fl_method_call.h" 46 | "fl_method_channel.h" 47 | "fl_method_codec.h" 48 | "fl_method_response.h" 49 | "fl_plugin_registrar.h" 50 | "fl_plugin_registry.h" 51 | "fl_standard_message_codec.h" 52 | "fl_standard_method_codec.h" 53 | "fl_string_codec.h" 54 | "fl_value.h" 55 | "fl_view.h" 56 | "flutter_linux.h" 57 | ) 58 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 59 | add_library(flutter INTERFACE) 60 | target_include_directories(flutter INTERFACE 61 | "${EPHEMERAL_DIR}" 62 | ) 63 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 64 | target_link_libraries(flutter INTERFACE 65 | PkgConfig::GTK 66 | PkgConfig::GLIB 67 | PkgConfig::GIO 68 | ) 69 | add_dependencies(flutter flutter_assemble) 70 | 71 | # === Flutter tool backend === 72 | # _phony_ is a non-existent file to force this command to run every time, 73 | # since currently there's no way to get a full input/output list from the 74 | # flutter tool. 75 | add_custom_command( 76 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 77 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 78 | COMMAND ${CMAKE_COMMAND} -E env 79 | ${FLUTTER_TOOL_ENVIRONMENT} 80 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 81 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 82 | VERBATIM 83 | ) 84 | add_custom_target(flutter_assemble DEPENDS 85 | "${FLUTTER_LIBRARY}" 86 | ${FLUTTER_LIBRARY_HEADERS} 87 | ) 88 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void fl_register_plugins(FlPluginRegistry* registry) { 15 | g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar = 16 | fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin"); 17 | media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar); 18 | g_autoptr(FlPluginRegistrar) media_kit_video_registrar = 19 | fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitVideoPlugin"); 20 | media_kit_video_plugin_register_with_registrar(media_kit_video_registrar); 21 | g_autoptr(FlPluginRegistrar) pro_video_editor_registrar = 22 | fl_plugin_registry_get_registrar_for_plugin(registry, "ProVideoEditorPlugin"); 23 | pro_video_editor_plugin_register_with_registrar(pro_video_editor_registrar); 24 | g_autoptr(FlPluginRegistrar) volume_controller_registrar = 25 | fl_plugin_registry_get_registrar_for_plugin(registry, "VolumeControllerPlugin"); 26 | volume_controller_plugin_register_with_registrar(volume_controller_registrar); 27 | } 28 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | media_kit_libs_linux 7 | media_kit_video 8 | pro_video_editor 9 | volume_controller 10 | ) 11 | 12 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 13 | ) 14 | 15 | set(PLUGIN_BUNDLED_LIBRARIES) 16 | 17 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 18 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 19 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 22 | endforeach(plugin) 23 | 24 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 27 | endforeach(ffi_plugin) 28 | -------------------------------------------------------------------------------- /example/linux/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} 10 | "main.cc" 11 | "my_application.cc" 12 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 13 | ) 14 | 15 | # Apply the standard set of build settings. This can be removed for applications 16 | # that need different build settings. 17 | apply_standard_settings(${BINARY_NAME}) 18 | 19 | # Add preprocessor definitions for the application ID. 20 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 21 | 22 | # Add dependency libraries. Add any application-specific dependencies here. 23 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 24 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 25 | 26 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 27 | -------------------------------------------------------------------------------- /example/linux/runner/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /example/linux/runner/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /example/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import media_kit_libs_macos_video 9 | import media_kit_video 10 | import package_info_plus 11 | import path_provider_foundation 12 | import pro_video_editor 13 | import shared_preferences_foundation 14 | import video_player_avfoundation 15 | import volume_controller 16 | import wakelock_plus 17 | 18 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 19 | MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) 20 | MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) 21 | FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) 22 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 23 | ProVideoEditorPlugin.register(with: registry.registrar(forPlugin: "ProVideoEditorPlugin")) 24 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 25 | FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) 26 | VolumeControllerPlugin.register(with: registry.registrar(forPlugin: "VolumeControllerPlugin")) 27 | WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) 28 | } 29 | -------------------------------------------------------------------------------- /example/macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.15' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | 32 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 33 | target 'RunnerTests' do 34 | inherit! :search_paths 35 | end 36 | end 37 | 38 | post_install do |installer| 39 | installer.pods_project.targets.each do |target| 40 | flutter_additional_macos_build_settings(target) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @main 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | 10 | override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 11 | return true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /example/macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = pro_video_editor_example 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = ch.waio.proVideoEditorExample 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2025 ch.waio. All rights reserved. 15 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /example/macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /example/macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /example/macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | Task { @MainActor in 7 | let flutterViewController = FlutterViewController() 8 | let windowFrame = self.frame 9 | self.contentViewController = flutterViewController 10 | self.setFrame(windowFrame, display: true) 11 | 12 | RegisterGeneratedPlugins(registry: flutterViewController) 13 | } 14 | 15 | super.awakeFromNib() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | import XCTest 4 | 5 | 6 | @testable import pro_video_editor 7 | 8 | // This demonstrates a simple unit test of the Swift portion of this plugin's implementation. 9 | // 10 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 11 | 12 | class RunnerTests: XCTestCase { 13 | 14 | func testGetPlatformVersion() { 15 | let plugin = ProVideoEditorPlugin() 16 | 17 | let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: []) 18 | 19 | let resultExpectation = expectation(description: "result block must be called.") 20 | plugin.handle(call) { result in 21 | XCTAssertEqual(result as! String, 22 | "macOS " + ProcessInfo.processInfo.operatingSystemVersionString) 23 | resultExpectation.fulfill() 24 | } 25 | waitForExpectations(timeout: 1) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: pro_video_editor_example 2 | description: "Demonstrates how to use the pro_video_editor plugin." 3 | publish_to: 'none' 4 | 5 | environment: 6 | sdk: '>=3.4.0 <4.0.0' 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | pro_video_editor: 13 | path: ../ 14 | 15 | 16 | intl: ^0.20.1 17 | cupertino_icons: ^1.0.8 18 | 19 | pro_image_editor: ^9.11.1 20 | 21 | google_fonts: ^6.2.1 22 | flutter_colorpicker: ^1.1.0 23 | mime: ^2.0.0 24 | 25 | path_provider: ^2.1.5 26 | video_player: ^2.9.3 27 | # Optional multiplatform video player 28 | media_kit: ^1.1.11 # Primary package. 29 | media_kit_video: ^1.2.5 # For video rendering. 30 | media_kit_libs_video: ^1.0.5 # Native video dependencies. 31 | 32 | dev_dependencies: 33 | integration_test: 34 | sdk: flutter 35 | flutter_test: 36 | sdk: flutter 37 | 38 | flutter_lints: ^5.0.0 39 | 40 | 41 | flutter: 42 | uses-material-design: true 43 | assets: 44 | - assets/demo.mp4 45 | - assets/demo.mov -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:pro_video_editor_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => 22 | widget is Text && widget.data!.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/example/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/example/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | pro_video_editor_example 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pro_video_editor_example", 3 | "short_name": "pro_video_editor_example", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "Demonstrates how to use the pro_video_editor plugin.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /example/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void RegisterPlugins(flutter::PluginRegistry* registry) { 15 | MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( 16 | registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi")); 17 | MediaKitVideoPluginCApiRegisterWithRegistrar( 18 | registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi")); 19 | ProVideoEditorPluginCApiRegisterWithRegistrar( 20 | registry->GetRegistrarForPlugin("ProVideoEditorPluginCApi")); 21 | VolumeControllerPluginCApiRegisterWithRegistrar( 22 | registry->GetRegistrarForPlugin("VolumeControllerPluginCApi")); 23 | } 24 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | media_kit_libs_windows_video 7 | media_kit_video 8 | pro_video_editor 9 | volume_controller 10 | ) 11 | 12 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 13 | ) 14 | 15 | set(PLUGIN_BUNDLED_LIBRARIES) 16 | 17 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 18 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 19 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 22 | endforeach(plugin) 23 | 24 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 27 | endforeach(ffi_plugin) 28 | -------------------------------------------------------------------------------- /example/windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | // Flutter can complete the first frame before the "show window" callback is 35 | // registered. The following call ensures a frame is pending to ensure the 36 | // window is shown. It is a no-op if the first frame hasn't completed yet. 37 | flutter_controller_->ForceRedraw(); 38 | 39 | return true; 40 | } 41 | 42 | void FlutterWindow::OnDestroy() { 43 | if (flutter_controller_) { 44 | flutter_controller_ = nullptr; 45 | } 46 | 47 | Win32Window::OnDestroy(); 48 | } 49 | 50 | LRESULT 51 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 52 | WPARAM const wparam, 53 | LPARAM const lparam) noexcept { 54 | // Give Flutter, including plugins, an opportunity to handle window messages. 55 | if (flutter_controller_) { 56 | std::optional result = 57 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 58 | lparam); 59 | if (result) { 60 | return *result; 61 | } 62 | } 63 | 64 | switch (message) { 65 | case WM_FONTCHANGE: 66 | flutter_controller_->engine()->ReloadSystemFonts(); 67 | break; 68 | } 69 | 70 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 71 | } 72 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /example/windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"pro_video_editor_example", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /example/windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /example/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/example/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /example/windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | unsigned int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr) 51 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length == 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /example/windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /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 | /Flutter/ephemeral/ 38 | /Flutter/flutter_export_environment.sh 39 | -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hm21/pro_video_editor/eaf6903c1ac89239ac98b47e9922242cad114870/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/src/core/constants/LoggingConstants.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct Tags { 4 | static let package = "ProVideoEditor" 5 | static let render = "\(package)-Renderer" 6 | static let thumbnail = "\(package)-Thumbnail" 7 | } 8 | -------------------------------------------------------------------------------- /ios/Classes/src/features/render/helpers/ApplyAudio.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | import Foundation 3 | 4 | public func applyAudio( 5 | from asset: AVAsset, 6 | to composition: AVMutableComposition, 7 | timeRange: CMTimeRange, 8 | enableAudio: Bool 9 | ) async { 10 | guard enableAudio else { 11 | print("[\(Tags.render)] Removing audio from export") 12 | return 13 | } 14 | 15 | do { 16 | let audioTracks: [AVAssetTrack] 17 | if #available(iOS 15.0, *) { 18 | audioTracks = try await asset.loadTracks(withMediaType: .audio) 19 | } else { 20 | audioTracks = asset.tracks(withMediaType: .audio) 21 | } 22 | 23 | if let audioTrack = audioTracks.first { 24 | if let audioCompositionTrack = composition.addMutableTrack( 25 | withMediaType: .audio, 26 | preferredTrackID: kCMPersistentTrackID_Invalid 27 | ) { 28 | try? audioCompositionTrack.insertTimeRange(timeRange, of: audioTrack, at: .zero) 29 | } 30 | } 31 | } catch { 32 | print("[\(Tags.render)] ⚠️ Failed to load audio tracks: \(error.localizedDescription)") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ios/Classes/src/features/render/helpers/ApplyBitrate.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | 3 | public func applyBitrate(requestedBitrate: Int?, presetHint: String? = nil) -> String { 4 | if let bitrate = requestedBitrate { 5 | print("[Render] Requested Bitrate: \(bitrate) bps") 6 | print("[Render] ⚠️ AVAssetExportSession does not support custom bitrate directly.") 7 | 8 | if bitrate >= 50_000_000 { 9 | if #available(iOS 11.0, *) { 10 | return AVAssetExportPresetHEVC3840x2160 // Use 4K HEVC as max on iOS 11 | } else { 12 | return AVAssetExportPreset3840x2160 13 | } 14 | } else if bitrate >= 40_000_000 { 15 | if #available(iOS 11.0, *) { 16 | return AVAssetExportPresetHEVC3840x2160 17 | } else { 18 | return AVAssetExportPreset3840x2160 19 | } 20 | } else if bitrate >= 30_000_000 { 21 | if #available(iOS 11.0, *) { 22 | return AVAssetExportPresetHEVC1920x1080 23 | } else { 24 | return AVAssetExportPreset1920x1080 25 | } 26 | } else if bitrate >= 20_000_000 { 27 | if #available(iOS 11.0, *) { 28 | return AVAssetExportPresetHEVCHighestQuality 29 | } else { 30 | return AVAssetExportPresetHighestQuality 31 | } 32 | } else if bitrate >= 10_000_000 { 33 | return AVAssetExportPresetHighestQuality 34 | } else if bitrate >= 7_000_000 { 35 | return AVAssetExportPreset1920x1080 36 | } else if bitrate >= 5_000_000 { 37 | return AVAssetExportPreset1280x720 38 | } else if bitrate >= 3_000_000 { 39 | return AVAssetExportPreset960x540 40 | } else if bitrate >= 2_000_000 { 41 | return AVAssetExportPreset640x480 42 | } else if bitrate >= 1_000_000 { 43 | return AVAssetExportPresetMediumQuality 44 | } else { 45 | return AVAssetExportPresetLowQuality 46 | } 47 | } 48 | 49 | return presetHint ?? AVAssetExportPresetHighestQuality 50 | } 51 | -------------------------------------------------------------------------------- /ios/Classes/src/features/render/helpers/ApplyBlur.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | 3 | func applyBlur( 4 | config: inout VideoCompositorConfig, 5 | sigma: Double? 6 | ) { 7 | config.blurSigma = (sigma ?? 0) * 2.5 8 | 9 | if sigma == nil || sigma == 0 { return } 10 | 11 | print("[\(Tags.render)] Applying blur: sigma=\(sigma!)") 12 | } 13 | -------------------------------------------------------------------------------- /ios/Classes/src/features/render/helpers/ApplyCrop.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | import CoreGraphics 3 | 4 | func applyCrop( 5 | config: inout VideoCompositorConfig, 6 | naturalSize: CGSize, 7 | rotateTurns: Int?, 8 | cropX: Int?, 9 | cropY: Int?, 10 | cropWidth: Int?, 11 | cropHeight: Int? 12 | ) -> CGSize { 13 | let x = CGFloat(cropX ?? 0) 14 | let y = CGFloat(cropY ?? 0) 15 | let width = CGFloat(cropWidth ?? Int(naturalSize.width) - Int(x)) 16 | let height = CGFloat(cropHeight ?? Int(naturalSize.height) - Int(y)) 17 | 18 | config.cropX = x 19 | config.cropY = y 20 | config.cropWidth = width 21 | config.cropHeight = height 22 | 23 | if cropX != 0 || cropY != 0 || cropWidth != nil || cropHeight != nil { 24 | print( 25 | "[\(Tags.render)] Applying crop: x=\(Int(x)) y=\(Int(y)) width=\(Int(width)) height=\(Int(height))" 26 | ) 27 | } 28 | 29 | let cropRect = CGRect(x: x, y: y, width: width, height: height) 30 | 31 | let turns = 4 - (rotateTurns ?? 0) % 4 32 | 33 | let isPortraitRotation = turns % 2 == 1 34 | return isPortraitRotation 35 | ? CGSize(width: cropRect.size.height, height: cropRect.size.width) 36 | : cropRect.size 37 | } 38 | -------------------------------------------------------------------------------- /ios/Classes/src/features/render/helpers/ApplyFlip.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | 3 | func applyFlip( 4 | config: inout VideoCompositorConfig, 5 | flipX: Bool, 6 | flipY: Bool 7 | ) { 8 | config.flipX = flipX 9 | config.flipY = flipY 10 | 11 | if !flipX && !flipY { return } 12 | 13 | print("[\(Tags.render)] Applying flip: flipX=\(flipX), flipY=\(flipY)") 14 | } 15 | -------------------------------------------------------------------------------- /ios/Classes/src/features/render/helpers/ApplyImageLayer.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | import CoreImage 3 | 4 | func applyImageLayer( 5 | config: inout VideoCompositorConfig, 6 | imageData: Data? 7 | ) { 8 | config.overlayImage = imageData 9 | guard imageData != nil else { return } 10 | 11 | print("[Render] Applying overlay image") 12 | } 13 | -------------------------------------------------------------------------------- /ios/Classes/src/features/render/helpers/ApplyPlaybackSpeed.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | 3 | public func applyPlaybackSpeed( 4 | composition: AVMutableComposition, 5 | speed: Float? 6 | ) { 7 | guard let speed = speed, speed > 0, speed != 1 else { return } 8 | 9 | print("[\(Tags.render)] Applying playback speed: \(speed)x") 10 | 11 | let tracks = composition.tracks 12 | for track in tracks { 13 | let range = CMTimeRange(start: .zero, duration: track.timeRange.duration) 14 | let scaledDuration = CMTimeMultiplyByFloat64(range.duration, multiplier: 1 / Double(speed)) 15 | track.scaleTimeRange(range, toDuration: scaledDuration) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ios/Classes/src/features/render/helpers/ApplyRotation.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | 3 | func applyRotation( 4 | config: inout VideoCompositorConfig, 5 | rotateTurns: Int? 6 | ) { 7 | let normalizedTurns = ((rotateTurns ?? 0) % 4 + 4) % 4 8 | let turns = (4 - normalizedTurns) % 4 9 | let degrees = turns * 90 10 | let radians = CGFloat(Double(degrees) * .pi / 180) 11 | 12 | config.rotateRadians = radians 13 | config.rotateTurns = turns 14 | 15 | if turns == 0 { return } 16 | print("[\(Tags.render)] Applying rotation: \(degrees) degrees") 17 | } 18 | -------------------------------------------------------------------------------- /ios/Classes/src/features/render/helpers/ApplyScale.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | 3 | func applyScale( 4 | config: inout VideoCompositorConfig, 5 | scaleX: Float?, 6 | scaleY: Float? 7 | ) { 8 | let x = CGFloat(scaleX ?? 1.0) 9 | let y = CGFloat(scaleY ?? 1.0) 10 | 11 | config.scaleX = x 12 | config.scaleY = y 13 | 14 | if x != 1.0 || y != 1.0 { 15 | print("[\(Tags.render)] Applying scale: scaleX=\(x), scaleY=\(y)") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ios/Classes/src/features/render/helpers/ApplyTrim.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | 3 | public func applyTrim( 4 | asset: AVAsset, 5 | startUs: Int64?, 6 | endUs: Int64? 7 | ) async -> CMTimeRange { 8 | // Load duration 9 | let duration: CMTime 10 | if #available(iOS 15.0, *) { 11 | do { 12 | duration = try await asset.load(.duration) 13 | } catch { 14 | return CMTimeRange(start: .zero, duration: .positiveInfinity) 15 | } 16 | } else { 17 | duration = asset.duration 18 | } 19 | 20 | // Prepare start and end CMTime 21 | let start = startUs != nil 22 | ? CMTime(value: startUs!, timescale: 1_000_000) 23 | : .zero 24 | 25 | let end = endUs != nil 26 | ? CMTime(value: endUs!, timescale: 1_000_000) 27 | : duration 28 | 29 | // Logging in ms for easier debugging 30 | let startMs = Int64(start.seconds * 1000) 31 | let endMs = Int64(end.seconds * 1000) 32 | print("[\(Tags.render)] Applying trim: start=\(startMs) ms, end=\(endMs) ms") 33 | 34 | return CMTimeRange(start: start, end: end) 35 | } 36 | -------------------------------------------------------------------------------- /ios/Classes/src/features/render/models/VideoCompositorConfig.swift: -------------------------------------------------------------------------------- 1 | import CoreImage 2 | 3 | struct VideoCompositorConfig { 4 | var blurSigma: Double = 0.0 5 | var overlayImage: Data? = nil 6 | 7 | var rotateRadians: Double = 0.0 8 | var rotateTurns: Int = 0 9 | var flipX: Bool = false 10 | var flipY: Bool = false 11 | 12 | var cropX: CGFloat = 0.0 13 | var cropY: CGFloat = 0.0 14 | var cropWidth: CGFloat? = nil 15 | var cropHeight: CGFloat? = nil 16 | 17 | var scaleX: CGFloat = 1.0 18 | var scaleY: CGFloat = 1.0 19 | 20 | var lutData: Data? = nil 21 | var lutSize: Int = 33 22 | 23 | var videoRotationDegrees: Double = 0.0 24 | var shouldApplyOrientationCorrection: Bool = false 25 | 26 | var preferredTransform: CGAffineTransform = .identity 27 | var originalNaturalSize: CGSize = .zero 28 | } 29 | -------------------------------------------------------------------------------- /ios/Classes/src/features/render/utils/VideoMimeUtils.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | 3 | func mapFormatToMimeType(format: String) -> AVFileType { 4 | switch format { 5 | case "mp4": 6 | return .mp4 7 | case "mov": 8 | return .mov 9 | default: 10 | return .mp4 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ios/Resources/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyTrackingDomains 6 | 7 | NSPrivacyAccessedAPITypes 8 | 9 | NSPrivacyCollectedDataTypes 10 | 11 | NSPrivacyTracking 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ios/pro_video_editor.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint pro_video_editor.podspec` to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'pro_video_editor' 7 | s.version = '0.0.1' 8 | s.summary = 'A new Flutter plugin project.' 9 | s.description = <<-DESC 10 | A new Flutter plugin project. 11 | DESC 12 | s.homepage = 'https://waio.ch' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'WAIO Frei Applications' => 'info@waio.ch' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.dependency 'Flutter' 18 | s.platform = :ios, '13.0' 19 | 20 | # Flutter.framework does not contain a i386 slice. 21 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } 22 | s.swift_version = '5.0' 23 | 24 | # If your plugin requires a privacy manifest, for example if it uses any 25 | # required reason APIs, update the PrivacyInfo.xcprivacy file to describe your 26 | # plugin's privacy impact, and then uncomment this line. For more information, 27 | # see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files 28 | # s.resource_bundles = {'pro_video_editor_privacy' => ['Resources/PrivacyInfo.xcprivacy']} 29 | end 30 | -------------------------------------------------------------------------------- /lib/core/models/thumbnail/key_frames_configs.model.dart: -------------------------------------------------------------------------------- 1 | import '/core/models/thumbnail/thumbnail_base.abstract.dart'; 2 | 3 | /// Configuration model for extracting key frames from a video. 4 | /// 5 | /// Defines video input, output size, maximum number of key frames, 6 | /// and rendering options for the resulting thumbnails. 7 | class KeyFramesConfigs extends ThumbnailBase { 8 | /// Creates a [KeyFramesConfigs] instance with the given settings. 9 | /// 10 | /// If [maxOutputFrames] is not provided, it defaults to unlimited. 11 | KeyFramesConfigs({ 12 | required super.video, 13 | required super.outputSize, 14 | super.outputFormat, 15 | super.boxFit, 16 | super.id, 17 | int? maxOutputFrames, 18 | }) : maxOutputFrames = maxOutputFrames ??= double.infinity.toInt(); 19 | 20 | /// The maximum number of frames to extract as thumbnails. 21 | /// 22 | /// Defaults to no limit when not specified. 23 | final int maxOutputFrames; 24 | 25 | @override 26 | Map toMap() { 27 | return { 28 | 'id': id, 29 | 'boxFit': boxFit.name, 30 | 'outputFormat': outputFormat.name, 31 | 'outputWidth': outputSize.width.round(), 32 | 'outputHeight': outputSize.height.round(), 33 | 'maxOutputFrames': maxOutputFrames, 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/core/models/thumbnail/thumbnail_base.abstract.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | import '../../../pro_video_editor_platform_interface.dart'; 4 | import '../video/editor_video_model.dart'; 5 | import '../video/progress_model.dart'; 6 | import 'thumbnail_box_fit.model.dart'; 7 | import 'thumbnail_format.model.dart'; 8 | 9 | /// Base model for thumbnail generation tasks. 10 | /// 11 | /// Defines the video input, output size, image format, and box fit options. 12 | abstract class ThumbnailBase { 13 | /// Creates a base thumbnail model. 14 | ThumbnailBase({ 15 | required this.video, 16 | required this.outputSize, 17 | this.outputFormat = ThumbnailFormat.jpeg, 18 | this.boxFit = ThumbnailBoxFit.cover, 19 | String? id, 20 | }) : id = id ?? DateTime.now().microsecondsSinceEpoch.toString(); 21 | 22 | /// Unique ID for the task, useful when running multiple tasks at once. 23 | final String id; 24 | 25 | /// The video from which thumbnails will be generated. 26 | final EditorVideo video; 27 | 28 | /// The desired size of each generated thumbnail. 29 | final Size outputSize; 30 | 31 | /// The format used for thumbnail images. 32 | /// 33 | /// Defaults to [ThumbnailFormat.jpeg]. 34 | final ThumbnailFormat outputFormat; 35 | 36 | /// Determines how the video content is fit into the thumbnail size. 37 | /// 38 | /// Defaults to [ThumbnailBoxFit.cover]. 39 | final ThumbnailBoxFit boxFit; 40 | 41 | /// Converts the model into a serializable map. 42 | Map toMap(); 43 | 44 | /// Returns a [Stream] of [ProgressModel] objects that provides updates on 45 | /// the progress of the generation associated with this model's [id]. 46 | /// 47 | /// The stream is obtained from the [ProVideoEditor] singleton instance and 48 | /// is specific to the current video's identifier. 49 | Stream get progressStream { 50 | return ProVideoEditor.instance.progressStreamById(id); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/core/models/thumbnail/thumbnail_box_fit.model.dart: -------------------------------------------------------------------------------- 1 | /// Defines how the video content should be fit within the thumbnail bounds. 2 | /// 3 | /// Similar to [BoxFit], this determines whether the thumbnail should fill 4 | /// or fit within the specified size. 5 | enum ThumbnailBoxFit { 6 | /// Scales the content to completely fill the thumbnail bounds. 7 | /// 8 | /// This may crop parts of the video to maintain the aspect ratio. 9 | cover, 10 | 11 | /// Scales the content to fit entirely within the thumbnail bounds. 12 | /// 13 | /// This may result in empty space (letterboxing) to preserve aspect ratio. 14 | contain, 15 | } 16 | -------------------------------------------------------------------------------- /lib/core/models/thumbnail/thumbnail_configs.model.dart: -------------------------------------------------------------------------------- 1 | import 'thumbnail_base.abstract.dart'; 2 | 3 | /// Configuration model for generating video thumbnails. 4 | /// 5 | /// Defines the video source, output size, desired timestamps, 6 | /// and thumbnail rendering options. 7 | class ThumbnailConfigs extends ThumbnailBase { 8 | /// Creates a [ThumbnailConfigs] instance with the given parameters. 9 | /// 10 | /// Requires a video source, output size, and at least one timestamp. 11 | ThumbnailConfigs({ 12 | required super.video, 13 | required super.outputSize, 14 | super.outputFormat, 15 | super.boxFit, 16 | super.id, 17 | required this.timestamps, 18 | }); 19 | 20 | /// A list of timestamps to capture thumbnails from. 21 | final List timestamps; 22 | 23 | @override 24 | Map toMap() { 25 | return { 26 | 'id': id, 27 | 'boxFit': boxFit.name, 28 | 'outputFormat': outputFormat.name, 29 | 'outputWidth': outputSize.width.round(), 30 | 'outputHeight': outputSize.height.round(), 31 | 'timestamps': timestamps 32 | .map( 33 | (timestamp) => timestamp.inMicroseconds, 34 | ) 35 | .toList(), 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/core/models/thumbnail/thumbnail_format.model.dart: -------------------------------------------------------------------------------- 1 | /// Supported image formats for video thumbnails. 2 | enum ThumbnailFormat { 3 | /// JPEG format (typically smaller file size, lossy compression). 4 | jpeg, 5 | 6 | /// PNG format (lossless compression, larger file size). 7 | png, 8 | 9 | /// WebP format (modern, efficient, may not be supported on all platforms). 10 | /// 11 | /// Only supported on android. 12 | webp, 13 | } 14 | -------------------------------------------------------------------------------- /lib/core/models/video/export_transform_model.dart: -------------------------------------------------------------------------------- 1 | /// Represents a set of transformations to apply during video export. 2 | /// 3 | /// This includes resizing, rotation, flipping, and positional offsets. 4 | class ExportTransform { 5 | /// Creates an [ExportTransform] with optional transformations. 6 | const ExportTransform({ 7 | this.width, 8 | this.height, 9 | this.rotateTurns = 0, 10 | this.x, 11 | this.y, 12 | this.flipX = false, 13 | this.flipY = false, 14 | this.scaleX, 15 | this.scaleY, 16 | }); 17 | 18 | /// Output width in pixels. If null, original width is used. 19 | final int? width; 20 | 21 | /// Output height in pixels. If null, original height is used. 22 | final int? height; 23 | 24 | /// Number of clockwise 90° rotations to apply (0 = no rotation). 25 | final int rotateTurns; 26 | 27 | /// Horizontal offset 28 | final int? x; 29 | 30 | /// Vertical offset 31 | final int? y; 32 | 33 | /// Horizontal scale factor for resizing the video or overlay image. 34 | /// 35 | /// A value of `1.0` means no scaling. Values greater than `1.0` enlarge 36 | /// the content, while values between `0.0` and `1.0` shrink it. 37 | final double? scaleX; 38 | 39 | /// Vertical scale factor for resizing the video or overlay image. 40 | /// 41 | /// A value of `1.0` means no scaling. Values greater than `1.0` enlarge 42 | /// the content, while values between `0.0` and `1.0` shrink it. 43 | final double? scaleY; 44 | 45 | /// Whether to flip horizontally. 46 | final bool flipX; 47 | 48 | /// Whether to flip vertically. 49 | final bool flipY; 50 | 51 | /// Returns a copy of this config with the given fields replaced. 52 | ExportTransform copyWith({ 53 | int? width, 54 | int? height, 55 | int? rotateTurns, 56 | int? x, 57 | int? y, 58 | bool? flipX, 59 | bool? flipY, 60 | }) { 61 | return ExportTransform( 62 | width: width ?? this.width, 63 | height: height ?? this.height, 64 | rotateTurns: rotateTurns ?? this.rotateTurns, 65 | x: x ?? this.x, 66 | y: y ?? this.y, 67 | flipX: flipX ?? this.flipX, 68 | flipY: flipY ?? this.flipY, 69 | ); 70 | } 71 | 72 | @override 73 | bool operator ==(Object other) { 74 | if (identical(this, other)) return true; 75 | 76 | return other is ExportTransform && 77 | other.width == width && 78 | other.height == height && 79 | other.rotateTurns == rotateTurns && 80 | other.x == x && 81 | other.y == y && 82 | other.flipX == flipX && 83 | other.flipY == flipY; 84 | } 85 | 86 | @override 87 | int get hashCode { 88 | return width.hashCode ^ 89 | height.hashCode ^ 90 | rotateTurns.hashCode ^ 91 | x.hashCode ^ 92 | y.hashCode ^ 93 | flipX.hashCode ^ 94 | flipY.hashCode; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/core/models/video/progress_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:pro_video_editor/shared/utils/parser/double_parser.dart'; 2 | 3 | /// Model representing a progress update. 4 | class ProgressModel { 5 | /// Creates a [ProgressModel] with given [id] and [progress]. 6 | const ProgressModel({ 7 | required this.id, 8 | required this.progress, 9 | }); 10 | 11 | /// Creates a [ProgressModel] from a map. 12 | factory ProgressModel.fromMap(Map map) { 13 | return ProgressModel( 14 | id: map['id'] ?? '', 15 | progress: safeParseDouble(map['progress']), 16 | ); 17 | } 18 | 19 | /// The ID of the task. 20 | final String id; 21 | 22 | /// The progress value (0.0 to 1.0). 23 | final double progress; 24 | } 25 | -------------------------------------------------------------------------------- /lib/core/platform/io/io_helper.dart: -------------------------------------------------------------------------------- 1 | export 'io_web.dart' if (dart.library.io) 'dart:io'; 2 | -------------------------------------------------------------------------------- /lib/core/services/web/web_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import '/core/models/thumbnail/key_frames_configs.model.dart'; 4 | import '/core/models/thumbnail/thumbnail_configs.model.dart'; 5 | import '/core/models/video/editor_video_model.dart'; 6 | import '/core/models/video/video_metadata_model.dart'; 7 | import '/core/services/web/web_thumbnail_generator.dart'; 8 | import 'web_meta_data_reader.dart'; 9 | 10 | /// A platform-specific implementation for handling video operations on web. 11 | /// 12 | /// This class provides methods to extract metadata and generate thumbnails 13 | /// using browser capabilities. 14 | class WebManager { 15 | /// Retrieves metadata from the provided [EditorVideo] on the web. 16 | /// 17 | /// Loads the video using an HTML video element and extracts duration, 18 | /// resolution, file size, and format. 19 | /// 20 | /// Returns a [VideoMetadata] object. 21 | Future getMetadata(EditorVideo value) async { 22 | return await WebMetaDataReader().getMetaData(value); 23 | } 24 | 25 | /// Generates thumbnails for a video based on the given [ThumbnailConfigs]. 26 | /// 27 | /// Extracts frames from specified timestamps and returns them as a list of 28 | /// image data in [Uint8List] format. 29 | Future> getThumbnails( 30 | ThumbnailConfigs value, { 31 | void Function(double progress)? onProgress, 32 | }) async { 33 | return await WebThumbnailGenerator().getThumbnails( 34 | value, 35 | onProgress: onProgress, 36 | ); 37 | } 38 | 39 | /// Extracts evenly spaced key frames using the [KeyFramesConfigs] settings. 40 | /// 41 | /// Returns a list of [Uint8List] image data captured at calculated 42 | /// intervals throughout the video. 43 | Future> getKeyFrames( 44 | KeyFramesConfigs value, { 45 | void Function(double progress)? onProgress, 46 | }) async { 47 | return await WebThumbnailGenerator().getKeyFrames( 48 | value, 49 | onProgress: onProgress, 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/core/utils/web_blob_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:js_interop'; 2 | import 'dart:typed_data'; 3 | 4 | /// A Dart extension type for working with JavaScript `Blob` objects. 5 | /// 6 | /// Provides factory constructors for creating `Blob` instances from byte data 7 | /// and exposes properties such as `type`, `blobParts`, and `options`. 8 | /// 9 | /// - `Blob.fromBytes`: Creates a `Blob` from a list of integers. 10 | /// - `Blob.fromUint8List`: Creates a `Blob` from a `Uint8List`. 11 | /// - `type`: Retrieves the MIME type of the `Blob`. 12 | /// - `blobParts`: Accesses the parts of the `Blob` as a `JSArrayBuffer`. 13 | /// - `options`: Retrieves the options used to create the `Blob`. 14 | @JS('Blob') 15 | extension type Blob._(JSObject _) implements JSObject { 16 | /// A JavaScript `Blob` factory for creating binary large objects. 17 | /// 18 | /// This factory allows you to create a `Blob` from a list of `ArrayBuffer`s. 19 | /// You can also provide optional configuration options. 20 | external factory Blob(JSArray blobParts, JSObject? options); 21 | 22 | /// Creates a [Blob] from a list of bytes. 23 | /// 24 | /// This constructor converts the given [bytes] into a `Uint8List`, 25 | /// wraps it in a JavaScript `ArrayBuffer`, and passes it to the `Blob` 26 | /// constructor with no additional options. 27 | factory Blob.fromBytes(List bytes) { 28 | final data = Uint8List.fromList(bytes).buffer.toJS; 29 | return Blob([data].toJS, null); 30 | } 31 | 32 | /// Creates a [Blob] directly from a [Uint8List]. 33 | /// 34 | /// This constructor wraps the byte buffer in a JavaScript `ArrayBuffer` 35 | /// and passes it to the `Blob` constructor with no additional options. 36 | factory Blob.fromUint8List(Uint8List bytes) { 37 | final data = Uint8List.fromList(bytes).buffer.toJS; 38 | return Blob([data].toJS, null); 39 | } 40 | 41 | /// The MIME type of the `Blob` content as a string. 42 | @JS('type') 43 | external String get type; 44 | 45 | /// The internal blob parts as a JavaScript array of `ArrayBuffer`s. 46 | external JSArrayBuffer? get blobParts; 47 | 48 | /// The options used when the `Blob` was created. 49 | external JSObject? get options; 50 | } 51 | -------------------------------------------------------------------------------- /lib/core/utils/web_canvas_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:js_interop'; 3 | import 'package:web/web.dart'; 4 | 5 | /// Extension on [HTMLCanvasElement] to provide a method for asynchronously 6 | /// converting the canvas content to a [Blob]. 7 | /// 8 | /// The [toBlobAsync] method uses a [Completer] to handle the asynchronous 9 | /// operation of the `toBlob` JavaScript API, allowing Dart code to work 10 | /// seamlessly with the result. 11 | /// 12 | /// - [mimeType]: The MIME type of the resulting [Blob]. 13 | /// - Returns: A [Future] that completes with the resulting [Blob] or an 14 | /// error if the operation fails. 15 | extension CanvasToBlobFuture on HTMLCanvasElement { 16 | /// Converts the current canvas content into a `Blob` asynchronously. 17 | /// 18 | /// This method generates a `Blob` object that represents the content of the 19 | /// canvas in the specified MIME type format. 20 | /// 21 | /// [mimeType] specifies the desired MIME type for the resulting `Blob`. 22 | /// Common examples include `image/png` or `image/jpeg`. 23 | /// 24 | /// Returns a `Future` that completes with the generated `Blob` object. 25 | Future toBlobAsync(String mimeType) { 26 | final completer = Completer(); 27 | 28 | // Define JS interop callback explicitly: 29 | final jsCallback = (JSAny? blob) { 30 | if (blob != null) { 31 | completer.complete(blob as Blob); 32 | } else { 33 | completer.completeError('toBlob failed'); 34 | } 35 | }.toJS; 36 | 37 | toBlob(jsCallback, mimeType); 38 | 39 | return completer.future; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/pro_video_editor.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: directives_ordering 2 | 3 | export 'core/models/video/progress_model.dart'; 4 | export 'core/models/video/editor_video_model.dart'; 5 | export 'core/models/video/export_transform_model.dart'; 6 | export 'core/models/video/render_video_model.dart'; 7 | export 'core/models/video/video_metadata_model.dart'; 8 | export 'pro_video_editor_platform_interface.dart'; 9 | export 'shared/utils/converters.dart'; 10 | 11 | /// Thumbnails 12 | export 'core/models/thumbnail/key_frames_configs.model.dart'; 13 | export 'core/models/thumbnail/thumbnail_box_fit.model.dart'; 14 | export 'core/models/thumbnail/thumbnail_configs.model.dart'; 15 | export 'core/models/thumbnail/thumbnail_format.model.dart'; 16 | -------------------------------------------------------------------------------- /lib/pro_video_editor_web.dart: -------------------------------------------------------------------------------- 1 | // In order to *not* need this ignore, consider extracting the "web" version 2 | // of your plugin as a separate package, instead of inlining it in the same 3 | // package as the core of your plugin. 4 | // ignore: avoid_web_libraries_in_flutter 5 | 6 | import 'dart:async'; 7 | import 'dart:typed_data'; 8 | 9 | import 'package:flutter_web_plugins/flutter_web_plugins.dart'; 10 | import 'package:pro_video_editor/core/models/video/progress_model.dart'; 11 | import 'package:web/web.dart' as web; 12 | 13 | import '/core/services/web/web_manager.dart'; 14 | import 'core/models/thumbnail/key_frames_configs.model.dart'; 15 | import 'core/models/thumbnail/thumbnail_configs.model.dart'; 16 | import 'core/models/video/editor_video_model.dart'; 17 | import 'core/models/video/render_video_model.dart'; 18 | import 'core/models/video/video_metadata_model.dart'; 19 | import 'pro_video_editor_platform_interface.dart'; 20 | 21 | /// A web implementation of the ProVideoEditorPlatform of the ProVideoEditor 22 | /// plugin. 23 | class ProVideoEditorWeb extends ProVideoEditor { 24 | /// Constructs a ProVideoEditorWeb 25 | ProVideoEditorWeb(); 26 | 27 | final WebManager _manager = WebManager(); 28 | 29 | /// Registers the web implementation of the ProVideoEditor platform interface. 30 | static void registerWith(Registrar registrar) { 31 | ProVideoEditor.instance = ProVideoEditorWeb(); 32 | } 33 | 34 | /// Returns a [String] containing the version of the platform. 35 | @override 36 | Future getPlatformVersion() async { 37 | final version = web.window.navigator.userAgent; 38 | return version; 39 | } 40 | 41 | @override 42 | Future getMetadata(EditorVideo value) async { 43 | return _manager.getMetadata(value); 44 | } 45 | 46 | @override 47 | Future> getThumbnails(ThumbnailConfigs value) { 48 | return _manager.getThumbnails( 49 | value, 50 | onProgress: (progress) => _updateProgress(value.id, progress), 51 | ); 52 | } 53 | 54 | @override 55 | Future> getKeyFrames(KeyFramesConfigs value) { 56 | return _manager.getKeyFrames( 57 | value, 58 | onProgress: (progress) => _updateProgress(value.id, progress), 59 | ); 60 | } 61 | 62 | @override 63 | Future renderVideo(RenderVideoModel value) { 64 | throw UnimplementedError('renderVideo() has not been implemented.'); 65 | } 66 | 67 | @override 68 | void initializeStream() {} 69 | 70 | void _updateProgress(String taskId, double progress) { 71 | progressCtrl.add(ProgressModel(id: taskId, progress: progress)); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/shared/utils/converters.dart: -------------------------------------------------------------------------------- 1 | // Flutter imports: 2 | import 'package:flutter/services.dart'; 3 | 4 | // Package imports: 5 | import 'package:http/http.dart' as http; 6 | 7 | import '/core/platform/io/io_helper.dart'; 8 | 9 | /// Loads an image asset as a Uint8List. 10 | /// 11 | /// This function allows you to load an image asset from the app's assets 12 | /// directory and convert it into a Uint8List for further use. 13 | /// 14 | /// Parameters: 15 | /// - `assetPath`: A String representing the asset path of the image to be 16 | /// loaded. 17 | /// 18 | /// Returns: 19 | /// A Future that resolves to a Uint8List containing the image data. 20 | /// 21 | /// Example Usage: 22 | /// ```dart 23 | /// final Uint8List imageBytes = await loadAssetImageAsUint8List('assets/image.png'); 24 | /// ``` 25 | Future loadAssetImageAsUint8List(String assetPath) async { 26 | // Load the asset as a ByteData 27 | final ByteData data = await rootBundle.load(assetPath); 28 | 29 | // Convert the ByteData to a Uint8List 30 | final Uint8List uint8List = data.buffer.asUint8List(); 31 | 32 | return uint8List; 33 | } 34 | 35 | /// Fetches an image from a network URL as a Uint8List. 36 | /// 37 | /// This function allows you to fetch an image from a network URL and convert 38 | /// it into a Uint8List for further use. 39 | /// 40 | /// Parameters: 41 | /// - `imageUrl`: A String representing the network URL of the image to be 42 | /// fetched. 43 | /// 44 | /// Returns: 45 | /// A Future that resolves to a Uint8List containing the image data. 46 | /// 47 | /// Example Usage: 48 | /// ```dart 49 | /// final Uint8List imageBytes = await fetchImageAsUint8List('https://example.com/image.jpg'); 50 | /// ``` 51 | Future fetchImageAsUint8List(String imageUrl) async { 52 | final response = await http.get(Uri.parse(imageUrl)); 53 | 54 | if (response.statusCode == 200) { 55 | // Convert the response body to a Uint8List 56 | final Uint8List uint8List = Uint8List.fromList(response.bodyBytes); 57 | return uint8List; 58 | } else { 59 | throw Exception('Failed to load image: $imageUrl'); 60 | } 61 | } 62 | 63 | /// Reads a file as a Uint8List. 64 | /// 65 | /// This function allows you to read the contents of a file and convert it into 66 | /// a Uint8List for further use. 67 | /// 68 | /// Parameters: 69 | /// - `file`: A File object representing the image file to be read. 70 | /// 71 | /// Returns: 72 | /// A Future that resolves to a Uint8List containing the file's data. 73 | /// 74 | /// Example Usage: 75 | /// ```dart 76 | /// final File imageFile = File('path/to/image.png'); 77 | /// final Uint8List fileBytes = await readFileAsUint8List(imageFile); 78 | /// ``` 79 | Future readFileAsUint8List(File file) async { 80 | try { 81 | // Read the file as bytes 82 | final Uint8List uint8List = await file.readAsBytes(); 83 | 84 | return uint8List; 85 | } catch (e) { 86 | throw Exception('Failed to read file: ${file.path}'); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/shared/utils/file_constructor_utils.dart: -------------------------------------------------------------------------------- 1 | import '/core/platform/io/io_helper.dart'; 2 | 3 | /// Converts a given dynamic input into a `File` instance. 4 | /// 5 | /// This function ensures that the provided input is either: 6 | /// - A `String` representing a file path, which is then converted into a 7 | /// `File`. 8 | /// - An existing `File` instance, which is returned as is. 9 | /// 10 | /// Throws an [ArgumentError] if the input is neither a `String` nor a `File`. 11 | /// 12 | /// Example usage: 13 | /// ```dart 14 | /// // Converts String to File 15 | /// File file1 = ensureFileInstance('path/to/file.txt'); 16 | /// // Returns existing File 17 | /// File file2 = ensureFileInstance(existingFile); 18 | /// ``` 19 | /// 20 | /// @param [file] A `String` (file path) or a `File` instance. 21 | /// 22 | /// @returns A `File` instance corresponding to the given input. 23 | /// 24 | /// @throws [ArgumentError] If the input is neither a `String` nor a `File`. 25 | File ensureFileInstance(dynamic file) { 26 | if (file is String) { 27 | return File(file); 28 | } else if (file is File) { 29 | return file; 30 | } 31 | 32 | throw ArgumentError( 33 | 'Only type `File` or `String` which is the path from the file is ' 34 | 'allowed!', 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /lib/shared/utils/parser/double_parser.dart: -------------------------------------------------------------------------------- 1 | /// Safely parses a [dynamic] value to a [double]. 2 | /// 3 | /// This function attempts to convert the provided [value] to a [double]. 4 | /// If the [value] is `null`, an invalid number, or cannot be parsed, 5 | /// the [fallback] value is returned instead. 6 | /// 7 | /// - Parameters: 8 | /// - [value]: A [dynamic] value that is expected to be convertible to a 9 | /// [double]. 10 | /// - [fallback]: A [double] value to return if parsing fails or if [value] 11 | /// is `null`. 12 | /// Defaults to 0 if not provided. 13 | /// 14 | /// - Returns: 15 | /// A [double] representation of the [value] if parsing succeeds, or the 16 | /// [fallback] value if it fails. 17 | /// 18 | /// - Example: 19 | /// ```dart 20 | /// safeParseDouble('3.14'); // returns 3.14 21 | /// safeParseDouble(null); // returns 0 (fallback) 22 | /// safeParseDouble('abc', fallback: 1.5); // returns 1.5 (fallback) 23 | /// safeParseDouble(10); // returns 10.0 (automatic conversion from int) 24 | /// ``` 25 | double safeParseDouble(dynamic value, {double fallback = 0}) { 26 | return double.tryParse((value ?? fallback).toString()) ?? fallback; 27 | } 28 | 29 | /// Attempts to parse a [dynamic] value to a [double]. 30 | /// 31 | /// This function tries to convert the provided [value] to a [double]. 32 | /// If the [value] cannot be parsed or is invalid, it returns `null`. 33 | /// 34 | /// - Parameters: 35 | /// - [value]: A [dynamic] value that is expected to be convertible to a 36 | /// [double]. 37 | /// 38 | /// - Returns: 39 | /// A [double] if the parsing is successful, or `null` if parsing fails. 40 | /// 41 | /// - Example: 42 | /// ```dart 43 | /// tryParseDouble('3.14'); // returns 3.14 44 | /// tryParseDouble('abc'); // returns null 45 | /// tryParseDouble(10); // returns 10.0 (automatic conversion from int) 46 | /// tryParseDouble(null); // throws error (null cannot be converted to String) 47 | /// ``` 48 | double? tryParseDouble(dynamic value) { 49 | return double.tryParse(value.toString()); 50 | } 51 | -------------------------------------------------------------------------------- /lib/shared/utils/parser/int_parser.dart: -------------------------------------------------------------------------------- 1 | import 'package:pro_video_editor/shared/utils/parser/double_parser.dart'; 2 | 3 | /// Safely parses a [dynamic] value to an [int]. 4 | /// 5 | /// This function attempts to convert the provided [value] to an [int]. 6 | /// If the [value] is `null`, an invalid number, or cannot be parsed, 7 | /// the [fallback] value is returned instead. 8 | /// 9 | /// - Parameters: 10 | /// - [value]: A [dynamic] value that is expected to be convertible to an 11 | /// [int]. 12 | /// - [fallback]: An [int] value to return if parsing fails or if [value] is 13 | /// `null`. 14 | /// Defaults to 0 if not provided. 15 | /// 16 | /// - Returns: 17 | /// An [int] representation of the [value] if parsing succeeds, or the 18 | /// [fallback] value if it fails. 19 | /// 20 | /// - Example: 21 | /// ```dart 22 | /// safeParseInt('123'); // returns 123 23 | /// safeParseInt(null); // returns 0 (fallback) 24 | /// safeParseInt('abc', fallback: 10); // returns 10 (fallback) 25 | /// safeParseInt(15); // returns 15 (automatic conversion from int) 26 | /// ``` 27 | int safeParseInt(dynamic value, {int fallback = 0}) { 28 | return int.tryParse((value ?? fallback).toString()) ?? 29 | safeParseDouble(value, fallback: fallback.toDouble()).toInt(); 30 | } 31 | 32 | /// Attempts to parse a [dynamic] value to an [int]. 33 | /// 34 | /// This function tries to convert the provided [value] to an [int]. 35 | /// If the [value] cannot be parsed, it returns `null`. 36 | /// 37 | /// - Parameters: 38 | /// - [value]: A [dynamic] value that is expected to be convertible to an 39 | /// [int]. 40 | /// 41 | /// - Returns: 42 | /// An [int] if the parsing is successful, or `null` if parsing fails. 43 | /// 44 | /// - Example: 45 | /// ```dart 46 | /// tryParseInt('123'); // returns 123 47 | /// tryParseInt('abc'); // returns null 48 | /// tryParseInt(42); // returns 42 (automatic conversion from int) 49 | /// tryParseInt(null); // returns null 50 | /// ``` 51 | int? tryParseInt(dynamic value) { 52 | return int.tryParse(value.toString()); 53 | } 54 | -------------------------------------------------------------------------------- /lib/shared/utils/parser/size_parser.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'double_parser.dart'; 3 | 4 | /// Safely parses a [Map] representation of a size to a [Size] object. 5 | /// 6 | /// This function attempts to convert the provided [map] to a [Size] object. 7 | /// If the [map] is `null`, missing required keys (`width` and `height`), or 8 | /// contains invalid values, a [fallback] size is returned instead. 9 | /// 10 | /// - Parameters: 11 | /// - [map]: A [Map] that is expected to contain `width` and `height` keys, 12 | /// where their values can be converted to [double]. 13 | /// - [fallback]: A [Size] value to return if parsing fails or if [map] is 14 | /// `null`. 15 | /// Defaults to [Size.zero] if not provided. 16 | /// 17 | /// - Returns: 18 | /// A [Size] object constructed from the [map] if parsing succeeds, or the 19 | /// [fallback] size if it fails. 20 | /// 21 | /// - Example: 22 | /// ```dart 23 | /// safeParseSize({'width': 200, 'height': 100}); // returns Size(200.0, 100.0) 24 | /// safeParseSize(null); // returns Size.zero (fallback) 25 | /// safeParseSize({'width': 'abc', 'height': 50}, fallback: Size(10, 10)); 26 | /// // returns Size(10.0, 10.0) (fallback) 27 | /// ``` 28 | Size safeParseSize(Map? map, {Size fallback = Size.zero}) { 29 | if (map == null) return fallback; 30 | 31 | return Size( 32 | safeParseDouble(map['width'] ?? map['w'], fallback: fallback.width), 33 | safeParseDouble(map['height'] ?? map['h'], fallback: fallback.height), 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /linux/include/pro_video_editor/pro_video_editor_plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_PLUGIN_PRO_VIDEO_EDITOR_PLUGIN_H_ 2 | #define FLUTTER_PLUGIN_PRO_VIDEO_EDITOR_PLUGIN_H_ 3 | 4 | #include 5 | 6 | G_BEGIN_DECLS 7 | 8 | #ifdef FLUTTER_PLUGIN_IMPL 9 | #define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) 10 | #else 11 | #define FLUTTER_PLUGIN_EXPORT 12 | #endif 13 | 14 | typedef struct _ProVideoEditorPlugin ProVideoEditorPlugin; 15 | typedef struct { 16 | GObjectClass parent_class; 17 | } ProVideoEditorPluginClass; 18 | 19 | FLUTTER_PLUGIN_EXPORT GType pro_video_editor_plugin_get_type(); 20 | 21 | FLUTTER_PLUGIN_EXPORT void pro_video_editor_plugin_register_with_registrar( 22 | FlPluginRegistrar* registrar); 23 | 24 | G_END_DECLS 25 | 26 | #endif // FLUTTER_PLUGIN_PRO_VIDEO_EDITOR_PLUGIN_H_ 27 | -------------------------------------------------------------------------------- /linux/pro_video_editor_plugin_private.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "include/pro_video_editor/pro_video_editor_plugin.h" 4 | 5 | // This file exposes some plugin internals for unit testing. See 6 | // https://github.com/flutter/flutter/issues/88724 for current limitations 7 | // in the unit-testable API. 8 | 9 | // Handles the getPlatformVersion method call. 10 | FlMethodResponse *get_platform_version(); 11 | -------------------------------------------------------------------------------- /linux/src/thumbnail_generator.h: -------------------------------------------------------------------------------- 1 | // src/video_metadata.h 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | namespace pro_video_editor { 8 | 9 | void HandleGenerateThumbnails( 10 | const flutter::EncodableMap& args, 11 | std::unique_ptr> result); 12 | 13 | } // namespace pro_video_editor 14 | -------------------------------------------------------------------------------- /linux/src/video_metadata.h: -------------------------------------------------------------------------------- 1 | // src/video_metadata.h 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | namespace pro_video_editor { 8 | 9 | void HandleGetMetadata( 10 | const flutter::EncodableMap& args, 11 | std::unique_ptr> result); 12 | 13 | } // namespace pro_video_editor 14 | -------------------------------------------------------------------------------- /linux/test/pro_video_editor_plugin_test.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "include/pro_video_editor/pro_video_editor_plugin.h" 6 | #include "pro_video_editor_plugin_private.h" 7 | 8 | // This demonstrates a simple unit test of the C portion of this plugin's 9 | // implementation. 10 | // 11 | // Once you have built the plugin's example app, you can run these tests 12 | // from the command line. For instance, for a plugin called my_plugin 13 | // built for x64 debug, run: 14 | // $ build/linux/x64/debug/plugins/my_plugin/my_plugin_test 15 | 16 | namespace pro_video_editor { 17 | namespace test { 18 | 19 | TEST(ProVideoEditorPlugin, GetPlatformVersion) { 20 | g_autoptr(FlMethodResponse) response = get_platform_version(); 21 | ASSERT_NE(response, nullptr); 22 | ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); 23 | FlValue* result = fl_method_success_response_get_result( 24 | FL_METHOD_SUCCESS_RESPONSE(response)); 25 | ASSERT_EQ(fl_value_get_type(result), FL_VALUE_TYPE_STRING); 26 | // The full string varies, so just validate that it has the right format. 27 | EXPECT_THAT(fl_value_get_string(result), testing::StartsWith("Linux ")); 28 | } 29 | 30 | } // namespace test 31 | } // namespace pro_video_editor 32 | -------------------------------------------------------------------------------- /macos/Classes/src/core/constants/LoggingConstants.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct Tags { 4 | static let package = "ProVideoEditor" 5 | static let render = "\(package)-Renderer" 6 | static let thumbnail = "\(package)-Thumbnail" 7 | } 8 | -------------------------------------------------------------------------------- /macos/Classes/src/features/render/helpers/ApplyAudio.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | import Foundation 3 | 4 | public func applyAudio( 5 | from asset: AVAsset, 6 | to composition: AVMutableComposition, 7 | timeRange: CMTimeRange, 8 | enableAudio: Bool 9 | ) async { 10 | guard enableAudio else { 11 | print("[\(Tags.render)] Removing audio from export") 12 | return 13 | } 14 | 15 | do { 16 | let audioTracks: [AVAssetTrack] 17 | if #available(macOS 13.0, *) { 18 | audioTracks = try await asset.loadTracks(withMediaType: .audio) 19 | } else { 20 | audioTracks = asset.tracks(withMediaType: .audio) 21 | } 22 | 23 | if let audioTrack = audioTracks.first { 24 | if let audioCompositionTrack = composition.addMutableTrack( 25 | withMediaType: .audio, 26 | preferredTrackID: kCMPersistentTrackID_Invalid 27 | ) { 28 | try? audioCompositionTrack.insertTimeRange(timeRange, of: audioTrack, at: .zero) 29 | } 30 | } 31 | } catch { 32 | print("[\(Tags.render)] ⚠️ Failed to load audio tracks: \(error.localizedDescription)") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /macos/Classes/src/features/render/helpers/ApplyBitrate.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | import Foundation 3 | 4 | public func applyBitrate(requestedBitrate: Int?, presetHint: String? = nil) -> String { 5 | if let bitrate = requestedBitrate { 6 | print("[\(Tags.render)] Requested Bitrate: \(bitrate) bps") 7 | print("[\(Tags.render)] ⚠️ AVAssetExportSession does not support custom bitrate directly.") 8 | } 9 | 10 | if let bitrate = requestedBitrate { 11 | if bitrate >= 50_000_000 { 12 | if #available(macOS 12.1, *) { 13 | return AVAssetExportPresetHEVC7680x4320 // 8K 14 | } 15 | } else if bitrate >= 40_000_000 { 16 | if #available(macOS 10.13, *) { 17 | return AVAssetExportPresetHEVC3840x2160 // 4K HEVC 18 | } else { 19 | return AVAssetExportPreset3840x2160 // 4K H264 20 | } 21 | } else if bitrate >= 30_000_000 { 22 | if #available(macOS 10.13, *) { 23 | return AVAssetExportPresetHEVC1920x1080 // 1080p HEVC 24 | } else { 25 | return AVAssetExportPreset1920x1080 // 1080p H264 26 | } 27 | } else if bitrate >= 20_000_000 { 28 | if #available(macOS 10.13, *) { 29 | return AVAssetExportPresetHEVCHighestQuality 30 | } else { 31 | return AVAssetExportPresetHighestQuality 32 | } 33 | } else if bitrate >= 10_000_000 { 34 | return AVAssetExportPresetHighestQuality 35 | } else if bitrate >= 7_000_000 { 36 | return AVAssetExportPreset1920x1080 37 | } else if bitrate >= 5_000_000 { 38 | return AVAssetExportPreset1280x720 39 | } else if bitrate >= 3_000_000 { 40 | return AVAssetExportPreset960x540 41 | } else if bitrate >= 2_000_000 { 42 | return AVAssetExportPreset640x480 43 | } else if bitrate >= 1_000_000 { 44 | return AVAssetExportPresetMediumQuality 45 | } else { 46 | return AVAssetExportPresetLowQuality 47 | } 48 | } 49 | 50 | return AVAssetExportPresetHighestQuality 51 | } 52 | -------------------------------------------------------------------------------- /macos/Classes/src/features/render/helpers/ApplyBlur.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | 3 | func applyBlur( 4 | config: inout VideoCompositorConfig, 5 | sigma: Double? 6 | ) { 7 | config.blurSigma = (sigma ?? 0) * 2.5 8 | 9 | if sigma == nil || sigma == 0 { return } 10 | 11 | print("[\(Tags.render)] Applying blur: sigma=\(sigma!)") 12 | } 13 | -------------------------------------------------------------------------------- /macos/Classes/src/features/render/helpers/ApplyCrop.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | import CoreGraphics 3 | 4 | func applyCrop( 5 | config: inout VideoCompositorConfig, 6 | naturalSize: CGSize, 7 | rotateTurns: Int?, 8 | cropX: Int?, 9 | cropY: Int?, 10 | cropWidth: Int?, 11 | cropHeight: Int? 12 | ) -> CGSize { 13 | let x = CGFloat(cropX ?? 0) 14 | let y = CGFloat(cropY ?? 0) 15 | let width = CGFloat(cropWidth ?? Int(naturalSize.width) - Int(x)) 16 | let height = CGFloat(cropHeight ?? Int(naturalSize.height) - Int(y)) 17 | 18 | config.cropX = x 19 | config.cropY = y 20 | config.cropWidth = width 21 | config.cropHeight = height 22 | 23 | if cropX != 0 || cropY != 0 || cropWidth != nil || cropHeight != nil { 24 | print( 25 | "[\(Tags.render)] Applying crop: x=\(Int(x)) y=\(Int(y)) width=\(Int(width)) height=\(Int(height))" 26 | ) 27 | } 28 | 29 | let cropRect = CGRect(x: x, y: y, width: width, height: height) 30 | 31 | let turns = 4 - (rotateTurns ?? 0) % 4 32 | 33 | let isPortraitRotation = turns % 2 == 1 34 | return isPortraitRotation 35 | ? CGSize(width: cropRect.size.height, height: cropRect.size.width) 36 | : cropRect.size 37 | } 38 | -------------------------------------------------------------------------------- /macos/Classes/src/features/render/helpers/ApplyFlip.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | 3 | func applyFlip( 4 | config: inout VideoCompositorConfig, 5 | flipX: Bool, 6 | flipY: Bool 7 | ) { 8 | config.flipX = flipX 9 | config.flipY = flipY 10 | 11 | if !flipX && !flipY { return } 12 | 13 | print("[\(Tags.render)] Applying flip: flipX=\(flipX), flipY=\(flipY)") 14 | } 15 | -------------------------------------------------------------------------------- /macos/Classes/src/features/render/helpers/ApplyImageLayer.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | import AppKit 3 | import CoreImage 4 | 5 | func applyImageLayer( 6 | config: inout VideoCompositorConfig, 7 | imageData: Data? 8 | ) { 9 | config.overlayImage = imageData 10 | guard imageData != nil else { return } 11 | 12 | print("[\(Tags.render)] Applying overlay image") 13 | } 14 | -------------------------------------------------------------------------------- /macos/Classes/src/features/render/helpers/ApplyPlaybackSpeed.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | 3 | public func applyPlaybackSpeed( 4 | composition: AVMutableComposition, 5 | speed: Float? 6 | ) { 7 | guard let speed = speed, speed > 0, speed != 1 else { return } 8 | 9 | print("[\(Tags.render)] Applying playback speed: \(speed)x") 10 | 11 | let tracks = composition.tracks 12 | for track in tracks { 13 | let range = CMTimeRange(start: .zero, duration: track.timeRange.duration) 14 | let scaledDuration = CMTimeMultiplyByFloat64(range.duration, multiplier: 1 / Double(speed)) 15 | track.scaleTimeRange(range, toDuration: scaledDuration) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /macos/Classes/src/features/render/helpers/ApplyRotation.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | 3 | func applyRotation( 4 | config: inout VideoCompositorConfig, 5 | rotateTurns: Int? 6 | ) { 7 | let normalizedTurns = ((rotateTurns ?? 0) % 4 + 4) % 4 8 | let turns = (4 - normalizedTurns) % 4 9 | let degrees = turns * 90 10 | let radians = CGFloat(Double(degrees) * .pi / 180) 11 | 12 | config.rotateRadians = radians 13 | config.rotateTurns = turns 14 | 15 | if turns == 0 { return } 16 | print("[\(Tags.render)] Applying rotation: \(degrees) degrees") 17 | } 18 | -------------------------------------------------------------------------------- /macos/Classes/src/features/render/helpers/ApplyScale.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | 3 | func applyScale( 4 | config: inout VideoCompositorConfig, 5 | scaleX: Float?, 6 | scaleY: Float? 7 | ) { 8 | let x = CGFloat(scaleX ?? 1.0) 9 | let y = CGFloat(scaleY ?? 1.0) 10 | 11 | config.scaleX = x 12 | config.scaleY = y 13 | 14 | if x != 1.0 || y != 1.0 { 15 | print("[\(Tags.render)] Applying scale: scaleX=\(x), scaleY=\(y)") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /macos/Classes/src/features/render/helpers/ApplyTrim.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | 3 | public func applyTrim( 4 | asset: AVAsset, 5 | startUs: Int64?, 6 | endUs: Int64? 7 | ) async -> CMTimeRange { 8 | // Load duration 9 | let duration: CMTime 10 | if #available(macOS 13.0, *) { 11 | do { 12 | duration = try await asset.load(.duration) 13 | } catch { 14 | return CMTimeRange(start: .zero, duration: .positiveInfinity) 15 | } 16 | } else { 17 | duration = asset.duration 18 | } 19 | 20 | // Prepare start and end CMTime 21 | let start = startUs != nil 22 | ? CMTime(value: startUs!, timescale: 1_000_000) 23 | : .zero 24 | 25 | let end = endUs != nil 26 | ? CMTime(value: endUs!, timescale: 1_000_000) 27 | : duration 28 | 29 | // Logging in ms for easier debugging 30 | let startMs = Int64(start.seconds * 1000) 31 | let endMs = Int64(end.seconds * 1000) 32 | print("[\(Tags.render)] Applying trim: start=\(startMs) ms, end=\(endMs) ms") 33 | 34 | return CMTimeRange(start: start, end: end) 35 | } 36 | -------------------------------------------------------------------------------- /macos/Classes/src/features/render/models/VideoCompositorConfig.swift: -------------------------------------------------------------------------------- 1 | import CoreImage 2 | 3 | struct VideoCompositorConfig { 4 | var blurSigma: Double = 0.0 5 | var overlayImage: Data? = nil 6 | 7 | var rotateRadians: Double = 0.0 8 | var rotateTurns: Int = 0 9 | var flipX: Bool = false 10 | var flipY: Bool = false 11 | 12 | var cropX: CGFloat = 0.0 13 | var cropY: CGFloat = 0.0 14 | var cropWidth: CGFloat? = nil 15 | var cropHeight: CGFloat? = nil 16 | 17 | var scaleX: CGFloat = 1.0 18 | var scaleY: CGFloat = 1.0 19 | 20 | var lutData: Data? = nil 21 | var lutSize: Int = 33 22 | 23 | var videoRotationDegrees: Double = 0.0 24 | var shouldApplyOrientationCorrection: Bool = false 25 | 26 | var preferredTransform: CGAffineTransform = .identity 27 | var originalNaturalSize: CGSize = .zero 28 | } 29 | -------------------------------------------------------------------------------- /macos/Classes/src/features/render/utils/VideoMimeUtils.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | 3 | func mapFormatToMimeType(format: String) -> AVFileType { 4 | switch format { 5 | case "mp4": 6 | return .mp4 7 | case "mov": 8 | return .mov 9 | default: 10 | return .mp4 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /macos/Resources/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyTrackingDomains 6 | 7 | NSPrivacyCollectedDataTypes 8 | 9 | NSPrivacyTracking 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /macos/pro_video_editor.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint pro_video_editor.podspec` to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'pro_video_editor' 7 | s.version = '0.0.1' 8 | s.summary = 'A new Flutter plugin project.' 9 | s.description = <<-DESC 10 | A new Flutter plugin project. 11 | DESC 12 | s.homepage = 'https://waio.ch' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'WAIO Frei Applications' => 'info@waio.ch' } 15 | 16 | s.source = { :path => '.' } 17 | s.source_files = 'Classes/**/*' 18 | 19 | # If your plugin requires a privacy manifest, for example if it collects user 20 | # data, update the PrivacyInfo.xcprivacy file to describe your plugin's 21 | # privacy impact, and then uncomment this line. For more information, 22 | # see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files 23 | # s.resource_bundles = {'pro_video_editor_privacy' => ['Resources/PrivacyInfo.xcprivacy']} 24 | 25 | s.dependency 'FlutterMacOS' 26 | 27 | s.platform = :osx, '10.15' 28 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } 29 | s.swift_version = '5.0' 30 | end 31 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: pro_video_editor 2 | description: "A Flutter video editor: Seamlessly enhance your videos with user-friendly editing features." 3 | version: 0.1.6 4 | homepage: https://github.com/hm21/pro_video_editor/ 5 | repository: https://github.com/hm21/pro_video_editor/ 6 | documentation: https://github.com/hm21/pro_video_editor/ 7 | funding: 8 | - https://github.com/sponsors/hm21 9 | 10 | 11 | topics: 12 | - video-editor 13 | - video 14 | - movie 15 | - editor 16 | 17 | screenshots: 18 | - description: "Showcase from the video editor with the grounded design" 19 | path: assets/showcase.jpg 20 | - description: "Showcase from the video editor with simple design" 21 | path: assets/showcase1.jpg 22 | 23 | environment: 24 | sdk: '>=3.4.0 <4.0.0' 25 | flutter: ">=3.29.0" 26 | 27 | dependencies: 28 | flutter: 29 | sdk: flutter 30 | flutter_web_plugins: 31 | sdk: flutter 32 | 33 | plugin_platform_interface: ^2.1.8 34 | http: ^1.4.0 35 | mime: ^2.0.0 36 | web: ^1.1.1 37 | 38 | dev_dependencies: 39 | flutter_test: 40 | sdk: flutter 41 | 42 | build_runner: ^2.4.8 43 | flutter_lints: ^5.0.0 44 | import_sorter: ^4.6.0 45 | mockito: ^5.4.6 46 | network_image_mock: ^2.1.1 47 | 48 | flutter: 49 | plugin: 50 | platforms: 51 | android: 52 | package: ch.waio.pro_video_editor 53 | pluginClass: ProVideoEditorPlugin 54 | ios: 55 | pluginClass: ProVideoEditorPlugin 56 | linux: 57 | pluginClass: ProVideoEditorPlugin 58 | macos: 59 | pluginClass: ProVideoEditorPlugin 60 | windows: 61 | pluginClass: ProVideoEditorPluginCApi 62 | web: 63 | pluginClass: ProVideoEditorWeb 64 | fileName: pro_video_editor_web.dart 65 | -------------------------------------------------------------------------------- /test/shared/utils/file_constructor_utils_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:mockito/mockito.dart'; 5 | import 'package:pro_video_editor/shared/utils/file_constructor_utils.dart'; 6 | 7 | class MockFile extends Mock implements File {} 8 | 9 | void main() { 10 | group('ensureFileInstance', () { 11 | test('returns File when given a String path', () { 12 | const filePath = 'test.txt'; 13 | final file = ensureFileInstance(filePath); 14 | expect(file, isA()); 15 | expect(file.path, filePath); 16 | }); 17 | 18 | test('returns the same File instance when given a File', () { 19 | final mockFile = MockFile(); 20 | final result = ensureFileInstance(mockFile); 21 | expect(result, same(mockFile)); 22 | }); 23 | 24 | test('throws ArgumentError when given an int', () { 25 | expect(() => ensureFileInstance(123), throwsArgumentError); 26 | }); 27 | 28 | test('throws ArgumentError when given null', () { 29 | expect(() => ensureFileInstance(null), throwsArgumentError); 30 | }); 31 | 32 | test('throws ArgumentError when given an unsupported type', () { 33 | expect(() => ensureFileInstance([]), throwsArgumentError); 34 | expect(() => ensureFileInstance({}), throwsArgumentError); 35 | expect(() => ensureFileInstance(3.14), throwsArgumentError); 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /test/shared/utils/parser/double_parser_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:pro_video_editor/shared/utils/parser/double_parser.dart'; 3 | 4 | void main() { 5 | group('safeParseDouble', () { 6 | test('parses valid double string', () { 7 | expect(safeParseDouble('3.14'), equals(3.14)); 8 | }); 9 | 10 | test('parses valid int string', () { 11 | expect(safeParseDouble('42'), equals(42.0)); 12 | }); 13 | 14 | test('parses int value', () { 15 | expect(safeParseDouble(10), equals(10.0)); 16 | }); 17 | 18 | test('returns fallback for null', () { 19 | expect(safeParseDouble(null), equals(0)); 20 | expect(safeParseDouble(null, fallback: 1.5), equals(1.5)); 21 | }); 22 | 23 | test('returns fallback for invalid string', () { 24 | expect(safeParseDouble('abc'), equals(0)); 25 | expect(safeParseDouble('abc', fallback: 2.2), equals(2.2)); 26 | }); 27 | 28 | test('parses already double value', () { 29 | expect(safeParseDouble(5.5), equals(5.5)); 30 | }); 31 | 32 | test('parses string with spaces', () { 33 | expect(safeParseDouble(' 7.7 '), equals(7.7)); 34 | }); 35 | }); 36 | 37 | group('tryParseDouble', () { 38 | test('parses valid double string', () { 39 | expect(tryParseDouble('3.14'), equals(3.14)); 40 | }); 41 | 42 | test('parses valid int string', () { 43 | expect(tryParseDouble('42'), equals(42.0)); 44 | }); 45 | 46 | test('parses int value', () { 47 | expect(tryParseDouble(10), equals(10.0)); 48 | }); 49 | 50 | test('returns null for invalid string', () { 51 | expect(tryParseDouble('abc'), isNull); 52 | }); 53 | 54 | test('parses already double value', () { 55 | expect(tryParseDouble(5.5), equals(5.5)); 56 | }); 57 | 58 | test('parses string with spaces', () { 59 | expect(tryParseDouble(' 7.7 '), equals(7.7)); 60 | }); 61 | 62 | test('returns null when value is null', () { 63 | expect(tryParseDouble(null), isNull); 64 | }); 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /test/shared/utils/parser/int_parser_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:pro_video_editor/shared/utils/parser/int_parser.dart'; 3 | 4 | void main() { 5 | group('safeParseInt', () { 6 | test('parses valid int string', () { 7 | expect(safeParseInt('123'), 123); 8 | }); 9 | 10 | test('returns fallback for null', () { 11 | expect(safeParseInt(null), 0); 12 | expect(safeParseInt(null, fallback: 7), 7); 13 | }); 14 | 15 | test('returns fallback for invalid string', () { 16 | expect(safeParseInt('abc'), 0); 17 | expect(safeParseInt('abc', fallback: 5), 5); 18 | }); 19 | 20 | test('returns int for int input', () { 21 | expect(safeParseInt(15), 15); 22 | expect(safeParseInt(-42), -42); 23 | }); 24 | 25 | test('parses double string by truncating', () { 26 | expect(safeParseInt('12.7'), 12); 27 | expect(safeParseInt('99.99', fallback: 1), 99); 28 | }); 29 | 30 | test('parses double input by truncating', () { 31 | expect(safeParseInt(7.8), 7); 32 | expect(safeParseInt(-3.2), -3); 33 | }); 34 | 35 | test('returns fallback for completely invalid input', () { 36 | expect(safeParseInt({}, fallback: 11), 11); 37 | expect(safeParseInt([], fallback: 22), 22); 38 | }); 39 | }); 40 | 41 | group('tryParseInt', () { 42 | test('parses valid int string', () { 43 | expect(tryParseInt('123'), 123); 44 | }); 45 | 46 | test('returns null for null', () { 47 | expect(tryParseInt(null), null); 48 | }); 49 | 50 | test('returns null for invalid string', () { 51 | expect(tryParseInt('abc'), null); 52 | }); 53 | 54 | test('returns int for int input', () { 55 | expect(tryParseInt(42), 42); 56 | expect(tryParseInt(-7), -7); 57 | }); 58 | 59 | test('returns null for double string', () { 60 | expect(tryParseInt('12.7'), null); 61 | }); 62 | 63 | test('returns null for double input', () { 64 | expect(tryParseInt(7.8), null); 65 | }); 66 | 67 | test('returns null for non-number input', () { 68 | expect(tryParseInt({}), null); 69 | expect(tryParseInt([]), null); 70 | }); 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /test/shared/utils/parser/size_parser_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:pro_video_editor/shared/utils/parser/size_parser.dart'; 4 | 5 | void main() { 6 | group('safeParseSize', () { 7 | test('parses valid width and height', () { 8 | final map = {'width': 200, 'height': 100}; 9 | final result = safeParseSize(map); 10 | expect(result, const Size(200.0, 100.0)); 11 | }); 12 | 13 | test('returns fallback for null map', () { 14 | const fallback = Size(10, 20); 15 | final result = safeParseSize(null, fallback: fallback); 16 | expect(result, fallback); 17 | }); 18 | 19 | test('returns Size.zero for null map if no fallback', () { 20 | final result = safeParseSize(null); 21 | expect(result, Size.zero); 22 | }); 23 | 24 | test('parses width and height as strings', () { 25 | final map = {'width': '50.5', 'height': '25.2'}; 26 | final result = safeParseSize(map); 27 | expect(result, const Size(50.5, 25.2)); 28 | }); 29 | 30 | test('uses fallback if width is invalid', () { 31 | const fallback = Size(1, 2); 32 | final map = {'width': 'abc', 'height': 10}; 33 | final result = safeParseSize(map, fallback: fallback); 34 | expect(result.width, fallback.width); 35 | expect(result.height, 10); 36 | }); 37 | 38 | test('uses fallback if height is invalid', () { 39 | const fallback = Size(3, 4); 40 | final map = {'width': 10, 'height': 'xyz'}; 41 | final result = safeParseSize(map, fallback: fallback); 42 | expect(result.width, 10); 43 | expect(result.height, fallback.height); 44 | }); 45 | 46 | test('uses fallback if both width and height are missing', () { 47 | const fallback = Size(5, 6); 48 | Map? map = {}; 49 | final result = safeParseSize(map, fallback: fallback); 50 | expect(result, fallback); 51 | }); 52 | 53 | test('parses using "w" and "h" keys', () { 54 | final map = {'w': 12, 'h': 34}; 55 | final result = safeParseSize(map); 56 | expect(result, const Size(12.0, 34.0)); 57 | }); 58 | 59 | test('uses fallback width if width is missing', () { 60 | const fallback = Size(7, 8); 61 | final map = {'height': 20}; 62 | final result = safeParseSize(map, fallback: fallback); 63 | expect(result.width, fallback.width); 64 | expect(result.height, 20.0); 65 | }); 66 | 67 | test('uses fallback height if height is missing', () { 68 | const fallback = Size(9, 10); 69 | final map = {'width': 30}; 70 | final result = safeParseSize(map, fallback: fallback); 71 | expect(result.width, 30.0); 72 | expect(result.height, fallback.height); 73 | }); 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/include/pro_video_editor/pro_video_editor_plugin_c_api.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_PLUGIN_PRO_VIDEO_EDITOR_PLUGIN_C_API_H_ 2 | #define FLUTTER_PLUGIN_PRO_VIDEO_EDITOR_PLUGIN_C_API_H_ 3 | 4 | #include 5 | 6 | #ifdef FLUTTER_PLUGIN_IMPL 7 | #define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) 8 | #else 9 | #define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) 10 | #endif 11 | 12 | #if defined(__cplusplus) 13 | extern "C" { 14 | #endif 15 | 16 | FLUTTER_PLUGIN_EXPORT void ProVideoEditorPluginCApiRegisterWithRegistrar( 17 | FlutterDesktopPluginRegistrarRef registrar); 18 | 19 | #if defined(__cplusplus) 20 | } // extern "C" 21 | #endif 22 | 23 | #endif // FLUTTER_PLUGIN_PRO_VIDEO_EDITOR_PLUGIN_C_API_H_ 24 | -------------------------------------------------------------------------------- /windows/pro_video_editor_plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_PLUGIN_PRO_VIDEO_EDITOR_PLUGIN_H_ 2 | #define FLUTTER_PLUGIN_PRO_VIDEO_EDITOR_PLUGIN_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace pro_video_editor { 10 | 11 | class ProVideoEditorPlugin : public flutter::Plugin { 12 | public: 13 | static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar); 14 | 15 | ProVideoEditorPlugin(); 16 | 17 | virtual ~ProVideoEditorPlugin(); 18 | 19 | // Disallow copy and assign. 20 | ProVideoEditorPlugin(const ProVideoEditorPlugin&) = delete; 21 | ProVideoEditorPlugin& operator=(const ProVideoEditorPlugin&) = delete; 22 | 23 | // Called when a method is called on this plugin's channel from Dart. 24 | void HandleMethodCall( 25 | const flutter::MethodCall &method_call, 26 | std::unique_ptr> result); 27 | }; 28 | 29 | } // namespace pro_video_editor 30 | 31 | #endif // FLUTTER_PLUGIN_PRO_VIDEO_EDITOR_PLUGIN_H_ 32 | -------------------------------------------------------------------------------- /windows/pro_video_editor_plugin_c_api.cpp: -------------------------------------------------------------------------------- 1 | #include "include/pro_video_editor/pro_video_editor_plugin_c_api.h" 2 | 3 | #include 4 | 5 | #include "pro_video_editor_plugin.h" 6 | 7 | void ProVideoEditorPluginCApiRegisterWithRegistrar( 8 | FlutterDesktopPluginRegistrarRef registrar) { 9 | pro_video_editor::ProVideoEditorPlugin::RegisterWithRegistrar( 10 | flutter::PluginRegistrarManager::GetInstance() 11 | ->GetRegistrar(registrar)); 12 | } 13 | -------------------------------------------------------------------------------- /windows/src/thumbnail_generator.h: -------------------------------------------------------------------------------- 1 | // src/video_metadata.h 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | namespace pro_video_editor { 8 | 9 | void HandleGenerateThumbnails( 10 | const flutter::EncodableMap& args, 11 | std::unique_ptr> result); 12 | 13 | } // namespace pro_video_editor 14 | -------------------------------------------------------------------------------- /windows/src/video_metadata.h: -------------------------------------------------------------------------------- 1 | // src/video_metadata.h 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | namespace pro_video_editor { 8 | 9 | void HandleGetMetadata( 10 | const flutter::EncodableMap& args, 11 | std::unique_ptr> result); 12 | 13 | } // namespace pro_video_editor 14 | -------------------------------------------------------------------------------- /windows/test/pro_video_editor_plugin_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "pro_video_editor_plugin.h" 12 | 13 | namespace pro_video_editor { 14 | namespace test { 15 | 16 | namespace { 17 | 18 | using flutter::EncodableMap; 19 | using flutter::EncodableValue; 20 | using flutter::MethodCall; 21 | using flutter::MethodResultFunctions; 22 | 23 | } // namespace 24 | 25 | TEST(ProVideoEditorPlugin, GetPlatformVersion) { 26 | ProVideoEditorPlugin plugin; 27 | // Save the reply value from the success callback. 28 | std::string result_string; 29 | plugin.HandleMethodCall( 30 | MethodCall("getPlatformVersion", std::make_unique()), 31 | std::make_unique>( 32 | [&result_string](const EncodableValue* result) { 33 | result_string = std::get(*result); 34 | }, 35 | nullptr, nullptr)); 36 | 37 | // Since the exact string varies by host, just ensure that it's a string 38 | // with the expected format. 39 | EXPECT_TRUE(result_string.rfind("Windows ", 0) == 0); 40 | } 41 | 42 | } // namespace test 43 | } // namespace pro_video_editor 44 | --------------------------------------------------------------------------------