Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: image asset ids #107

Merged
merged 11 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tricky-drinks-care.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@dotlottie/dotlottie-js": patch
---

fixes issues with wrong images ids
120 changes: 51 additions & 69 deletions apps/node/script.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1168,88 +1168,70 @@ async function create_showcase() {
});
}

async function debugScenario() {
async function logImages(arrayBuffer) {
// Create a new DotLottie instance from the array buffer
const dotLottie = await new DotLottie().fromArrayBuffer(arrayBuffer);

// Build the dotLottie instance
await dotLottie.build();

// Retrieve and log the image assets' IDs
const images = dotLottie.getImages().map((image) => image.id);

console.log({
images,
});

// Return the instance for further operations
return dotLottie;
}

async function debugScenario1() {
const dotLottie = new DotLottie();

await dotLottie
.setAuthor('Sam')
.setVersion('1.0')
.addAnimation({
id: 'folder',
url: 'https://lottie.host/32e49c72-af7a-4a79-97e4-fb0d115eae3e/wIqFzTMKsk.json',
})
.addStateMachine(
{
descriptor: {
id: 'openCloseFolder',
initial: 0,
},
states: [
{
name: "initialState",
type: "PlaybackState",
autoplay: true,
loop: false,
segment: [1.0, 2.0],
},
{
name: "open",
type: "PlaybackState",
autoplay: true,
loop: false,
segment: [1.0, 30.0],
},
{
name: "close",
type: "PlaybackState",
autoplay: true,
mode: "Reverse",
loop: false,
segment: [1.0, 30.0],
}
],
transitions: [
{
type: 'Transition',
from_state: 0,
to_state: 1,
string_event: {
value: 'open',
},
},
{
type: 'Transition',
from_state: 1,
to_state: 2,
string_event: {
value: 'close',
},
},
{
type: 'Transition',
from_state: 2,
to_state: 0,
on_complete_event: {},
},
],
listeners: [],
context_variables: [],
}
)
// Fetch the dotLottie file and convert it to an ArrayBuffer
const res = await fetch('https://lottie.host/7c65acc7-80e7-4bad-8098-6cebd17c8b92/ee8uf2TX6N.lottie');
const arrayBuffer = await res.arrayBuffer();

// Log the image assets on the first import
const dotLottie1 = await logImages(arrayBuffer);

// Rebuild the dotLottie instance (simulating re-import)
await dotLottie1.build();

// Convert the instance back to an ArrayBuffer
const buffer = await dotLottie1.toArrayBuffer({});

// Log the image assets after the second import
await logImages(buffer);
}

async function debugScenario2() {
const dotLottie = new DotLottie();

await dotLottie.addAnimation({
id: 'bull',
url: 'https://lottie.host/c7d4e60a-102a-4ca1-97f1-f73b2b5f0f7b/s2z6VrPpnn.json',
}).addAnimation({
id: 'shrek',
url: 'https://lottie.host/204e1620-32b7-46a1-8f99-eda64771b781/hYeiO04uqx.json'
})
.build()
.then((value) => {
return value.toArrayBuffer();
})
.then((value) => {
fs.writeFileSync('folder.lottie', Buffer.from(value));
fs.writeFileSync('new-issue.lottie', Buffer.from(value));
});

}

// create_showcase();

// debugScenario();
debugScenario1();
// debugScenario2();

createExplodingPigeon();
// createExplodingPigeon();
// createListenersAnimation();
// create_pigeon_fsm_eq_guard();
// create_pigeon_gt_gte_guard();
Expand Down
4 changes: 2 additions & 2 deletions apps/vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"devDependencies": {
"@dotlottie/dotlottie-js": "workspace:^",
"@vitejs/plugin-vue": "^4.1.0",
"typescript": "^5.0.2",
"typescript": "^5.6.3",
"vite": "^4.3.0",
"vue-tsc": "^1.2.0"
"vue-tsc": "^2.1.10"
}
}
3 changes: 2 additions & 1 deletion packages/dotlottie-js/jasmine/tsup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export default defineConfig(({ platform }) => {
platform,
target: ['esnext'],
tsconfig: 'tsconfig.build.json',
noExternal: platform === 'browser' ? ['fflate', 'browser-image-hash', 'valibot'] : ['browser-image-hash'],
noExternal:
platform === 'browser' ? ['fflate', 'file-type', 'browser-image-hash', 'valibot'] : ['browser-image-hash'],
loader: {
'.lottie': 'binary',
},
Expand Down
1 change: 1 addition & 0 deletions packages/dotlottie-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@lottie-animation-community/lottie-types": "^1.2.0",
"browser-image-hash": "^0.0.5",
"fflate": "^0.8.1",
"file-type": "^19.6.0",
"sharp": "^0.33.2",
"sharp-phash": "^2.1.0",
"valibot": "^0.13.1"
Expand Down
58 changes: 42 additions & 16 deletions packages/dotlottie-js/src/common/dotlottie-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,11 +221,12 @@ export class DotLottieCommon {
* @param newName - desired id and fileName,
* @param imageId - The id of the LottieImage to rename
*/
private _renameImage(animation: LottieAnimationCommon, newName: string, imageId: string): void {
animation.imageAssets.forEach((imageAsset) => {
private async _renameImage(animation: LottieAnimationCommon, newName: string, imageId: string): Promise<void> {
for (const imageAsset of animation.imageAssets) {
if (imageAsset.id === imageId) {
// Rename the LottieImage
imageAsset.renameImage(newName);
const oldPath = imageAsset.fileName;

await imageAsset.renameImage(newName);

if (!animation.data) throw createError('No animation data available.');

Expand All @@ -236,27 +237,42 @@ export class DotLottieCommon {
// Find the image asset inside the animation data and rename its path
for (const asset of animationAssets) {
if ('w' in asset && 'h' in asset) {
if (asset.id === imageId) {
if (asset.p === oldPath) {
asset.p = imageAsset.fileName;
}
}
}
}
});
}
}

private _renameImageAssets(): void {
const images: Map<string, LottieImageCommon[]> = new Map();
/**
* Generates a map of duplicate image ids and their count.
* @returns Map of duplicate image ids and their count.
*/
private _generateMapOfOccurencesFromImageIds(): Map<string, number> {
const dupeMap = new Map<string, number>();

this.animations.forEach((animation) => {
images.set(animation.id, animation.imageAssets);
animation.imageAssets.forEach((imageAsset) => {
if (dupeMap.has(imageAsset.id)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The id is not always the same as the filename, for example

id: image_0, filename: image_0.png
is not the same as
id: image_0, filename: 'image_0.webp'

right ? probably our ids should be the filename and we can get rid of using the image id in this case, no ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inside the lottie itself, images had an id field, and then a name and path attribute, thats why I mapped it like this to the image class.

The id is just the filename without the extension
In your example the id stayed the same despite changing the extension

const count = dupeMap.get(imageAsset.id) ?? 0;

dupeMap.set(imageAsset.id, count + 1);
} else {
dupeMap.set(imageAsset.id, 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be an occuranceMap not duplicateMap, right ? as based on this line, even if there is a unique image id, still would be pushed to the map with an occurance of 1

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was duplicateMap as duplicate filename not the content of the image, will change to filename occurence map

}
});
});

let size = 0;
return dupeMap;
}

images.forEach((value) => {
size += value.length;
});
/**
* Renames the image assets in all animations to avoid conflicts.
*/
private async _renameImageAssets(): Promise<void> {
const dupeMap = this._generateMapOfOccurencesFromImageIds();

for (let i = this.animations.length - 1; i >= 0; i -= 1) {
const animation = this.animations.at(i);
Expand All @@ -266,8 +282,18 @@ export class DotLottieCommon {
const image = animation.imageAssets.at(j);

if (image) {
this._renameImage(animation, `image_${size}`, image.id);
size -= 1;
let count = dupeMap.get(image.id) ?? 0;

if (count > 0) {
count -= 1;
}

dupeMap.set(image.id, count);

if (count > 0) {
// Rename the image
await this._renameImage(animation, `${image.id}_${count}`, image.id);
}
}
}
}
Expand Down Expand Up @@ -509,7 +535,7 @@ export class DotLottieCommon {

if (this.animations.length > 1) {
// Rename assets incrementally if there are multiple animations
this._renameImageAssets();
await this._renameImageAssets();
this._renameAudioAssets();
}

Expand Down
16 changes: 6 additions & 10 deletions packages/dotlottie-js/src/common/lottie-image-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import type { ZipOptions } from 'fflate';

import type { LottieAnimationCommon } from './lottie-animation-common';
import { dataUrlFromU8, DotLottieError } from './utils';
import { dataUrlFromU8, DotLottieError, getMimeTypeFromBase64 } from './utils';

export type ImageData = string | ArrayBuffer | Blob;

Expand Down Expand Up @@ -129,18 +129,14 @@ export class LottieImageCommon {
* Renames the id and fileName to newName.
* @param newName - A new id and filename for the image.
*/
public renameImage(newName: string): void {
public async renameImage(newName: string): Promise<void> {
this.id = newName;

if (this.fileName) {
let fileExt = this.fileName.split('.').pop();
const data = await this.toDataURL();

if (!fileExt) {
fileExt = '.png';
}
// Default to png if the file extension isn't available
this.fileName = `${newName}.${fileExt}`;
}
const mimeType = await getMimeTypeFromBase64(data);

this.fileName = `${newName}.${mimeType ? mimeType.split('/')[1] : 'png'}`;
}

public async toArrayBuffer(): Promise<ArrayBuffer> {
Expand Down
Loading
Loading