react-native-video-trim 7.1.0 → 8.0.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.
@@ -26,6 +26,14 @@ export interface BaseOptions {
26
26
  * happens regardless of this flag, so precise trimming comes for free in that case.
27
27
  */
28
28
  enablePreciseTrimming: boolean;
29
+ /** When `true`, strips the audio track from the output. Default `false`. */
30
+ removeAudio: boolean;
31
+ /**
32
+ * Playback speed multiplier applied during export. `1.0` is normal speed,
33
+ * `2.0` is double speed, `0.5` is half speed. Valid range: 0.25–4.0.
34
+ * When not `1.0`, re-encoding is forced regardless of `enablePreciseTrimming`.
35
+ */
36
+ speed: number;
29
37
  }
30
38
 
31
39
  /**
@@ -129,6 +137,16 @@ export interface EditorConfig extends BaseOptions {
129
137
  * `"light"` uses a white background with black icons/text and white trimmer-handle chevrons.
130
138
  */
131
139
  theme?: string;
140
+ /** Color of the audio waveform bars as a `processColor` value. */
141
+ waveformColor?: number;
142
+ /** Background color behind the audio waveform bars as a `processColor` value. */
143
+ waveformBackgroundColor?: number;
144
+ /** Width of each waveform bar in dp/pt (default: `3`). */
145
+ waveformBarWidth?: number;
146
+ /** Gap between waveform bars in dp/pt (default: `2`). */
147
+ waveformBarGap?: number;
148
+ /** Corner radius of waveform bars in dp/pt (default: `1.5`). */
149
+ waveformBarCornerRadius?: number;
132
150
  }
133
151
 
134
152
  /**
@@ -169,6 +187,143 @@ export interface TrimResult {
169
187
  success: boolean;
170
188
  }
171
189
 
190
+ /**
191
+ * Options for extracting a single frame from a video.
192
+ */
193
+ export interface FrameExtractionOptions {
194
+ /** Timestamp in milliseconds at which to extract the frame. */
195
+ time: number;
196
+ /** Output image format: `"jpeg"` (default) or `"png"`. */
197
+ format: string;
198
+ /** JPEG compression quality from 0–100. Default `80`. Ignored for PNG. */
199
+ quality: number;
200
+ /** Maximum width in pixels. Height is auto-calculated to preserve aspect ratio. `-1` for original. */
201
+ maxWidth: number;
202
+ /** Maximum height in pixels. Width is auto-calculated to preserve aspect ratio. `-1` for original. */
203
+ maxHeight: number;
204
+ }
205
+
206
+ /**
207
+ * Result returned by {@link Spec.getFrameAt}.
208
+ */
209
+ export interface FrameResult {
210
+ /** Absolute path to the extracted image file. */
211
+ outputPath: string;
212
+ }
213
+
214
+ /**
215
+ * Options for extracting the audio track from a video file.
216
+ */
217
+ export interface ExtractAudioOptions {
218
+ /** Output audio file extension (e.g. `"mp3"`, `"m4a"`, `"wav"`). Default `"mp3"`. */
219
+ outputExt: string;
220
+ }
221
+
222
+ /**
223
+ * Result returned by {@link Spec.extractAudio}.
224
+ */
225
+ export interface ExtractAudioResult {
226
+ /** Absolute path to the extracted audio file. */
227
+ outputPath: string;
228
+ /** Duration of the audio in milliseconds. */
229
+ duration: number;
230
+ }
231
+
232
+ /**
233
+ * Options for compressing a video file.
234
+ */
235
+ export interface CompressOptions {
236
+ /**
237
+ * Quality preset: `"low"` (smallest file), `"medium"` (balanced), `"high"` (best quality).
238
+ * Maps to CRF 28, 23, 18 respectively. Ignored when `bitrate` is set.
239
+ */
240
+ quality: string;
241
+ /** Explicit target bitrate in bits per second. Overrides `quality` when set. `-1` to use quality preset. */
242
+ bitrate: number;
243
+ /** Target width in pixels. `-1` to keep original. Height is auto-calculated to preserve aspect ratio. */
244
+ width: number;
245
+ /** Target height in pixels. `-1` to keep original. Width is auto-calculated to preserve aspect ratio. */
246
+ height: number;
247
+ /** Target frame rate. `-1` to keep original. */
248
+ frameRate: number;
249
+ /** Output file extension (e.g. `"mp4"`). Default `"mp4"`. */
250
+ outputExt: string;
251
+ /** When `true`, strips the audio track from the output. Default `false`. */
252
+ removeAudio: boolean;
253
+ }
254
+
255
+ /**
256
+ * Result returned by {@link Spec.compress}.
257
+ */
258
+ export interface CompressResult {
259
+ /** Absolute path to the compressed output file. */
260
+ outputPath: string;
261
+ }
262
+
263
+ /**
264
+ * Options for converting a video segment to an animated GIF.
265
+ */
266
+ export interface GifOptions {
267
+ /** Start time in milliseconds. Default `0`. */
268
+ startTime: number;
269
+ /** End time in milliseconds. Default `-1` (end of video). */
270
+ endTime: number;
271
+ /** Frame rate of the GIF. Default `10`. */
272
+ fps: number;
273
+ /** Width in pixels. Height is auto-calculated to preserve aspect ratio. `-1` for original. */
274
+ width: number;
275
+ }
276
+
277
+ /**
278
+ * Result returned by {@link Spec.toGif}.
279
+ */
280
+ export interface GifResult {
281
+ /** Absolute path to the generated GIF file. */
282
+ outputPath: string;
283
+ }
284
+
285
+ /**
286
+ * Options for merging multiple media files into one.
287
+ */
288
+ export interface MergeOptions {
289
+ /** Output file extension (e.g. `"mp4"`, `"wav"`). Default `"mp4"`. */
290
+ outputExt: string;
291
+ }
292
+
293
+ /**
294
+ * Result returned by {@link Spec.merge}.
295
+ */
296
+ export interface MergeResult {
297
+ /** Absolute path to the merged output file. */
298
+ outputPath: string;
299
+ /** Total duration of the merged file in milliseconds. */
300
+ duration: number;
301
+ }
302
+
303
+ /**
304
+ * Result returned by {@link Spec.saveToPhoto}.
305
+ */
306
+ export interface SaveToPhotoResult {
307
+ /** Whether the file was saved to the photo library successfully. */
308
+ success: boolean;
309
+ }
310
+
311
+ /**
312
+ * Result returned by {@link Spec.saveToDocuments}.
313
+ */
314
+ export interface SaveToDocumentsResult {
315
+ /** Whether the file was saved to documents successfully. */
316
+ success: boolean;
317
+ }
318
+
319
+ /**
320
+ * Result returned by {@link Spec.share}.
321
+ */
322
+ export interface ShareResult {
323
+ /** Whether the user completed the share action. */
324
+ success: boolean;
325
+ }
326
+
172
327
  /**
173
328
  * TurboModule spec for the native VideoTrim module.
174
329
  */
@@ -187,6 +342,22 @@ export interface Spec extends TurboModule {
187
342
  isValidFile(url: string): Promise<FileValidationResult>;
188
343
  /** Perform a headless trim (no UI) on the given URL with the specified options. */
189
344
  trim(url: string, options: TrimOptions): Promise<TrimResult>;
345
+ /** Extract a single frame from a video at the specified timestamp. */
346
+ getFrameAt(url: string, options: FrameExtractionOptions): Promise<FrameResult>;
347
+ /** Extract the audio track from a video file into a separate audio file. */
348
+ extractAudio(url: string, options: ExtractAudioOptions): Promise<ExtractAudioResult>;
349
+ /** Compress a video file to reduce its size. */
350
+ compress(url: string, options: CompressOptions): Promise<CompressResult>;
351
+ /** Convert a video segment to an animated GIF. */
352
+ toGif(url: string, options: GifOptions): Promise<GifResult>;
353
+ /** Merge multiple media files into a single file. Headless only, no editor UI. */
354
+ merge(urls: ReadonlyArray<string>, options: MergeOptions): Promise<MergeResult>;
355
+ /** Save a file to the device's photo library. Requires photo library permission. */
356
+ saveToPhoto(filePath: string): Promise<SaveToPhotoResult>;
357
+ /** Present the system document picker to save a file to the user's chosen location. */
358
+ saveToDocuments(filePath: string): Promise<SaveToDocumentsResult>;
359
+ /** Open the system share sheet for a file. */
360
+ share(filePath: string): Promise<ShareResult>;
190
361
 
191
362
  /** Emitted when the trim operation starts. */
192
363
  readonly onStartTrimming: EventEmitter<void>;
package/src/index.tsx CHANGED
@@ -2,8 +2,21 @@ import VideoTrimNewArch from './NativeVideoTrim';
2
2
  import VideoTrimOldArch from './OldArch';
3
3
  import type {
4
4
  BaseOptions,
5
+ CompressOptions,
6
+ CompressResult,
5
7
  EditorConfig,
8
+ ExtractAudioOptions,
9
+ ExtractAudioResult,
6
10
  FileValidationResult,
11
+ FrameExtractionOptions,
12
+ FrameResult,
13
+ GifOptions,
14
+ GifResult,
15
+ MergeOptions,
16
+ MergeResult,
17
+ SaveToDocumentsResult,
18
+ SaveToPhotoResult,
19
+ ShareResult,
7
20
  TrimOptions,
8
21
  TrimResult,
9
22
  } from './NativeVideoTrim';
@@ -21,6 +34,64 @@ function createBaseOptions(overrides: Partial<BaseOptions> = {}): BaseOptions {
21
34
  removeAfterSavedToPhoto: false,
22
35
  removeAfterFailedToSavePhoto: false,
23
36
  enablePreciseTrimming: false,
37
+ removeAudio: false,
38
+ speed: 1.0,
39
+ ...overrides,
40
+ };
41
+ }
42
+
43
+ function createCompressOptions(
44
+ overrides: Partial<CompressOptions> = {}
45
+ ): CompressOptions {
46
+ return {
47
+ quality: 'medium',
48
+ bitrate: -1,
49
+ width: -1,
50
+ height: -1,
51
+ frameRate: -1,
52
+ outputExt: 'mp4',
53
+ removeAudio: false,
54
+ ...overrides,
55
+ };
56
+ }
57
+
58
+ function createFrameExtractionOptions(
59
+ overrides: Partial<FrameExtractionOptions> = {}
60
+ ): FrameExtractionOptions {
61
+ return {
62
+ time: 0,
63
+ format: 'jpeg',
64
+ quality: 80,
65
+ maxWidth: -1,
66
+ maxHeight: -1,
67
+ ...overrides,
68
+ };
69
+ }
70
+
71
+ function createExtractAudioOptions(
72
+ overrides: Partial<ExtractAudioOptions> = {}
73
+ ): ExtractAudioOptions {
74
+ return {
75
+ outputExt: 'm4a',
76
+ ...overrides,
77
+ };
78
+ }
79
+
80
+ function createGifOptions(overrides: Partial<GifOptions> = {}): GifOptions {
81
+ return {
82
+ startTime: 0,
83
+ endTime: -1,
84
+ fps: 10,
85
+ width: -1,
86
+ ...overrides,
87
+ };
88
+ }
89
+
90
+ function createMergeOptions(
91
+ overrides: Partial<MergeOptions> = {}
92
+ ): MergeOptions {
93
+ return {
94
+ outputExt: 'mp4',
24
95
  ...overrides,
25
96
  };
26
97
  }
@@ -73,6 +144,11 @@ function createEditorConfig(
73
144
  alertOnFailMessage:
74
145
  'Fail to load media. Possibly invalid file or no network connection',
75
146
  alertOnFailCloseText: 'Close',
147
+ waveformColor: processColor('white') as number,
148
+ waveformBackgroundColor: processColor('#3478F6') as number,
149
+ waveformBarWidth: 3,
150
+ waveformBarGap: 2,
151
+ waveformBarCornerRadius: 1.5,
76
152
  ...createBaseOptions(overrides),
77
153
  ...overrides,
78
154
  };
@@ -98,14 +174,29 @@ function createTrimOptions(overrides: Partial<TrimOptions> = {}): TrimOptions {
98
174
  export function showEditor(
99
175
  filePath: string,
100
176
  config: Partial<
101
- Omit<EditorConfig, 'headerTextColor' | 'trimmerColor' | 'handleIconColor'>
177
+ Omit<
178
+ EditorConfig,
179
+ | 'headerTextColor'
180
+ | 'trimmerColor'
181
+ | 'handleIconColor'
182
+ | 'waveformColor'
183
+ | 'waveformBackgroundColor'
184
+ >
102
185
  > & {
103
186
  headerTextColor?: string;
104
187
  trimmerColor?: string;
105
188
  handleIconColor?: string;
189
+ waveformColor?: string;
190
+ waveformBackgroundColor?: string;
106
191
  }
107
192
  ): void {
108
- const { headerTextColor, trimmerColor, handleIconColor } = config;
193
+ const {
194
+ headerTextColor,
195
+ trimmerColor,
196
+ handleIconColor,
197
+ waveformColor,
198
+ waveformBackgroundColor,
199
+ } = config;
109
200
  const isLight = config.theme === 'light';
110
201
  const _headerTextColor = processColor(
111
202
  headerTextColor || (isLight ? 'black' : 'white')
@@ -114,6 +205,10 @@ export function showEditor(
114
205
  const _handleIconColor = processColor(
115
206
  handleIconColor || (isLight ? 'white' : 'black')
116
207
  );
208
+ const _waveformColor = processColor(waveformColor || 'white');
209
+ const _waveformBackgroundColor = processColor(
210
+ waveformBackgroundColor || '#3478F6'
211
+ );
117
212
 
118
213
  VideoTrim.showEditor(
119
214
  filePath,
@@ -122,6 +217,8 @@ export function showEditor(
122
217
  headerTextColor: _headerTextColor as any,
123
218
  trimmerColor: _trimmerColor as any,
124
219
  handleIconColor: _handleIconColor as any,
220
+ waveformColor: _waveformColor as any,
221
+ waveformBackgroundColor: _waveformBackgroundColor as any,
125
222
  })
126
223
  );
127
224
  }
@@ -188,5 +285,117 @@ export function trim(
188
285
  return VideoTrim.trim(url, createTrimOptions(options));
189
286
  }
190
287
 
288
+ /**
289
+ * Extract a single frame from a video at a given timestamp
290
+ *
291
+ * @param {string} url: absolute non-empty file path
292
+ * @param {Partial<FrameExtractionOptions>} options: extraction options
293
+ * @returns {Promise<FrameResult>} A **Promise** which resolves to the FrameResult
294
+ */
295
+ export function getFrameAt(
296
+ url: string,
297
+ options: Partial<FrameExtractionOptions> = {}
298
+ ): Promise<FrameResult> {
299
+ return VideoTrim.getFrameAt(url, createFrameExtractionOptions(options));
300
+ }
301
+
302
+ /**
303
+ * Extract the audio track from a video file
304
+ *
305
+ * @param {string} url: absolute non-empty file path
306
+ * @param {Partial<ExtractAudioOptions>} options: extraction options
307
+ * @returns {Promise<ExtractAudioResult>} A **Promise** which resolves to the result
308
+ */
309
+ export function extractAudio(
310
+ url: string,
311
+ options: Partial<ExtractAudioOptions> = {}
312
+ ): Promise<ExtractAudioResult> {
313
+ return VideoTrim.extractAudio(url, createExtractAudioOptions(options));
314
+ }
315
+
316
+ /**
317
+ * Compress a video file to reduce its size
318
+ *
319
+ * @param {string} url: absolute non-empty file path
320
+ * @param {Partial<CompressOptions>} options: compression options
321
+ * @returns {Promise<CompressResult>} A **Promise** which resolves to the result
322
+ */
323
+ export function compress(
324
+ url: string,
325
+ options: Partial<CompressOptions> = {}
326
+ ): Promise<CompressResult> {
327
+ return VideoTrim.compress(url, createCompressOptions(options));
328
+ }
329
+
330
+ /**
331
+ * Convert a video segment to an animated GIF
332
+ *
333
+ * @param {string} url: absolute non-empty file path
334
+ * @param {Partial<GifOptions>} options: GIF conversion options
335
+ * @returns {Promise<GifResult>} A **Promise** which resolves to the result
336
+ */
337
+ export function toGif(
338
+ url: string,
339
+ options: Partial<GifOptions> = {}
340
+ ): Promise<GifResult> {
341
+ return VideoTrim.toGif(url, createGifOptions(options));
342
+ }
343
+
344
+ /**
345
+ * Merge multiple media files into a single file (headless, no UI)
346
+ *
347
+ * @param {string[]} urls: array of file paths to merge in order
348
+ * @param {Partial<MergeOptions>} options: merge options
349
+ * @returns {Promise<MergeResult>} A **Promise** which resolves to the result
350
+ */
351
+ export function merge(
352
+ urls: string[],
353
+ options: Partial<MergeOptions> = {}
354
+ ): Promise<MergeResult> {
355
+ if (!urls?.length) {
356
+ throw new Error('URLs array cannot be empty!');
357
+ }
358
+ return VideoTrim.merge(urls, createMergeOptions(options));
359
+ }
360
+
361
+ /**
362
+ * Save a file to the device's photo library
363
+ *
364
+ * @param {string} filePath: absolute path to the file
365
+ * @returns {Promise<SaveToPhotoResult>} A **Promise** which resolves to the result
366
+ */
367
+ export function saveToPhoto(filePath: string): Promise<SaveToPhotoResult> {
368
+ if (!filePath?.trim().length) {
369
+ throw new Error('File path cannot be empty!');
370
+ }
371
+ return VideoTrim.saveToPhoto(filePath);
372
+ }
373
+
374
+ /**
375
+ * Present the system document picker to save a file
376
+ *
377
+ * @param {string} filePath: absolute path to the file
378
+ * @returns {Promise<SaveToDocumentsResult>} A **Promise** which resolves to the result
379
+ */
380
+ export function saveToDocuments(filePath: string): Promise<SaveToDocumentsResult> {
381
+ if (!filePath?.trim().length) {
382
+ throw new Error('File path cannot be empty!');
383
+ }
384
+ return VideoTrim.saveToDocuments(filePath);
385
+ }
386
+
387
+ /**
388
+ * Open the system share sheet for a file
389
+ *
390
+ * @param {string} filePath: absolute path to the file
391
+ * @returns {Promise<ShareResult>} A **Promise** which resolves to the result
392
+ */
393
+ export function share(filePath: string): Promise<ShareResult> {
394
+ if (!filePath?.trim().length) {
395
+ throw new Error('File path cannot be empty!');
396
+ }
397
+ return VideoTrim.share(filePath);
398
+ }
399
+
191
400
  export * from './NativeVideoTrim';
192
401
  export default VideoTrim;