├── .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 |
--------------------------------------------------------------------------------