visualfries 0.1.4 → 0.1.6
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/dist/SceneBuilder.svelte.js +3 -3
- package/dist/components/hooks/MediaHook.js +10 -0
- package/dist/components/hooks/MediaSeekingHook.js +4 -1
- package/dist/fonts/GoogleFontsProvider.js +1 -1
- package/dist/managers/MediaManager.js +1 -0
- package/dist/managers/StateManager.svelte.js +2 -0
- package/dist/managers/SubtitlesManager.svelte.d.ts +1 -0
- package/dist/managers/SubtitlesManager.svelte.js +28 -0
- package/dist/utils/utils.d.ts +2 -2
- package/dist/utils/utils.js +6 -23
- package/package.json +1 -1
|
@@ -380,8 +380,7 @@ export class SceneBuilder {
|
|
|
380
380
|
this.stateManager.removeLoadingComponent(componentId);
|
|
381
381
|
}
|
|
382
382
|
buildCharactersList() {
|
|
383
|
-
|
|
384
|
-
this.stateManager.setCharactersList(buildCharactersListFromComponentsAndSubtitles(this.sceneData.layers, this.subtitles.data));
|
|
383
|
+
this.stateManager.setCharactersList(buildCharactersListFromComponentsAndSubtitles(this.sceneData.layers, this.subtitles.getSubtitlesCharactersList()));
|
|
385
384
|
}
|
|
386
385
|
render() {
|
|
387
386
|
const rendered = this.runSync(CommandType.RENDER);
|
|
@@ -400,9 +399,10 @@ export class SceneBuilder {
|
|
|
400
399
|
this.appManager.destroy();
|
|
401
400
|
this.domManager.destroy();
|
|
402
401
|
this.stateManager.destroy();
|
|
403
|
-
this.mediaManager.destroy();
|
|
404
402
|
this.timelineManager.destroy();
|
|
405
403
|
this.componentsManager.destroy();
|
|
404
|
+
// media manages should be destroyed last
|
|
405
|
+
this.mediaManager.destroy();
|
|
406
406
|
// Remove the container from the DI container cache
|
|
407
407
|
removeContainer(this.sceneData.id);
|
|
408
408
|
}
|
|
@@ -229,6 +229,16 @@ export class MediaHook {
|
|
|
229
229
|
async #handleDestroy() {
|
|
230
230
|
this.#destroyed = true;
|
|
231
231
|
this.#lastTargetTime = null;
|
|
232
|
+
// Release the media element back to the MediaManager
|
|
233
|
+
if (this.#mediaElement) {
|
|
234
|
+
const mediaType = this.#context.type === 'VIDEO' ? 'video' : 'audio';
|
|
235
|
+
const source = this.#context.contextData.source;
|
|
236
|
+
if (source && source.url) {
|
|
237
|
+
this.mediaManager.releaseMediaElement(source.url, mediaType);
|
|
238
|
+
}
|
|
239
|
+
this.#context.removeResource(mediaType === 'video' ? 'videoElement' : 'audioElement');
|
|
240
|
+
}
|
|
241
|
+
this.#mediaElement = undefined;
|
|
232
242
|
}
|
|
233
243
|
async handle(type, context) {
|
|
234
244
|
this.#context = context;
|
|
@@ -91,7 +91,9 @@ export class MediaSeekingHook {
|
|
|
91
91
|
};
|
|
92
92
|
// Add error event handling
|
|
93
93
|
media.onerror = () => {
|
|
94
|
-
|
|
94
|
+
if (media.error && media.error.code !== 4) {
|
|
95
|
+
console.error('Media error:', media.src, media.error);
|
|
96
|
+
}
|
|
95
97
|
};
|
|
96
98
|
}
|
|
97
99
|
}
|
|
@@ -100,6 +102,7 @@ export class MediaSeekingHook {
|
|
|
100
102
|
await this.#handleSetup();
|
|
101
103
|
}
|
|
102
104
|
async #handleDestroy() {
|
|
105
|
+
// Clear media element reference - MediaHook will handle releaseMediaElement
|
|
103
106
|
this.#mediaElement = undefined;
|
|
104
107
|
}
|
|
105
108
|
async #handleUpdate() {
|
|
@@ -22,7 +22,7 @@ export const createGoogleFontsProvider = () => {
|
|
|
22
22
|
const formattedFontFamily = fontFamily.replace(/\s/g, '+');
|
|
23
23
|
let apiUrl = `https://fonts.googleapis.com/css2?family=${formattedFontFamily}`;
|
|
24
24
|
if (text) {
|
|
25
|
-
apiUrl += `&text=${
|
|
25
|
+
apiUrl += `&text=${text}`;
|
|
26
26
|
}
|
|
27
27
|
// 1. Fetch the CSS file from Google Fonts.
|
|
28
28
|
const cssResponse = await fetch(apiUrl, {
|
|
@@ -181,6 +181,7 @@ export class MediaManager {
|
|
|
181
181
|
if (type === 'video') {
|
|
182
182
|
const videoElement = this.videoElements.get(mediaPath);
|
|
183
183
|
if (videoElement) {
|
|
184
|
+
// https://html.spec.whatwg.org/multipage/media.html#best-practices-for-authors-using-media-elements
|
|
184
185
|
videoElement.src = '';
|
|
185
186
|
videoElement.load(); // This frees up memory
|
|
186
187
|
this.videoElements.delete(mediaPath);
|
|
@@ -208,6 +208,7 @@ export declare class SubtitlesManager {
|
|
|
208
208
|
addNewSubtitleAfter(subtitleId: string, newText: string): void;
|
|
209
209
|
splitByChars(maxChars: number): void;
|
|
210
210
|
getText(): string | undefined;
|
|
211
|
+
getSubtitlesCharactersList(): string[];
|
|
211
212
|
updateSettings(newSettings: Partial<SceneSubtitlesSettings>): void;
|
|
212
213
|
findTextChunkTiming(searchText: string, options?: {
|
|
213
214
|
caseSensitive?: boolean;
|
|
@@ -201,6 +201,28 @@ function buildSubtitlesManager(timeManager, eventManager, sceneData, subtitles)
|
|
|
201
201
|
// Key format: "assetId:lang"
|
|
202
202
|
// This prevents recalculating unchanged languages when editing a single language
|
|
203
203
|
let validatedCollections = new Map();
|
|
204
|
+
function getSubtitlesCharactersList() {
|
|
205
|
+
// loop all subtitles in index
|
|
206
|
+
let charactersList = [];
|
|
207
|
+
for (const [assetId, assetIndex] of Object.entries(index)) {
|
|
208
|
+
for (const [lang, langIndex] of Object.entries(assetIndex)) {
|
|
209
|
+
for (const [subtitleId, subtitle] of Object.entries(langIndex)) {
|
|
210
|
+
const text = subtitle.text;
|
|
211
|
+
const characters = text.split('');
|
|
212
|
+
if (characters && characters.length > 0) {
|
|
213
|
+
for (const char of characters) {
|
|
214
|
+
if (!charactersList.includes(char)) {
|
|
215
|
+
charactersList.push(char);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// remove duplicates
|
|
223
|
+
charactersList = [...new Set(charactersList)];
|
|
224
|
+
return charactersList;
|
|
225
|
+
}
|
|
204
226
|
// Mark subtitle as dirty (needs word validation)
|
|
205
227
|
function markSubtitleDirty(subtitleId) {
|
|
206
228
|
validatedSubtitles.delete(subtitleId);
|
|
@@ -514,6 +536,9 @@ function buildSubtitlesManager(timeManager, eventManager, sceneData, subtitles)
|
|
|
514
536
|
get settings() {
|
|
515
537
|
return { ...settings };
|
|
516
538
|
},
|
|
539
|
+
getSubtitlesCharactersList() {
|
|
540
|
+
return getSubtitlesCharactersList();
|
|
541
|
+
},
|
|
517
542
|
updateSettings(newSettings) {
|
|
518
543
|
settings = { ...settings, ...newSettings };
|
|
519
544
|
eventManager.emit('subtitlessettingschange');
|
|
@@ -1272,6 +1297,9 @@ export class SubtitlesManager {
|
|
|
1272
1297
|
}
|
|
1273
1298
|
return this.builder.getText(this.assetId, this.language);
|
|
1274
1299
|
}
|
|
1300
|
+
getSubtitlesCharactersList() {
|
|
1301
|
+
return this.builder.getSubtitlesCharactersList();
|
|
1302
|
+
}
|
|
1275
1303
|
updateSettings(newSettings) {
|
|
1276
1304
|
return this.builder.updateSettings(newSettings);
|
|
1277
1305
|
}
|
package/dist/utils/utils.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Appearance, SceneLayer
|
|
1
|
+
import type { Appearance, SceneLayer } from '..';
|
|
2
2
|
import * as PIXI from 'pixi.js-legacy';
|
|
3
3
|
export declare function changeIdDeep<T>(obj: T): T;
|
|
4
|
-
export declare const buildCharactersListFromComponentsAndSubtitles: (layers: SceneLayer[],
|
|
4
|
+
export declare const buildCharactersListFromComponentsAndSubtitles: (layers: SceneLayer[], subtitlesCharactersList: string[]) => string[];
|
|
5
5
|
export declare const setPlacementAndOpacity: (obj: PIXI.Sprite | PIXI.Graphics, c: Appearance) => void;
|
package/dist/utils/utils.js
CHANGED
|
@@ -19,7 +19,7 @@ export function changeIdDeep(obj) {
|
|
|
19
19
|
}
|
|
20
20
|
return obj;
|
|
21
21
|
}
|
|
22
|
-
export const buildCharactersListFromComponentsAndSubtitles = function (layers,
|
|
22
|
+
export const buildCharactersListFromComponentsAndSubtitles = function (layers, subtitlesCharactersList) {
|
|
23
23
|
const characters = ' ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-,;:!$?\'"';
|
|
24
24
|
const charactestList = characters.split('');
|
|
25
25
|
const components = layers.flatMap((layer) => layer.components);
|
|
@@ -42,28 +42,11 @@ export const buildCharactersListFromComponentsAndSubtitles = function (layers, s
|
|
|
42
42
|
charactestList.push(...new Set(variantChars));
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const source = c.source;
|
|
51
|
-
if (source && source.assetId && subtitles[source.assetId]) {
|
|
52
|
-
for (const langSubs of Object.values(subtitles[source.assetId])) {
|
|
53
|
-
for (const subtitle of langSubs) {
|
|
54
|
-
const textList = subtitle.text.split('');
|
|
55
|
-
const missingChars = textList.filter((char) => {
|
|
56
|
-
// Check if the character is not whitespace, not in the characters list,
|
|
57
|
-
// and is a Unicode letter or number
|
|
58
|
-
return !/\s/.test(char) && !characters.includes(char) && /\p{L}|\p{N}/u.test(char);
|
|
59
|
-
});
|
|
60
|
-
// Add both uppercase and lowercase variants of missing chars
|
|
61
|
-
const variantChars = missingChars.flatMap((char) => [
|
|
62
|
-
char.toLowerCase(),
|
|
63
|
-
char.toUpperCase()
|
|
64
|
-
]);
|
|
65
|
-
charactestList.push(...new Set(variantChars));
|
|
66
|
-
}
|
|
45
|
+
if (subtitlesCharactersList.length > 0) {
|
|
46
|
+
for (const char of subtitlesCharactersList) {
|
|
47
|
+
if (!characters.includes(char)) {
|
|
48
|
+
// Add both uppercase and lowercase variants of missing chars
|
|
49
|
+
charactestList.push(char.toLowerCase(), char.toUpperCase());
|
|
67
50
|
}
|
|
68
51
|
}
|
|
69
52
|
}
|