@imgly/editor-react-native 1.72.3 → 1.73.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/IMGLYEditorModule.podspec +23 -0
- package/android/build.gradle +9 -6
- package/android/src/main/AndroidManifest.xml +1 -1
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/activity/EditorActivity.kt +2 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/builder/EditorBuilder.kt +240 -282
- package/ios/Extensions/OnCreate+.swift +38 -62
- package/ios/IMGLYEditorModule.swift +1 -0
- package/ios/Model/EditorBuilder.swift +132 -225
- package/ios/Model/ModalEditorConfiguration.swift +40 -0
- package/ios/Model/Source.swift +8 -0
- package/package.json +1 -1
- package/src/NativeIMGLYEditor.ts +1 -1
- package/ios/View/ModalEditor.swift +0 -43
|
@@ -4,30 +4,31 @@ import android.graphics.Bitmap
|
|
|
4
4
|
import android.net.Uri
|
|
5
5
|
import android.util.Log
|
|
6
6
|
import androidx.compose.runtime.Composable
|
|
7
|
+
import androidx.core.graphics.createBitmap
|
|
8
|
+
import androidx.core.net.toUri
|
|
9
|
+
import kotlinx.coroutines.CancellationException
|
|
7
10
|
import kotlinx.coroutines.Dispatchers
|
|
8
|
-
import kotlinx.coroutines.Job
|
|
9
|
-
import kotlinx.coroutines.coroutineScope
|
|
10
11
|
import kotlinx.coroutines.flow.toList
|
|
11
|
-
import kotlinx.coroutines.launch
|
|
12
12
|
import kotlinx.coroutines.withContext
|
|
13
|
-
import ly.img.editor.
|
|
14
|
-
import ly.img.editor.
|
|
15
|
-
import ly.img.editor.
|
|
16
|
-
import ly.img.editor.
|
|
17
|
-
import ly.img.editor.
|
|
18
|
-
import ly.img.editor.
|
|
19
|
-
import ly.img.editor.
|
|
20
|
-
import ly.img.editor.
|
|
21
|
-
import ly.img.editor.
|
|
22
|
-
import ly.img.editor.
|
|
23
|
-
import ly.img.editor.
|
|
24
|
-
import ly.img.editor.
|
|
25
|
-
import ly.img.editor.
|
|
26
|
-
import ly.img.editor.
|
|
27
|
-
import ly.img.editor.
|
|
28
|
-
import ly.img.editor.
|
|
29
|
-
import ly.img.editor.core.
|
|
30
|
-
import ly.img.editor.core.
|
|
13
|
+
import ly.img.editor.Editor
|
|
14
|
+
import ly.img.editor.configuration.apparel.ApparelConfigurationBuilder
|
|
15
|
+
import ly.img.editor.configuration.apparel.callback.onCreate
|
|
16
|
+
import ly.img.editor.configuration.apparel.callback.onExport
|
|
17
|
+
import ly.img.editor.configuration.design.DesignConfigurationBuilder
|
|
18
|
+
import ly.img.editor.configuration.design.callback.onCreate
|
|
19
|
+
import ly.img.editor.configuration.design.callback.onExport
|
|
20
|
+
import ly.img.editor.configuration.photo.PhotoConfigurationBuilder
|
|
21
|
+
import ly.img.editor.configuration.photo.callback.onCreate
|
|
22
|
+
import ly.img.editor.configuration.photo.callback.onExport
|
|
23
|
+
import ly.img.editor.configuration.postcard.PostcardConfigurationBuilder
|
|
24
|
+
import ly.img.editor.configuration.postcard.callback.onCreate
|
|
25
|
+
import ly.img.editor.configuration.postcard.callback.onExport
|
|
26
|
+
import ly.img.editor.configuration.video.VideoConfigurationBuilder
|
|
27
|
+
import ly.img.editor.configuration.video.callback.onCreate
|
|
28
|
+
import ly.img.editor.configuration.video.callback.onExport
|
|
29
|
+
import ly.img.editor.core.EditorScope
|
|
30
|
+
import ly.img.editor.core.configuration.EditorConfiguration
|
|
31
|
+
import ly.img.editor.core.configuration.remember
|
|
31
32
|
import ly.img.editor.reactnative.module.model.EditorPreset
|
|
32
33
|
import ly.img.editor.reactnative.module.model.EditorResult
|
|
33
34
|
import ly.img.editor.reactnative.module.model.EditorSettings
|
|
@@ -36,13 +37,10 @@ import ly.img.engine.DesignBlock
|
|
|
36
37
|
import ly.img.engine.DesignBlockType
|
|
37
38
|
import ly.img.engine.Engine
|
|
38
39
|
import ly.img.engine.FillType
|
|
39
|
-
import ly.img.engine.MimeType
|
|
40
|
-
import ly.img.engine.addDefaultAssetSources
|
|
41
|
-
import ly.img.engine.addDemoAssetSources
|
|
42
40
|
import java.io.File
|
|
43
41
|
import java.io.FileOutputStream
|
|
42
|
+
import java.nio.ByteBuffer
|
|
44
43
|
import java.util.UUID
|
|
45
|
-
import kotlin.coroutines.cancellation.CancellationException
|
|
46
44
|
|
|
47
45
|
/** A closure used to return a [Result] with type [EditorResult] from the editor. */
|
|
48
46
|
typealias EditorBuilderResult = (Result<EditorResult?>) -> Unit
|
|
@@ -109,33 +107,41 @@ class EditorBuilder {
|
|
|
109
107
|
result: EditorBuilderResult,
|
|
110
108
|
onClose: (Throwable?) -> Unit,
|
|
111
109
|
) {
|
|
112
|
-
|
|
110
|
+
Editor(
|
|
113
111
|
license = settings.license,
|
|
114
|
-
baseUri =
|
|
112
|
+
baseUri = settings.baseUri.toUri(),
|
|
115
113
|
userId = settings.userId,
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
114
|
+
configuration = {
|
|
115
|
+
EditorConfiguration.remember(::PhotoConfigurationBuilder) {
|
|
116
|
+
onCreate = {
|
|
117
|
+
onCreate(
|
|
118
|
+
createScene = {
|
|
119
|
+
EditorBuilderDefaults.onCreateScene(
|
|
120
|
+
scope = this@Editor,
|
|
121
|
+
settings = settings,
|
|
122
|
+
defaultUri = EditorBuilderDefaults.defaultPhotoUri,
|
|
123
|
+
sourceType = EditorSourceType.IMAGE,
|
|
124
|
+
)
|
|
125
|
+
},
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
onExport = {
|
|
129
|
+
onExport(
|
|
130
|
+
postExport = {
|
|
131
|
+
val result = EditorBuilderDefaults.getExportResult(
|
|
132
|
+
scope = this@Editor,
|
|
133
|
+
byteBuffer = it,
|
|
134
|
+
)
|
|
135
|
+
result(Result.success(result))
|
|
136
|
+
},
|
|
137
|
+
error = {
|
|
138
|
+
result(Result.failure(it))
|
|
139
|
+
},
|
|
140
|
+
)
|
|
141
|
+
}
|
|
135
142
|
}
|
|
136
143
|
},
|
|
137
|
-
)
|
|
138
|
-
PhotoEditor(engineConfiguration = engineConfiguration) {
|
|
144
|
+
) {
|
|
139
145
|
onClose(it)
|
|
140
146
|
}
|
|
141
147
|
}
|
|
@@ -146,32 +152,40 @@ class EditorBuilder {
|
|
|
146
152
|
result: EditorBuilderResult,
|
|
147
153
|
onClose: (Throwable?) -> Unit,
|
|
148
154
|
) {
|
|
149
|
-
|
|
155
|
+
Editor(
|
|
150
156
|
license = settings.license,
|
|
151
|
-
baseUri =
|
|
157
|
+
baseUri = settings.baseUri.toUri(),
|
|
152
158
|
userId = settings.userId,
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
159
|
+
configuration = {
|
|
160
|
+
EditorConfiguration.remember(::ApparelConfigurationBuilder) {
|
|
161
|
+
onCreate = {
|
|
162
|
+
onCreate(
|
|
163
|
+
createScene = {
|
|
164
|
+
EditorBuilderDefaults.onCreateScene(
|
|
165
|
+
scope = this@Editor,
|
|
166
|
+
settings = settings,
|
|
167
|
+
defaultUri = "file:///android_asset/scene/apparel.scene".toUri(),
|
|
168
|
+
)
|
|
169
|
+
},
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
onExport = {
|
|
173
|
+
onExport(
|
|
174
|
+
postExport = {
|
|
175
|
+
val result = EditorBuilderDefaults.getExportResult(
|
|
176
|
+
scope = this@Editor,
|
|
177
|
+
byteBuffer = it,
|
|
178
|
+
)
|
|
179
|
+
result(Result.success(result))
|
|
180
|
+
},
|
|
181
|
+
error = {
|
|
182
|
+
result(Result.failure(it))
|
|
183
|
+
},
|
|
184
|
+
)
|
|
185
|
+
}
|
|
171
186
|
}
|
|
172
187
|
},
|
|
173
|
-
)
|
|
174
|
-
ApparelEditor(engineConfiguration = engineConfiguration) {
|
|
188
|
+
) {
|
|
175
189
|
onClose(it)
|
|
176
190
|
}
|
|
177
191
|
}
|
|
@@ -182,33 +196,40 @@ class EditorBuilder {
|
|
|
182
196
|
result: EditorBuilderResult,
|
|
183
197
|
onClose: (Throwable?) -> Unit,
|
|
184
198
|
) {
|
|
185
|
-
|
|
199
|
+
Editor(
|
|
186
200
|
license = settings.license,
|
|
187
|
-
baseUri =
|
|
201
|
+
baseUri = settings.baseUri.toUri(),
|
|
188
202
|
userId = settings.userId,
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
203
|
+
configuration = {
|
|
204
|
+
EditorConfiguration.remember(::DesignConfigurationBuilder) {
|
|
205
|
+
onCreate = {
|
|
206
|
+
onCreate(
|
|
207
|
+
createScene = {
|
|
208
|
+
EditorBuilderDefaults.onCreateScene(
|
|
209
|
+
scope = this@Editor,
|
|
210
|
+
settings = settings,
|
|
211
|
+
defaultUri = "file:///android_asset/scene/design.scene".toUri(),
|
|
212
|
+
)
|
|
213
|
+
},
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
onExport = {
|
|
217
|
+
onExport(
|
|
218
|
+
postExport = {
|
|
219
|
+
val result = EditorBuilderDefaults.getExportResult(
|
|
220
|
+
scope = this@Editor,
|
|
221
|
+
byteBuffer = it,
|
|
222
|
+
)
|
|
223
|
+
result(Result.success(result))
|
|
224
|
+
},
|
|
225
|
+
error = {
|
|
226
|
+
result(Result.failure(it))
|
|
227
|
+
},
|
|
228
|
+
)
|
|
229
|
+
}
|
|
207
230
|
}
|
|
208
231
|
},
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
DesignEditor(engineConfiguration = engineConfiguration) {
|
|
232
|
+
) {
|
|
212
233
|
onClose(it)
|
|
213
234
|
}
|
|
214
235
|
}
|
|
@@ -219,33 +240,40 @@ class EditorBuilder {
|
|
|
219
240
|
result: EditorBuilderResult,
|
|
220
241
|
onClose: (Throwable?) -> Unit,
|
|
221
242
|
) {
|
|
222
|
-
|
|
243
|
+
Editor(
|
|
223
244
|
license = settings.license,
|
|
224
|
-
baseUri =
|
|
245
|
+
baseUri = settings.baseUri.toUri(),
|
|
225
246
|
userId = settings.userId,
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
247
|
+
configuration = {
|
|
248
|
+
EditorConfiguration.remember(::PostcardConfigurationBuilder) {
|
|
249
|
+
onCreate = {
|
|
250
|
+
onCreate(
|
|
251
|
+
createScene = {
|
|
252
|
+
EditorBuilderDefaults.onCreateScene(
|
|
253
|
+
scope = this@Editor,
|
|
254
|
+
settings = settings,
|
|
255
|
+
defaultUri = "file:///android_asset/scene/postcard.scene".toUri(),
|
|
256
|
+
)
|
|
257
|
+
},
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
onExport = {
|
|
261
|
+
onExport(
|
|
262
|
+
postExport = {
|
|
263
|
+
val result = EditorBuilderDefaults.getExportResult(
|
|
264
|
+
scope = this@Editor,
|
|
265
|
+
byteBuffer = it,
|
|
266
|
+
)
|
|
267
|
+
result(Result.success(result))
|
|
268
|
+
},
|
|
269
|
+
error = {
|
|
270
|
+
result(Result.failure(it))
|
|
271
|
+
},
|
|
272
|
+
)
|
|
273
|
+
}
|
|
244
274
|
}
|
|
245
275
|
},
|
|
246
|
-
)
|
|
247
|
-
|
|
248
|
-
PostcardEditor(engineConfiguration = engineConfiguration) {
|
|
276
|
+
) {
|
|
249
277
|
onClose(it)
|
|
250
278
|
}
|
|
251
279
|
}
|
|
@@ -256,36 +284,44 @@ class EditorBuilder {
|
|
|
256
284
|
result: EditorBuilderResult,
|
|
257
285
|
onClose: (Throwable?) -> Unit,
|
|
258
286
|
) {
|
|
259
|
-
|
|
287
|
+
Editor(
|
|
260
288
|
license = settings.license,
|
|
261
|
-
baseUri =
|
|
289
|
+
baseUri = settings.baseUri.toUri(),
|
|
262
290
|
userId = settings.userId,
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
291
|
+
configuration = {
|
|
292
|
+
EditorConfiguration.remember(::VideoConfigurationBuilder) {
|
|
293
|
+
onCreate = {
|
|
294
|
+
onCreate(
|
|
295
|
+
createScene = {
|
|
296
|
+
EditorBuilderDefaults.onCreateScene(
|
|
297
|
+
scope = this@Editor,
|
|
298
|
+
settings = settings,
|
|
299
|
+
defaultUri = "file:///android_asset/scene/video.scene".toUri(),
|
|
300
|
+
)
|
|
301
|
+
},
|
|
302
|
+
)
|
|
303
|
+
}
|
|
304
|
+
onExport = {
|
|
305
|
+
onExport(
|
|
306
|
+
postExport = {
|
|
307
|
+
val result = EditorBuilderDefaults.getExportResult(
|
|
308
|
+
scope = this@Editor,
|
|
309
|
+
byteBuffer = it,
|
|
310
|
+
)
|
|
311
|
+
exportStatus = null
|
|
312
|
+
result(Result.success(result))
|
|
313
|
+
},
|
|
314
|
+
error = {
|
|
315
|
+
exportStatus = null
|
|
316
|
+
if (it !is CancellationException) {
|
|
317
|
+
result(Result.failure(it))
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
)
|
|
284
321
|
}
|
|
285
322
|
}
|
|
286
323
|
},
|
|
287
|
-
)
|
|
288
|
-
VideoEditor(engineConfiguration = engineConfiguration) {
|
|
324
|
+
) {
|
|
289
325
|
onClose(it)
|
|
290
326
|
}
|
|
291
327
|
}
|
|
@@ -295,181 +331,97 @@ class EditorBuilder {
|
|
|
295
331
|
/** Default implementations to ease the editor configuration process. */
|
|
296
332
|
object EditorBuilderDefaults {
|
|
297
333
|
/** The default image uri for the photo editor. */
|
|
298
|
-
val defaultPhotoUri: Uri =
|
|
334
|
+
val defaultPhotoUri: Uri = "file:///android_asset/photo-ui-empty.png".toUri()
|
|
299
335
|
|
|
300
336
|
/**
|
|
301
|
-
* Default plugin implementation for the *onCreate* callback.
|
|
302
|
-
*
|
|
303
|
-
* @param
|
|
304
|
-
* @param settings
|
|
305
|
-
* @param
|
|
306
|
-
* @param sourceType
|
|
337
|
+
* Default plugin implementation for the scene creation part of the *onCreate* callback.
|
|
338
|
+
*
|
|
339
|
+
* @param scope the [EditorScope] of the editor.
|
|
340
|
+
* @param settings the [EditorSettings] containing the relevant editor information.
|
|
341
|
+
* @param defaultUri the default source [Uri] if no [EditorSettings.source] is specified.
|
|
342
|
+
* @param sourceType the [EditorSourceType] defining of which type the source is.
|
|
307
343
|
*/
|
|
308
|
-
suspend fun
|
|
309
|
-
|
|
310
|
-
eventHandler: EditorEventHandler,
|
|
344
|
+
suspend fun onCreateScene(
|
|
345
|
+
scope: EditorScope,
|
|
311
346
|
settings: EditorSettings,
|
|
312
|
-
|
|
347
|
+
defaultUri: Uri,
|
|
313
348
|
sourceType: EditorSourceType = EditorSourceType.SCENE,
|
|
314
|
-
) =
|
|
349
|
+
) = scope.run {
|
|
350
|
+
editorContext.engine.scene.get()?.let { return@run it }
|
|
351
|
+
|
|
315
352
|
fun isValidUri(uri: Uri): Boolean = try {
|
|
316
353
|
uri.scheme != null && uri.path != null
|
|
317
|
-
} catch (
|
|
354
|
+
} catch (_: Exception) {
|
|
318
355
|
false
|
|
319
356
|
}
|
|
320
357
|
|
|
321
|
-
val
|
|
322
|
-
|
|
358
|
+
val source = settings.source
|
|
359
|
+
val uri = if (source != null) {
|
|
360
|
+
val sourceUri = source.source.toUri()
|
|
323
361
|
val isValid = isValidUri(sourceUri)
|
|
324
362
|
if (!isValid) {
|
|
325
363
|
throw IllegalArgumentException("The specified source is not a valid Uri.")
|
|
326
364
|
}
|
|
327
365
|
sourceUri
|
|
328
366
|
} else {
|
|
329
|
-
|
|
367
|
+
defaultUri
|
|
330
368
|
}
|
|
331
369
|
|
|
332
370
|
when (settings.source?.type ?: sourceType) {
|
|
333
371
|
EditorSourceType.IMAGE -> {
|
|
334
|
-
engine.scene.createFromImage(uri)
|
|
372
|
+
editorContext.engine.scene.createFromImage(uri)
|
|
335
373
|
}
|
|
336
374
|
EditorSourceType.VIDEO -> {
|
|
337
|
-
engine.scene.createFromVideo(uri)
|
|
375
|
+
editorContext.engine.scene.createFromVideo(uri)
|
|
338
376
|
}
|
|
339
|
-
|
|
340
377
|
else -> {
|
|
341
|
-
engine.scene.load(uri)
|
|
378
|
+
editorContext.engine.scene.load(uri)
|
|
342
379
|
}
|
|
343
380
|
}
|
|
344
|
-
|
|
345
|
-
launch {
|
|
346
|
-
val baseUri = Uri.parse(settings.baseUri)
|
|
347
|
-
engine.addDefaultAssetSources(baseUri = baseUri)
|
|
348
|
-
SystemGalleryPermission.setMode(SystemGalleryConfiguration.Disabled)
|
|
349
|
-
val context = engine.applicationContext
|
|
350
|
-
listOf(
|
|
351
|
-
AssetSourceType.GalleryAllVisuals,
|
|
352
|
-
AssetSourceType.GalleryImage,
|
|
353
|
-
AssetSourceType.GalleryVideo,
|
|
354
|
-
).forEach { type ->
|
|
355
|
-
engine.asset.addSource(SystemGalleryAssetSource(context, type))
|
|
356
|
-
}
|
|
357
|
-
engine.addDemoAssetSources(
|
|
358
|
-
sceneMode = engine.scene.getMode(),
|
|
359
|
-
withUploadAssetSources = true,
|
|
360
|
-
baseUri = baseUri,
|
|
361
|
-
)
|
|
362
|
-
val defaultTypeface = TypefaceProvider().provideTypeface(engine, "Roboto")
|
|
363
|
-
requireNotNull(defaultTypeface)
|
|
364
|
-
engine.asset.addSource(TextAssetSource(engine, defaultTypeface))
|
|
365
|
-
}
|
|
366
|
-
coroutineContext[Job]?.invokeOnCompletion {
|
|
367
|
-
eventHandler.send(HideLoading)
|
|
368
|
-
}
|
|
381
|
+
requireNotNull(editorContext.engine.scene.get())
|
|
369
382
|
}
|
|
370
383
|
|
|
371
384
|
/**
|
|
372
|
-
* Default plugin implementation
|
|
373
|
-
*
|
|
374
|
-
* @param
|
|
375
|
-
* @param
|
|
376
|
-
* @param thumbnailHeight
|
|
385
|
+
* Default plugin implementation that returns [EditorResult] based on the exported [byteBuffer]
|
|
386
|
+
*
|
|
387
|
+
* @param scope the [EditorScope] of the editor.
|
|
388
|
+
* @param byteBuffer the buffer that contains the exported data.
|
|
389
|
+
* @param thumbnailHeight the height of the generated thumbnail.
|
|
390
|
+
* @return a new [EditorResult] that contains a uri to temporary file with [byteBuffer] content,
|
|
391
|
+
* thumbnail and an exported scene string.
|
|
377
392
|
*/
|
|
378
|
-
suspend fun
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
mimeType: MimeType,
|
|
393
|
+
suspend fun getExportResult(
|
|
394
|
+
scope: EditorScope,
|
|
395
|
+
byteBuffer: ByteBuffer,
|
|
382
396
|
thumbnailHeight: Int = 100,
|
|
383
|
-
)
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
scene.getPages().forEach { page ->
|
|
391
|
-
block.setScopeEnabled(page, key = "layer/visibility", enabled = true)
|
|
392
|
-
block.setVisible(page, visible = true)
|
|
393
|
-
}
|
|
394
|
-
},
|
|
395
|
-
)
|
|
396
|
-
val tempFile = EditorDefaults.writeToTempFile(blob, mimeType)
|
|
397
|
-
val scene = engine.scene.get()
|
|
398
|
-
val sceneString = scene?.let {
|
|
399
|
-
checkForContentUris(engine)
|
|
400
|
-
engine.scene.saveToString(
|
|
401
|
-
scene = it,
|
|
402
|
-
allowedResourceSchemes = listOf("blob", "bundle", "file", "http", "https", "content"),
|
|
403
|
-
)
|
|
404
|
-
}
|
|
405
|
-
val sceneUri = sceneString?.let { saveScene(it) }
|
|
406
|
-
val firstPage = engine.scene.getPages().first()
|
|
407
|
-
engine.block.setVisible(firstPage, true)
|
|
408
|
-
val thumbnail = saveThumbnail(firstPage, engine, thumbnailHeight)
|
|
409
|
-
eventHandler.send(HideLoading)
|
|
410
|
-
return EditorResult(
|
|
411
|
-
scene = sceneUri.toString(),
|
|
412
|
-
artifact = Uri.fromFile(tempFile).toString(),
|
|
413
|
-
thumbnail = thumbnail.toString(),
|
|
397
|
+
) = scope.run {
|
|
398
|
+
val scene = editorContext.engine.scene.get()
|
|
399
|
+
val sceneString = scene?.let {
|
|
400
|
+
checkForContentUris(editorContext.engine)
|
|
401
|
+
editorContext.engine.scene.saveToString(
|
|
402
|
+
scene = it,
|
|
403
|
+
allowedResourceSchemes = listOf("blob", "bundle", "file", "http", "https", "content"),
|
|
414
404
|
)
|
|
415
405
|
}
|
|
406
|
+
val sceneUri = sceneString?.let { saveScene(it) }
|
|
407
|
+
val firstPage = editorContext.engine.scene.getPages().first()
|
|
408
|
+
editorContext.engine.block.setVisible(firstPage, visible = true)
|
|
409
|
+
val thumbnail = saveThumbnail(firstPage, editorContext.engine, thumbnailHeight)
|
|
410
|
+
EditorResult(
|
|
411
|
+
scene = sceneUri.toString(),
|
|
412
|
+
artifact = Uri.fromFile(byteBuffer.writeToFile()).toString(),
|
|
413
|
+
thumbnail = thumbnail.toString(),
|
|
414
|
+
)
|
|
416
415
|
}
|
|
417
416
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
* @param eventHandler The [EditorEventHandler] that should be used.
|
|
422
|
-
* @param mimeType The [MimeType] of the exported artifact.
|
|
423
|
-
* @param thumbnailHeight The height of the generated thumbnail.
|
|
424
|
-
*/
|
|
425
|
-
suspend fun onExportVideo(
|
|
426
|
-
engine: Engine,
|
|
427
|
-
eventHandler: EditorEventHandler,
|
|
428
|
-
mimeType: MimeType,
|
|
429
|
-
thumbnailHeight: Int = 100,
|
|
430
|
-
): EditorResult {
|
|
431
|
-
EditorBuilderDefaults.run {
|
|
432
|
-
eventHandler.send(ShowVideoExportProgressEvent(0f))
|
|
433
|
-
|
|
434
|
-
// First create the thumbnail and save the scene
|
|
435
|
-
// in order to finish before the video export sheet is done.
|
|
436
|
-
val page = engine.scene.getCurrentPage() ?: engine.scene.getPages().first()
|
|
437
|
-
val thumbnail = saveThumbnail(page, engine, thumbnailHeight)
|
|
438
|
-
val scene = engine.scene.get()
|
|
439
|
-
val sceneString = scene?.let {
|
|
440
|
-
checkForContentUris(engine)
|
|
441
|
-
engine.scene.saveToString(
|
|
442
|
-
scene = it,
|
|
443
|
-
allowedResourceSchemes = listOf("blob", "bundle", "file", "http", "https", "content"),
|
|
444
|
-
)
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
val buffer = engine.block.exportVideo(
|
|
448
|
-
block = page,
|
|
449
|
-
timeOffset = 0.0,
|
|
450
|
-
duration = engine.block.getDuration(page),
|
|
451
|
-
mimeType = mimeType,
|
|
452
|
-
progressCallback = { progress ->
|
|
453
|
-
eventHandler.send(
|
|
454
|
-
ShowVideoExportProgressEvent(progress.encodedFrames.toFloat() / progress.totalFrames),
|
|
455
|
-
)
|
|
456
|
-
},
|
|
457
|
-
)
|
|
458
|
-
|
|
459
|
-
val tempFile = EditorDefaults.writeToTempFile(buffer, mimeType)
|
|
460
|
-
eventHandler.send(DismissVideoExportEvent)
|
|
461
|
-
|
|
462
|
-
return EditorResult(
|
|
463
|
-
scene = sceneString,
|
|
464
|
-
artifact = Uri.fromFile(tempFile).toString(),
|
|
465
|
-
thumbnail = thumbnail.toString(),
|
|
466
|
-
)
|
|
417
|
+
private suspend fun ByteBuffer.writeToFile(): File = withContext(Dispatchers.IO) {
|
|
418
|
+
File.createTempFile(UUID.randomUUID().toString(), ".tmp").apply {
|
|
419
|
+
outputStream().channel.write(this@writeToFile)
|
|
467
420
|
}
|
|
468
421
|
}
|
|
469
422
|
|
|
470
423
|
private suspend fun saveScene(scene: String): Uri = withContext(Dispatchers.IO) {
|
|
471
|
-
val file = File
|
|
472
|
-
.createTempFile("cesdk_export_scene_" + UUID.randomUUID().toString(), ".scene")
|
|
424
|
+
val file = File.createTempFile("cesdk_export_scene_" + UUID.randomUUID().toString(), ".scene")
|
|
473
425
|
file.writeText(scene)
|
|
474
426
|
Uri.fromFile(file)
|
|
475
427
|
}
|
|
@@ -479,13 +431,19 @@ object EditorBuilderDefaults {
|
|
|
479
431
|
engine: Engine,
|
|
480
432
|
height: Int,
|
|
481
433
|
): Uri {
|
|
482
|
-
val frames = engine.block.generateVideoThumbnailSequence(
|
|
434
|
+
val frames = engine.block.generateVideoThumbnailSequence(
|
|
435
|
+
block = id,
|
|
436
|
+
thumbnailHeight = height,
|
|
437
|
+
timeBegin = 0.0,
|
|
438
|
+
timeEnd = 0.1,
|
|
439
|
+
numberOfFrames = 1,
|
|
440
|
+
).toList()
|
|
483
441
|
val firstFrame = frames.first()
|
|
484
|
-
val buffer = firstFrame.imageData
|
|
485
|
-
val bitmap = Bitmap.createBitmap(firstFrame.width, firstFrame.height, Bitmap.Config.ARGB_8888)
|
|
486
|
-
buffer.rewind()
|
|
487
|
-
bitmap.copyPixelsFromBuffer(buffer)
|
|
488
442
|
return withContext(Dispatchers.IO) {
|
|
443
|
+
val buffer = firstFrame.imageData
|
|
444
|
+
val bitmap = createBitmap(firstFrame.width, firstFrame.height)
|
|
445
|
+
buffer.rewind()
|
|
446
|
+
bitmap.copyPixelsFromBuffer(buffer)
|
|
489
447
|
val file = File.createTempFile(UUID.randomUUID().toString(), ".png")
|
|
490
448
|
FileOutputStream(file).use { out ->
|
|
491
449
|
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
|