@technotoil/image-video-editor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ImageVideoEditor.podspec +21 -0
- package/README.md +136 -0
- package/android/build.gradle +76 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +13 -0
- package/android/src/main/java/com/technotoil/image_videoeditor/FrameGrabberModule.kt +67 -0
- package/android/src/main/java/com/technotoil/image_videoeditor/MediaEditorModule.kt +548 -0
- package/android/src/main/java/com/technotoil/image_videoeditor/MediaFileUtils.kt +29 -0
- package/android/src/main/java/com/technotoil/image_videoeditor/MediaLibraryModule.kt +305 -0
- package/android/src/main/java/com/technotoil/image_videoeditor/MediaPackage.kt +26 -0
- package/android/src/main/java/com/technotoil/image_videoeditor/MediaPickerModule.kt +111 -0
- package/android/src/main/java/com/technotoil/image_videoeditor/MediaPlayerModule.kt +34 -0
- package/android/src/main/java/com/technotoil/image_videoeditor/RNCameraViewManager.kt +761 -0
- package/android/src/main/java/com/technotoil/image_videoeditor/RNVideoPreviewManager.kt +317 -0
- package/ios/PrivacyInfo.xcprivacy +38 -0
- package/ios/RNCameraViewManager.m +420 -0
- package/ios/RNFrameGrabber.m +61 -0
- package/ios/RNMediaEditor.m +905 -0
- package/ios/RNMediaLibrary.m +389 -0
- package/ios/RNMediaPicker.m +144 -0
- package/ios/RNMediaPlayer.m +73 -0
- package/ios/RNVideoPreviewManager.m +263 -0
- package/ios/frames/film_vintage.png +0 -0
- package/ios/frames/floral_gold.png +0 -0
- package/ios/frames/minimal_double.png +0 -0
- package/ios/frames/polaroid_white.png +0 -0
- package/ios/frames/watercolor_floral.png +0 -0
- package/lib/module/assets/frames/film_vintage.png +0 -0
- package/lib/module/assets/frames/floral_gold.png +0 -0
- package/lib/module/assets/frames/minimal_double.png +0 -0
- package/lib/module/assets/frames/polaroid_white.png +0 -0
- package/lib/module/assets/frames/watercolor_floral.png +0 -0
- package/lib/module/components/VideoEditor.js +156 -0
- package/lib/module/components/VideoEditor.js.map +1 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/native/CameraView.js +104 -0
- package/lib/module/native/CameraView.js.map +1 -0
- package/lib/module/native/FrameGrabber.js +13 -0
- package/lib/module/native/FrameGrabber.js.map +1 -0
- package/lib/module/native/MediaEditor.js +19 -0
- package/lib/module/native/MediaEditor.js.map +1 -0
- package/lib/module/native/MediaLibrary.js +37 -0
- package/lib/module/native/MediaLibrary.js.map +1 -0
- package/lib/module/native/MediaPicker.js +13 -0
- package/lib/module/native/MediaPicker.js.map +1 -0
- package/lib/module/native/MediaPlayer.js +13 -0
- package/lib/module/native/MediaPlayer.js.map +1 -0
- package/lib/module/native/VideoPreview.js +12 -0
- package/lib/module/native/VideoPreview.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/screens/CropScreen.js +1211 -0
- package/lib/module/screens/CropScreen.js.map +1 -0
- package/lib/module/screens/EditorScreen.js +5752 -0
- package/lib/module/screens/EditorScreen.js.map +1 -0
- package/lib/module/screens/ExportScreen.js +289 -0
- package/lib/module/screens/ExportScreen.js.map +1 -0
- package/lib/module/screens/GalleryScreen.js +505 -0
- package/lib/module/screens/GalleryScreen.js.map +1 -0
- package/lib/module/screens/PickScreen.js +1195 -0
- package/lib/module/screens/PickScreen.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/src/components/VideoEditor.d.ts +13 -0
- package/lib/typescript/src/index.d.ts +2 -0
- package/lib/typescript/src/native/CameraView.d.ts +23 -0
- package/lib/typescript/src/native/FrameGrabber.d.ts +2 -0
- package/lib/typescript/src/native/MediaEditor.d.ts +3 -0
- package/lib/typescript/src/native/MediaLibrary.d.ts +16 -0
- package/lib/typescript/src/native/MediaPicker.d.ts +2 -0
- package/lib/typescript/src/native/MediaPlayer.d.ts +1 -0
- package/lib/typescript/src/native/VideoPreview.d.ts +19 -0
- package/lib/typescript/src/screens/CropScreen.d.ts +9 -0
- package/lib/typescript/src/screens/EditorScreen.d.ts +10 -0
- package/lib/typescript/src/screens/ExportScreen.d.ts +9 -0
- package/lib/typescript/src/screens/GalleryScreen.d.ts +8 -0
- package/lib/typescript/src/screens/PickScreen.d.ts +13 -0
- package/lib/typescript/src/types.d.ts +58 -0
- package/package.json +101 -0
- package/src/assets/frames/film_vintage.png +0 -0
- package/src/assets/frames/floral_gold.png +0 -0
- package/src/assets/frames/minimal_double.png +0 -0
- package/src/assets/frames/polaroid_white.png +0 -0
- package/src/assets/frames/watercolor_floral.png +0 -0
- package/src/components/VideoEditor.tsx +182 -0
- package/src/index.tsx +2 -0
- package/src/native/CameraView.tsx +95 -0
- package/src/native/FrameGrabber.ts +21 -0
- package/src/native/MediaEditor.ts +33 -0
- package/src/native/MediaLibrary.ts +69 -0
- package/src/native/MediaPicker.ts +17 -0
- package/src/native/MediaPlayer.ts +16 -0
- package/src/native/VideoPreview.tsx +20 -0
- package/src/screens/CropScreen.tsx +968 -0
- package/src/screens/EditorScreen.tsx +4517 -0
- package/src/screens/ExportScreen.tsx +282 -0
- package/src/screens/GalleryScreen.tsx +412 -0
- package/src/screens/PickScreen.tsx +1094 -0
- package/src/types.ts +58 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "ImageVideoEditor"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = "A high-performance React Native image and video editor."
|
|
9
|
+
s.description = package["description"]
|
|
10
|
+
s.homepage = "https://github.com/worktechnotoil/image-video-editor"
|
|
11
|
+
s.license = package["license"]
|
|
12
|
+
s.authors = package["author"]
|
|
13
|
+
|
|
14
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
15
|
+
s.source = { :git => "https://github.com/worktechnotoil/image-video-editor.git", :tag => "#{s.version}" }
|
|
16
|
+
|
|
17
|
+
s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
|
|
18
|
+
s.resources = "ios/frames/*.png"
|
|
19
|
+
|
|
20
|
+
install_modules_dependencies(s)
|
|
21
|
+
end
|
package/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# @technotoil/image-video-editor
|
|
2
|
+
|
|
3
|
+
A high-performance, feature-rich React Native image and video editor. This library packages video trimming, custom image overlay frames, audio mix, native iOS/Android camera, and video filter features into a single, cohesive standalone component.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
* 📹 **Native Camera View**: High-performance thread-safe AVCaptureSession wrapper for iOS and custom camera on Android.
|
|
7
|
+
* ✂️ **Video Trimming**: Seamless interactive trimming of video files.
|
|
8
|
+
* 🖼️ **Custom Photo Frames**: Layer beautiful custom frames on top of images.
|
|
9
|
+
* 🎵 **Audio Mixing**: Select and mix audio tracks onto video exports.
|
|
10
|
+
* 🎨 **Real-time Filters**: High-performance shaders/filters for photo and video formats.
|
|
11
|
+
* 📦 **Modular Export**: Triggers callbacks with local filesystem URIs suitable for server uploads.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
Add the library to your React Native project:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
yarn add @technotoil/image-video-editor
|
|
21
|
+
# or
|
|
22
|
+
npm install @technotoil/image-video-editor
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Ensure the peer dependencies are installed:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
yarn add react-native-image-crop-picker react-native-safe-area-context react-native-vector-icons react-native-video
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Native Setup
|
|
32
|
+
|
|
33
|
+
#### iOS Installation
|
|
34
|
+
Run pod install in your `ios` directory:
|
|
35
|
+
```bash
|
|
36
|
+
cd ios && pod install
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Ensure the following keys are added to your `Info.plist`:
|
|
40
|
+
```xml
|
|
41
|
+
<key>NSCameraUsageDescription</key>
|
|
42
|
+
<string>We need access to your camera to record videos and take photos.</string>
|
|
43
|
+
<key>NSMicrophoneUsageDescription</key>
|
|
44
|
+
<string>We need access to your microphone to record audio for videos.</string>
|
|
45
|
+
<key>NSPhotoLibraryUsageDescription</key>
|
|
46
|
+
<string>We need access to your photo library to pick and save media files.</string>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### Android Installation
|
|
50
|
+
Make sure to add the FFmpeg kit dependency in your app's `android/app/build.gradle`:
|
|
51
|
+
```groovy
|
|
52
|
+
dependencies {
|
|
53
|
+
implementation("io.github.maitrungduc1410:ffmpeg-kit-min:6.0.1")
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Add permissions in `AndroidManifest.xml`:
|
|
58
|
+
```xml
|
|
59
|
+
<uses-permission android:name="android.permission.CAMERA" />
|
|
60
|
+
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
|
61
|
+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
|
62
|
+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Quick Example (onPress Modal Integration)
|
|
68
|
+
|
|
69
|
+
The editor is designed to launch cleanly from a button action and output the video URL when exported.
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import React, { useState } from 'react';
|
|
73
|
+
import { View, Button, Modal, StyleSheet } from 'react-native';
|
|
74
|
+
import { VideoEditor } from '@technotoil/image-video-editor';
|
|
75
|
+
|
|
76
|
+
export default function App() {
|
|
77
|
+
const [editorVisible, setEditorVisible] = useState(false);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<View style={styles.container}>
|
|
81
|
+
<Button
|
|
82
|
+
title="Open Video Editor"
|
|
83
|
+
onPress={() => setEditorVisible(true)}
|
|
84
|
+
/>
|
|
85
|
+
|
|
86
|
+
<Modal
|
|
87
|
+
visible={editorVisible}
|
|
88
|
+
animationType="slide"
|
|
89
|
+
onRequestClose={() => setEditorVisible(false)}
|
|
90
|
+
>
|
|
91
|
+
<VideoEditor
|
|
92
|
+
headerTitle="Create New Post"
|
|
93
|
+
cameraModes={['POST', 'STORY', 'REEL']}
|
|
94
|
+
defaultCameraMode="REEL"
|
|
95
|
+
onCancelPress={() => setEditorVisible(false)}
|
|
96
|
+
onFinishExport={(editedMedia, paths, editedArray, cameraMode) => {
|
|
97
|
+
console.log('Export completed successfully!');
|
|
98
|
+
console.log('Exported File Paths:', paths); // array of video/image URLs
|
|
99
|
+
|
|
100
|
+
// Perform your server upload here:
|
|
101
|
+
// uploadToServer(paths[0]);
|
|
102
|
+
|
|
103
|
+
setEditorVisible(false);
|
|
104
|
+
}}
|
|
105
|
+
/>
|
|
106
|
+
</Modal>
|
|
107
|
+
</View>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const styles = StyleSheet.create({
|
|
112
|
+
container: {
|
|
113
|
+
flex: 1,
|
|
114
|
+
justifyContent: 'center',
|
|
115
|
+
alignItems: 'center',
|
|
116
|
+
backgroundColor: '#fff',
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Props API
|
|
122
|
+
|
|
123
|
+
| Prop | Type | Default | Description |
|
|
124
|
+
|---|---|---|---|
|
|
125
|
+
| `headerTitle` | `string` | `"New post"` | Title text displayed in the header. |
|
|
126
|
+
| `cameraModes` | `Array<'POST' \| 'STORY' \| 'REEL'>` | `['POST', 'STORY', 'REEL']` | Active shooting modes allowed. |
|
|
127
|
+
| `defaultCameraMode` | `'POST' \| 'STORY' \| 'REEL'` | `"REEL"` | Initial shooting mode when opening. |
|
|
128
|
+
| `musicList` | `MusicTrack[]` | `[]` | List of audio tracks available to overlay on videos. |
|
|
129
|
+
| `onCancelPress` | `() => void` | `undefined` | Callback fired when user cancels or leaves the editor. |
|
|
130
|
+
| `onFinishExport` | `(editedMedia: any, paths: string[], editedArray: any[], cameraMode: string) => void` | `undefined` | Fired when edits finish exporting. Fills `paths` with target video/image file URIs. |
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
MIT
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.getExtOrDefault = {name ->
|
|
3
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['ImageVideoEditor_' + name]
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
repositories {
|
|
7
|
+
google()
|
|
8
|
+
mavenCentral()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
dependencies {
|
|
12
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
13
|
+
// noinspection DifferentKotlinGradleVersion
|
|
14
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
apply plugin: "com.android.library"
|
|
19
|
+
apply plugin: "kotlin-android"
|
|
20
|
+
apply plugin: "com.facebook.react"
|
|
21
|
+
|
|
22
|
+
def getExtOrIntegerDefault(name) {
|
|
23
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["ImageVideoEditor_" + name]).toInteger()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
android {
|
|
27
|
+
namespace "com.technotoil.image_videoeditor"
|
|
28
|
+
|
|
29
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
30
|
+
|
|
31
|
+
defaultConfig {
|
|
32
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
33
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
buildFeatures {
|
|
37
|
+
buildConfig true
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
buildTypes {
|
|
41
|
+
release {
|
|
42
|
+
minifyEnabled false
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
lintOptions {
|
|
47
|
+
disable "GradleCompatible"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
compileOptions {
|
|
51
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
52
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
sourceSets {
|
|
56
|
+
main {
|
|
57
|
+
java.srcDirs += [
|
|
58
|
+
"generated/java",
|
|
59
|
+
"generated/jni"
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
repositories {
|
|
66
|
+
mavenCentral()
|
|
67
|
+
google()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
def kotlin_version = getExtOrDefault("kotlinVersion")
|
|
71
|
+
|
|
72
|
+
dependencies {
|
|
73
|
+
implementation "com.facebook.react:react-android"
|
|
74
|
+
implementation "io.github.maitrungduc1410:ffmpeg-kit-min:6.0.1"
|
|
75
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
76
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
|
|
3
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
4
|
+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
5
|
+
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
|
6
|
+
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
|
7
|
+
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
|
8
|
+
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
|
9
|
+
|
|
10
|
+
<!-- Library manifest: only declare permissions here. Do NOT declare application/activity with
|
|
11
|
+
MAIN/LAUNCHER intent-filter in a library — that would create an extra app icon when merged
|
|
12
|
+
into a host application. Application-level components should be defined in the app only. -->
|
|
13
|
+
</manifest>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
package com.technotoil.image_videoeditor
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import android.media.MediaMetadataRetriever
|
|
5
|
+
import android.net.Uri
|
|
6
|
+
import com.facebook.react.bridge.Promise
|
|
7
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
8
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
9
|
+
import com.facebook.react.bridge.ReactMethod
|
|
10
|
+
import com.facebook.react.bridge.ReadableMap
|
|
11
|
+
import java.io.File
|
|
12
|
+
import java.io.FileOutputStream
|
|
13
|
+
|
|
14
|
+
class FrameGrabberModule(private val reactContext: ReactApplicationContext) :
|
|
15
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
16
|
+
|
|
17
|
+
override fun getName(): String = "RNFrameGrabber"
|
|
18
|
+
|
|
19
|
+
@ReactMethod
|
|
20
|
+
fun captureFrame(uriString: String, options: ReadableMap, promise: Promise) {
|
|
21
|
+
try {
|
|
22
|
+
val retriever = MediaMetadataRetriever()
|
|
23
|
+
|
|
24
|
+
val uri = Uri.parse(uriString)
|
|
25
|
+
if (uri.scheme == "file" || uriString.startsWith("/")) {
|
|
26
|
+
val path = uri.path ?: if (uriString.startsWith("file://")) uriString.substring(7) else uriString
|
|
27
|
+
retriever.setDataSource(path)
|
|
28
|
+
} else {
|
|
29
|
+
retriever.setDataSource(reactContext, uri)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
val timeMs = if (options.hasKey("timeMs")) options.getDouble("timeMs") else 0.0
|
|
33
|
+
val timeUs = (timeMs * 1000).toLong()
|
|
34
|
+
|
|
35
|
+
var bitmap: Bitmap? = null
|
|
36
|
+
try {
|
|
37
|
+
bitmap = retriever.getFrameAtTime(timeUs, MediaMetadataRetriever.OPTION_CLOSEST)
|
|
38
|
+
} catch (e: Exception) {
|
|
39
|
+
// Fallback
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (bitmap == null) {
|
|
43
|
+
try {
|
|
44
|
+
bitmap = retriever.getFrameAtTime(timeUs)
|
|
45
|
+
} catch (e: Exception) {
|
|
46
|
+
// Fallback
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
retriever.release()
|
|
51
|
+
|
|
52
|
+
if (bitmap == null) {
|
|
53
|
+
promise.reject("frame_failed", "Could not capture frame")
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
val outFile = File.createTempFile("frame_", ".jpg", reactContext.cacheDir)
|
|
58
|
+
FileOutputStream(outFile).use { out ->
|
|
59
|
+
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
promise.resolve(Uri.fromFile(outFile).toString())
|
|
63
|
+
} catch (e: Exception) {
|
|
64
|
+
promise.reject("frame_failed", e.message, e)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|