@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.
Files changed (99) hide show
  1. package/ImageVideoEditor.podspec +21 -0
  2. package/README.md +136 -0
  3. package/android/build.gradle +76 -0
  4. package/android/gradle.properties +5 -0
  5. package/android/src/main/AndroidManifest.xml +13 -0
  6. package/android/src/main/java/com/technotoil/image_videoeditor/FrameGrabberModule.kt +67 -0
  7. package/android/src/main/java/com/technotoil/image_videoeditor/MediaEditorModule.kt +548 -0
  8. package/android/src/main/java/com/technotoil/image_videoeditor/MediaFileUtils.kt +29 -0
  9. package/android/src/main/java/com/technotoil/image_videoeditor/MediaLibraryModule.kt +305 -0
  10. package/android/src/main/java/com/technotoil/image_videoeditor/MediaPackage.kt +26 -0
  11. package/android/src/main/java/com/technotoil/image_videoeditor/MediaPickerModule.kt +111 -0
  12. package/android/src/main/java/com/technotoil/image_videoeditor/MediaPlayerModule.kt +34 -0
  13. package/android/src/main/java/com/technotoil/image_videoeditor/RNCameraViewManager.kt +761 -0
  14. package/android/src/main/java/com/technotoil/image_videoeditor/RNVideoPreviewManager.kt +317 -0
  15. package/ios/PrivacyInfo.xcprivacy +38 -0
  16. package/ios/RNCameraViewManager.m +420 -0
  17. package/ios/RNFrameGrabber.m +61 -0
  18. package/ios/RNMediaEditor.m +905 -0
  19. package/ios/RNMediaLibrary.m +389 -0
  20. package/ios/RNMediaPicker.m +144 -0
  21. package/ios/RNMediaPlayer.m +73 -0
  22. package/ios/RNVideoPreviewManager.m +263 -0
  23. package/ios/frames/film_vintage.png +0 -0
  24. package/ios/frames/floral_gold.png +0 -0
  25. package/ios/frames/minimal_double.png +0 -0
  26. package/ios/frames/polaroid_white.png +0 -0
  27. package/ios/frames/watercolor_floral.png +0 -0
  28. package/lib/module/assets/frames/film_vintage.png +0 -0
  29. package/lib/module/assets/frames/floral_gold.png +0 -0
  30. package/lib/module/assets/frames/minimal_double.png +0 -0
  31. package/lib/module/assets/frames/polaroid_white.png +0 -0
  32. package/lib/module/assets/frames/watercolor_floral.png +0 -0
  33. package/lib/module/components/VideoEditor.js +156 -0
  34. package/lib/module/components/VideoEditor.js.map +1 -0
  35. package/lib/module/index.js +4 -0
  36. package/lib/module/index.js.map +1 -0
  37. package/lib/module/native/CameraView.js +104 -0
  38. package/lib/module/native/CameraView.js.map +1 -0
  39. package/lib/module/native/FrameGrabber.js +13 -0
  40. package/lib/module/native/FrameGrabber.js.map +1 -0
  41. package/lib/module/native/MediaEditor.js +19 -0
  42. package/lib/module/native/MediaEditor.js.map +1 -0
  43. package/lib/module/native/MediaLibrary.js +37 -0
  44. package/lib/module/native/MediaLibrary.js.map +1 -0
  45. package/lib/module/native/MediaPicker.js +13 -0
  46. package/lib/module/native/MediaPicker.js.map +1 -0
  47. package/lib/module/native/MediaPlayer.js +13 -0
  48. package/lib/module/native/MediaPlayer.js.map +1 -0
  49. package/lib/module/native/VideoPreview.js +12 -0
  50. package/lib/module/native/VideoPreview.js.map +1 -0
  51. package/lib/module/package.json +1 -0
  52. package/lib/module/screens/CropScreen.js +1211 -0
  53. package/lib/module/screens/CropScreen.js.map +1 -0
  54. package/lib/module/screens/EditorScreen.js +5752 -0
  55. package/lib/module/screens/EditorScreen.js.map +1 -0
  56. package/lib/module/screens/ExportScreen.js +289 -0
  57. package/lib/module/screens/ExportScreen.js.map +1 -0
  58. package/lib/module/screens/GalleryScreen.js +505 -0
  59. package/lib/module/screens/GalleryScreen.js.map +1 -0
  60. package/lib/module/screens/PickScreen.js +1195 -0
  61. package/lib/module/screens/PickScreen.js.map +1 -0
  62. package/lib/module/types.js +2 -0
  63. package/lib/module/types.js.map +1 -0
  64. package/lib/typescript/src/components/VideoEditor.d.ts +13 -0
  65. package/lib/typescript/src/index.d.ts +2 -0
  66. package/lib/typescript/src/native/CameraView.d.ts +23 -0
  67. package/lib/typescript/src/native/FrameGrabber.d.ts +2 -0
  68. package/lib/typescript/src/native/MediaEditor.d.ts +3 -0
  69. package/lib/typescript/src/native/MediaLibrary.d.ts +16 -0
  70. package/lib/typescript/src/native/MediaPicker.d.ts +2 -0
  71. package/lib/typescript/src/native/MediaPlayer.d.ts +1 -0
  72. package/lib/typescript/src/native/VideoPreview.d.ts +19 -0
  73. package/lib/typescript/src/screens/CropScreen.d.ts +9 -0
  74. package/lib/typescript/src/screens/EditorScreen.d.ts +10 -0
  75. package/lib/typescript/src/screens/ExportScreen.d.ts +9 -0
  76. package/lib/typescript/src/screens/GalleryScreen.d.ts +8 -0
  77. package/lib/typescript/src/screens/PickScreen.d.ts +13 -0
  78. package/lib/typescript/src/types.d.ts +58 -0
  79. package/package.json +101 -0
  80. package/src/assets/frames/film_vintage.png +0 -0
  81. package/src/assets/frames/floral_gold.png +0 -0
  82. package/src/assets/frames/minimal_double.png +0 -0
  83. package/src/assets/frames/polaroid_white.png +0 -0
  84. package/src/assets/frames/watercolor_floral.png +0 -0
  85. package/src/components/VideoEditor.tsx +182 -0
  86. package/src/index.tsx +2 -0
  87. package/src/native/CameraView.tsx +95 -0
  88. package/src/native/FrameGrabber.ts +21 -0
  89. package/src/native/MediaEditor.ts +33 -0
  90. package/src/native/MediaLibrary.ts +69 -0
  91. package/src/native/MediaPicker.ts +17 -0
  92. package/src/native/MediaPlayer.ts +16 -0
  93. package/src/native/VideoPreview.tsx +20 -0
  94. package/src/screens/CropScreen.tsx +968 -0
  95. package/src/screens/EditorScreen.tsx +4517 -0
  96. package/src/screens/ExportScreen.tsx +282 -0
  97. package/src/screens/GalleryScreen.tsx +412 -0
  98. package/src/screens/PickScreen.tsx +1094 -0
  99. 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,5 @@
1
+ ImageVideoEditor_kotlinVersion=2.0.21
2
+ ImageVideoEditor_minSdkVersion=24
3
+ ImageVideoEditor_targetSdkVersion=34
4
+ ImageVideoEditor_compileSdkVersion=35
5
+ ImageVideoEditor_ndkVersion=27.1.12297006
@@ -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
+ }