@umituz/react-native-video-editor 1.1.54 → 1.1.55

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-video-editor",
3
- "version": "1.1.54",
3
+ "version": "1.1.55",
4
4
  "description": "Professional video editor with layer-based timeline, text/image/shape/audio/animation layers, and export functionality",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -6,10 +6,12 @@
6
6
  import { generateUUID } from "@umituz/react-native-design-system/uuid";
7
7
  import type { Scene } from "../../../domain/entities/video-project.types";
8
8
  import type { LayerOperationResult } from "../../../domain/entities/video-project.types";
9
+ import { cloneLayerWithNewId } from "../../../infrastructure/utils/data-clone.utils";
9
10
 
10
11
  class LayerDuplicateService {
11
12
  /**
12
13
  * Duplicate layer
14
+ * Optimized using clone utility for better performance
13
15
  */
14
16
  duplicateLayer(
15
17
  scenes: Scene[],
@@ -37,9 +39,9 @@ class LayerDuplicateService {
37
39
  };
38
40
  }
39
41
 
42
+ // Use clone utility for consistent duplication
40
43
  const duplicatedLayer = {
41
- ...JSON.parse(JSON.stringify(layerToDuplicate)),
42
- id: generateUUID(),
44
+ ...cloneLayerWithNewId(layerToDuplicate, generateUUID),
43
45
  position: {
44
46
  x: layerToDuplicate.position.x + 5,
45
47
  y: layerToDuplicate.position.y + 5,
@@ -6,6 +6,7 @@
6
6
  import { generateUUID } from "@umituz/react-native-design-system/uuid";
7
7
  import type { Scene, Audio } from "../../domain/entities/video-project.types";
8
8
  import type { SceneOperationResult } from "../../domain/entities/video-project.types";
9
+ import { cloneSceneWithNewId } from "../utils/data-clone.utils";
9
10
 
10
11
  class SceneOperationsService {
11
12
  /**
@@ -38,6 +39,7 @@ class SceneOperationsService {
38
39
 
39
40
  /**
40
41
  * Duplicate scene
42
+ * Optimized using clone utility for better performance
41
43
  */
42
44
  duplicateScene(scenes: Scene[], sceneIndex: number): SceneOperationResult {
43
45
  try {
@@ -50,14 +52,9 @@ class SceneOperationsService {
50
52
  }
51
53
 
52
54
  const sceneToDuplicate = scenes[sceneIndex];
53
- const duplicatedScene: Scene = {
54
- ...JSON.parse(JSON.stringify(sceneToDuplicate)),
55
- id: generateUUID(),
56
- layers: sceneToDuplicate.layers.map((layer) => ({
57
- ...layer,
58
- id: generateUUID(),
59
- })),
60
- };
55
+
56
+ // Use clone utility for consistent duplication
57
+ const duplicatedScene = cloneSceneWithNewId(sceneToDuplicate, generateUUID);
61
58
 
62
59
  const updatedScenes = [...scenes];
63
60
  updatedScenes.splice(sceneIndex + 1, 0, duplicatedScene);
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Data Clone Utilities
3
+ * Optimized deep cloning functions for video editor data structures
4
+ */
5
+
6
+ import type { Scene, VideoProject, Layer } from "../../domain/entities/video-project.types";
7
+
8
+ /**
9
+ * Optimized deep clone for VideoProject
10
+ * Uses structured clone if available, falls back to manual clone
11
+ */
12
+ export function cloneVideoProject(project: VideoProject): VideoProject {
13
+ // Use structured clone for better performance if available
14
+ if (typeof structuredClone !== 'undefined') {
15
+ return structuredClone(project);
16
+ }
17
+
18
+ // Manual clone fallback - only clone what's necessary
19
+ return {
20
+ ...project,
21
+ scenes: project.scenes.map((scene: Scene) => ({
22
+ ...scene,
23
+ layers: scene.layers.map((layer: Layer) => ({ ...layer })),
24
+ })),
25
+ };
26
+ }
27
+
28
+ /**
29
+ * Optimized deep clone for Scene
30
+ */
31
+ export function cloneScene(scene: Scene): Scene {
32
+ if (typeof structuredClone !== 'undefined') {
33
+ return structuredClone(scene);
34
+ }
35
+
36
+ return {
37
+ ...scene,
38
+ layers: scene.layers.map((layer: Layer) => ({ ...layer })),
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Optimized shallow clone for Layer
44
+ * Layer objects are already immutable in our architecture
45
+ */
46
+ export function cloneLayer(layer: Layer): Layer {
47
+ return { ...layer };
48
+ }
49
+
50
+ /**
51
+ * Clone multiple layers
52
+ */
53
+ export function cloneLayers(layers: Layer[]): Layer[] {
54
+ return layers.map((layer: Layer) => ({ ...layer }));
55
+ }
56
+
57
+ /**
58
+ * Clone scene with new ID (for duplication)
59
+ */
60
+ export function cloneSceneWithNewId(scene: Scene, generateId: () => string): Scene {
61
+ return {
62
+ ...scene,
63
+ id: generateId(),
64
+ layers: scene.layers.map((layer: Layer) => ({
65
+ ...layer,
66
+ id: generateId(),
67
+ })),
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Clone layer with new ID (for duplication)
73
+ */
74
+ export function cloneLayerWithNewId(layer: Layer, generateId: () => string): Layer {
75
+ return {
76
+ ...layer,
77
+ id: generateId(),
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Optimized clone for Audio configuration
83
+ */
84
+ export function cloneAudio(audio: Scene["audio"]): Scene["audio"] {
85
+ if (!audio) return audio;
86
+ return { ...audio };
87
+ }
88
+
89
+ /**
90
+ * Optimized clone for Background configuration
91
+ */
92
+ export function cloneBackground(background: Scene["background"]): Scene["background"] {
93
+ return { ...background };
94
+ }
95
+
96
+ /**
97
+ * Optimized clone for Transition configuration
98
+ */
99
+ export function cloneTransition(transition: Scene["transition"]): Scene["transition"] {
100
+ return { ...transition };
101
+ }
102
+
103
+ /**
104
+ * Create a safe copy of an array (shallow clone)
105
+ */
106
+ export function cloneArray<T>(array: T[]): T[] {
107
+ return [...array];
108
+ }
109
+
110
+ /**
111
+ * Safe array slice that handles edge cases
112
+ */
113
+ export function safeSlice<T>(
114
+ array: T[],
115
+ start?: number,
116
+ end?: number,
117
+ ): T[] {
118
+ return array.slice(start, end);
119
+ }
120
+
121
+ /**
122
+ * Check if two values are shallow equal
123
+ */
124
+ export function isShallowEqual<T>(a: T, b: T): boolean {
125
+ if (a === b) return true;
126
+ if (a == null || b == null) return false;
127
+ if (typeof a !== 'object' || typeof b !== 'object') return false;
128
+
129
+ const keysA = Object.keys(a as object);
130
+ const keysB = Object.keys(b as object);
131
+
132
+ if (keysA.length !== keysB.length) return false;
133
+
134
+ for (const key of keysA) {
135
+ if ((a as Record<string, unknown>)[key] !== (b as Record<string, unknown>)[key]) {
136
+ return false;
137
+ }
138
+ }
139
+
140
+ return true;
141
+ }
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Position & Size Calculation Utilities
3
+ * Centralized position and size calculations for video editor
4
+ */
5
+
6
+ export interface Position {
7
+ x: number;
8
+ y: number;
9
+ }
10
+
11
+ export interface Size {
12
+ width: number;
13
+ height: number;
14
+ }
15
+
16
+ /**
17
+ * Convert percentage position to canvas pixels
18
+ */
19
+ export function percentageToPixels(
20
+ percentage: number,
21
+ canvasSize: number,
22
+ ): number {
23
+ if (canvasSize <= 0) return 0;
24
+ return (percentage / 100) * canvasSize;
25
+ }
26
+
27
+ /**
28
+ * Convert canvas pixels to percentage
29
+ */
30
+ export function pixelsToPercentage(
31
+ pixels: number,
32
+ canvasSize: number,
33
+ ): number {
34
+ if (canvasSize <= 0) return 0;
35
+ return (pixels / canvasSize) * 100;
36
+ }
37
+
38
+ /**
39
+ * Convert position from percentage to pixels
40
+ */
41
+ export function positionToPixels(
42
+ position: Position,
43
+ canvasWidth: number,
44
+ canvasHeight: number,
45
+ ): Position {
46
+ return {
47
+ x: percentageToPixels(position.x, canvasWidth),
48
+ y: percentageToPixels(position.y, canvasHeight),
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Convert position from pixels to percentage
54
+ */
55
+ export function positionToPercentage(
56
+ position: Position,
57
+ canvasWidth: number,
58
+ canvasHeight: number,
59
+ ): Position {
60
+ return {
61
+ x: pixelsToPercentage(position.x, canvasWidth),
62
+ y: pixelsToPercentage(position.y, canvasHeight),
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Convert size from percentage to pixels
68
+ */
69
+ export function sizeToPixels(
70
+ size: Size,
71
+ canvasWidth: number,
72
+ canvasHeight: number,
73
+ ): Size {
74
+ return {
75
+ width: percentageToPixels(size.width, canvasWidth),
76
+ height: percentageToPixels(size.height, canvasHeight),
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Convert size from pixels to percentage
82
+ */
83
+ export function sizeToPercentage(
84
+ size: Size,
85
+ canvasWidth: number,
86
+ canvasHeight: number,
87
+ ): Size {
88
+ return {
89
+ width: pixelsToPercentage(size.width, canvasWidth),
90
+ height: pixelsToPercentage(size.height, canvasHeight),
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Clamp value between min and max
96
+ */
97
+ export function clamp(value: number, min: number, max: number): number {
98
+ return Math.max(min, Math.min(max, value));
99
+ }
100
+
101
+ /**
102
+ * Clamp position to canvas bounds (in pixels)
103
+ */
104
+ export function clampPositionToCanvas(
105
+ position: Position,
106
+ canvasWidth: number,
107
+ canvasHeight: number,
108
+ elementWidth: number,
109
+ elementHeight: number,
110
+ ): Position {
111
+ return {
112
+ x: clamp(position.x, 0, canvasWidth - elementWidth),
113
+ y: clamp(position.y, 0, canvasHeight - elementHeight),
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Clamp position to percentage bounds (0-100)
119
+ */
120
+ export function clampPositionPercentage(position: Position): Position {
121
+ return {
122
+ x: clamp(position.x, 0, 100),
123
+ y: clamp(position.y, 0, 100),
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Clamp size to minimum and maximum percentage values
129
+ */
130
+ export function clampSizePercentage(
131
+ size: Size,
132
+ minSize: number = 1,
133
+ maxSize: number = 100,
134
+ ): Size {
135
+ return {
136
+ width: clamp(size.width, minSize, maxSize),
137
+ height: clamp(size.height, minSize, maxSize),
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Calculate center position for an element
143
+ */
144
+ export function calculateCenterPosition(
145
+ elementWidth: number,
146
+ elementHeight: number,
147
+ canvasWidth: number,
148
+ canvasHeight: number,
149
+ ): Position {
150
+ return {
151
+ x: (canvasWidth - elementWidth) / 2,
152
+ y: (canvasHeight - elementHeight) / 2,
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Offset position by delta
158
+ */
159
+ export function offsetPosition(position: Position, deltaX: number, deltaY: number): Position {
160
+ return {
161
+ x: position.x + deltaX,
162
+ y: position.y + deltaY,
163
+ };
164
+ }
165
+
166
+ /**
167
+ * Check if position is within canvas bounds
168
+ */
169
+ export function isPositionInBounds(
170
+ position: Position,
171
+ canvasWidth: number,
172
+ canvasHeight: number,
173
+ elementWidth: number,
174
+ elementHeight: number,
175
+ ): boolean {
176
+ return (
177
+ position.x >= 0 &&
178
+ position.y >= 0 &&
179
+ position.x + elementWidth <= canvasWidth &&
180
+ position.y + elementHeight <= canvasHeight
181
+ );
182
+ }
@@ -1,16 +1,10 @@
1
1
  /**
2
2
  * SRT Subtitle Utilities
3
+ * Re-exports time utilities for backward compatibility
3
4
  */
4
5
 
5
6
  import type { Subtitle } from "../../domain/entities/video-project.types";
6
-
7
- function toSrtTime(seconds: number): string {
8
- const h = Math.floor(seconds / 3600);
9
- const m = Math.floor((seconds % 3600) / 60);
10
- const s = Math.floor(seconds % 60);
11
- const ms = Math.floor((seconds % 1) * 1000);
12
- return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")},${String(ms).padStart(3, "0")}`;
13
- }
7
+ import { toSrtTime } from "./time-calculations.utils";
14
8
 
15
9
  export function generateSRT(subtitles: Subtitle[]): string {
16
10
  const validSubtitles = subtitles.filter((sub) => (
@@ -26,15 +20,5 @@ export function generateSRT(subtitles: Subtitle[]): string {
26
20
  .join("\n");
27
21
  }
28
22
 
29
- export function formatTimeDisplay(seconds: number): string {
30
- const m = Math.floor(seconds / 60);
31
- const s = Math.floor(seconds % 60);
32
- return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
33
- }
34
-
35
- export function formatTimeDetailed(seconds: number): string {
36
- const m = Math.floor(seconds / 60);
37
- const s = Math.floor(seconds % 60);
38
- const t = Math.floor((seconds % 1) * 10);
39
- return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}.${t}`;
40
- }
23
+ // Re-export time utilities for backward compatibility
24
+ export { formatTimeDisplay, formatTimeDetailed, toSrtTime } from "./time-calculations.utils";
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Time Calculation Utilities
3
+ * Centralized time-related calculations for video editor
4
+ */
5
+
6
+ /**
7
+ * Convert milliseconds to seconds
8
+ */
9
+ export function msToSeconds(ms: number): number {
10
+ return ms / 1000;
11
+ }
12
+
13
+ /**
14
+ * Convert seconds to milliseconds
15
+ */
16
+ export function secondsToMs(seconds: number): number {
17
+ return seconds * 1000;
18
+ }
19
+
20
+ /**
21
+ * Format milliseconds as display time (e.g., "5s" or "1:23")
22
+ */
23
+ export function formatTimeDisplay(ms: number, showMinutes: boolean = false): string {
24
+ const totalSeconds = Math.floor(ms / 1000);
25
+
26
+ if (!showMinutes || totalSeconds < 60) {
27
+ return `${totalSeconds}s`;
28
+ }
29
+
30
+ const minutes = Math.floor(totalSeconds / 60);
31
+ const seconds = totalSeconds % 60;
32
+ return `${minutes}:${String(seconds).padStart(2, "0")}`;
33
+ }
34
+
35
+ /**
36
+ * Format milliseconds as detailed time (e.g., "1:23.4")
37
+ */
38
+ export function formatTimeDetailed(ms: number): string {
39
+ const totalSeconds = Math.floor(ms / 1000);
40
+ const minutes = Math.floor(totalSeconds / 60);
41
+ const seconds = totalSeconds % 60;
42
+ const tenths = Math.floor((ms % 1000) / 100);
43
+
44
+ return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}.${tenths}`;
45
+ }
46
+
47
+ /**
48
+ * Calculate progress percentage (0-1)
49
+ */
50
+ export function calculateProgress(current: number, total: number): number {
51
+ if (total <= 0) return 0;
52
+ return Math.min(1, Math.max(0, current / total));
53
+ }
54
+
55
+ /**
56
+ * Calculate progress percentage (0-100)
57
+ */
58
+ export function calculateProgressPercent(current: number, total: number): number {
59
+ return calculateProgress(current, total) * 100;
60
+ }
61
+
62
+ /**
63
+ * Calculate delta time between two timestamps
64
+ */
65
+ export function calculateDelta(current: number, previous: number): number {
66
+ return current - previous;
67
+ }
68
+
69
+ /**
70
+ * Add delta time to base time
71
+ */
72
+ export function addDeltaTime(baseTime: number, deltaTime: number): number {
73
+ return baseTime + deltaTime;
74
+ }
75
+
76
+ /**
77
+ * Clamp time to duration bounds
78
+ */
79
+ export function clampTime(time: number, duration: number): number {
80
+ return Math.max(0, Math.min(duration, time));
81
+ }
82
+
83
+ /**
84
+ * Check if time is at or past duration
85
+ */
86
+ export function isTimeAtEnd(time: number, duration: number): boolean {
87
+ return time >= duration;
88
+ }
89
+
90
+ /**
91
+ * Convert seconds to SRT time format (HH:MM:SS,mmm)
92
+ */
93
+ export function toSrtTime(seconds: number): string {
94
+ const h = Math.floor(seconds / 3600);
95
+ const m = Math.floor((seconds % 3600) / 60);
96
+ const s = Math.floor(seconds % 60);
97
+ const ms = Math.floor((seconds % 1) * 1000);
98
+
99
+ return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")},${String(ms).padStart(3, "0")}`;
100
+ }
101
+
102
+ /**
103
+ * Calculate total duration from scenes (in milliseconds)
104
+ */
105
+ export function calculateTotalDuration(sceneDurations: number[]): number {
106
+ return sceneDurations.reduce((total, duration) => total + duration, 0);
107
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Video Calculation Utilities
3
+ * Centralized video-related calculations for export and file size estimation
4
+ */
5
+
6
+ import type { Scene, VideoProject } from "../../domain/entities/video-project.types";
7
+
8
+ /**
9
+ * Resolution multipliers for file size estimation
10
+ */
11
+ export const RESOLUTION_MULTIPLIERS: Record<string, number> = {
12
+ "720p": 0.5,
13
+ "1080p": 1.0,
14
+ "4k": 3.0,
15
+ };
16
+
17
+ /**
18
+ * Quality multipliers for file size estimation
19
+ */
20
+ export const QUALITY_MULTIPLIERS: Record<string, number> = {
21
+ low: 0.6,
22
+ medium: 1.0,
23
+ high: 1.4,
24
+ };
25
+
26
+ /**
27
+ * Base file size per second of video (in MB)
28
+ */
29
+ export const BASE_SIZE_PER_SECOND = 2.5;
30
+
31
+ /**
32
+ * Calculate estimated file size for video export
33
+ */
34
+ export function calculateEstimatedFileSize(
35
+ durationSeconds: number,
36
+ resolution: string,
37
+ quality: string,
38
+ ): number {
39
+ const baseSize = durationSeconds * BASE_SIZE_PER_SECOND;
40
+ const resolutionMultiplier = RESOLUTION_MULTIPLIERS[resolution] || 1.0;
41
+ const qualityMultiplier = QUALITY_MULTIPLIERS[quality] || 1.0;
42
+
43
+ return baseSize * resolutionMultiplier * qualityMultiplier;
44
+ }
45
+
46
+ /**
47
+ * Calculate estimated file size and return formatted string
48
+ */
49
+ export function formatEstimatedFileSize(
50
+ durationSeconds: number,
51
+ resolution: string,
52
+ quality: string,
53
+ decimals: number = 1,
54
+ ): string {
55
+ const sizeInMB = calculateEstimatedFileSize(durationSeconds, resolution, quality);
56
+ return sizeInMB.toFixed(decimals);
57
+ }
58
+
59
+ /**
60
+ * Calculate total project duration from scenes (in seconds)
61
+ */
62
+ export function calculateProjectDuration(scenes: Scene[]): number {
63
+ return scenes.reduce((total, scene) => total + scene.duration, 0) / 1000;
64
+ }
65
+
66
+ /**
67
+ * Calculate total project duration from VideoProject (in seconds)
68
+ */
69
+ export function calculateVideoProjectDuration(project: VideoProject): number {
70
+ return calculateProjectDuration(project.scenes);
71
+ }
72
+
73
+ /**
74
+ * Calculate aspect ratio as decimal
75
+ */
76
+ export function getAspectRatioValue(aspectRatio: string): number {
77
+ const ratioMap: Record<string, number> = {
78
+ "16:9": 16 / 9,
79
+ "9:16": 9 / 16,
80
+ "1:1": 1,
81
+ "4:5": 4 / 5,
82
+ };
83
+
84
+ return ratioMap[aspectRatio] || 16 / 9;
85
+ }
86
+
87
+ /**
88
+ * Calculate height from width and aspect ratio
89
+ */
90
+ export function calculateHeightFromAspectRatio(
91
+ width: number,
92
+ aspectRatio: string,
93
+ ): number {
94
+ const ratio = getAspectRatioValue(aspectRatio);
95
+ return width / ratio;
96
+ }
97
+
98
+ /**
99
+ * Calculate width from height and aspect ratio
100
+ */
101
+ export function calculateWidthFromAspectRatio(
102
+ height: number,
103
+ aspectRatio: string,
104
+ ): number {
105
+ const ratio = getAspectRatioValue(aspectRatio);
106
+ return height * ratio;
107
+ }
108
+
109
+ /**
110
+ * Calculate resolution dimensions
111
+ */
112
+ export interface ResolutionDimensions {
113
+ width: number;
114
+ height: number;
115
+ }
116
+
117
+ export function calculateResolutionDimensions(
118
+ resolution: string,
119
+ aspectRatio: string,
120
+ ): ResolutionDimensions {
121
+ const baseHeights: Record<string, number> = {
122
+ "720p": 720,
123
+ "1080p": 1080,
124
+ "4k": 2160,
125
+ };
126
+
127
+ const height = baseHeights[resolution] || 1080;
128
+ const width = calculateWidthFromAspectRatio(height, aspectRatio);
129
+
130
+ return { width, height };
131
+ }
132
+
133
+ /**
134
+ * Calculate bitrate based on resolution and quality (in kbps)
135
+ */
136
+ export function calculateBitrate(
137
+ resolution: string,
138
+ quality: string,
139
+ ): number {
140
+ const baseBitrates: Record<string, number> = {
141
+ "720p": 5000,
142
+ "1080p": 10000,
143
+ "4k": 40000,
144
+ };
145
+
146
+ const qualityMultipliers: Record<string, number> = {
147
+ low: 0.6,
148
+ medium: 1.0,
149
+ high: 1.5,
150
+ };
151
+
152
+ const baseBitrate = baseBitrates[resolution] || 10000;
153
+ const qualityMultiplier = qualityMultipliers[quality] || 1.0;
154
+
155
+ return Math.round(baseBitrate * qualityMultiplier);
156
+ }
157
+
158
+ /**
159
+ * Estimate file size based on bitrate and duration (in MB)
160
+ */
161
+ export function estimateFileSizeFromBitrate(
162
+ bitrateKbps: number,
163
+ durationSeconds: number,
164
+ ): number {
165
+ // Formula: (bitrate in kbps * duration) / 8 = kilobytes
166
+ // Then divide by 1024 to get MB
167
+ const kilobytes = (bitrateKbps * durationSeconds) / 8;
168
+ return kilobytes / 1024;
169
+ }