Skip to content

Commit

Permalink
fix: async / await issue leading to stack overflow
Browse files Browse the repository at this point in the history
issue was that we were waiting to long because of the Promise.all
before calling imageDimensionsFromStream. sometimes it already
fetched the whole image and calling a [].push(...spread) in the lib
with 100000's of items causes a stack overflow
  • Loading branch information
luwes committed Dec 13, 2024
1 parent 41356e7 commit 00bb4e6
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 39 deletions.
83 changes: 45 additions & 38 deletions blurup.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,21 +50,54 @@ export async function createBlurUp(playbackId, options) {
imageURL.searchParams.set('token', thumbnailToken);
}

const fetchOptions = { headers: { Accept: `image/${type}` } }
const imageFetch = fetch(imageURL, fetchOptions);
const fetchOptions = { headers: { Accept: `image/${type}` } };

const [{ width, height }, imageDataURL] = await Promise.all([
// first, let's get the image size from the source image
getSourceImageDimensions(new URL(imageURL), fetchOptions),
// next, let's get the tiny image data URL
getTinyImageDataURL(new URL(imageURL), fetchOptions),
]);

const aspectRatio = width / height;
const blurDataURL =
blur == 0
? imageDataURL
: `data:image/svg+xml;charset=utf-8,${svgBlurImage(imageDataURL, svgWidth, svgHeight, blur)}`;

return {
width,
height,
aspectRatio,
imageURL,
imageDataURL,
blurDataURL,
};
}

async function getSourceImageDimensions(imageURL, fetchOptions) {
// we also want to fetch the full-size source image from mux,
// so that we can measure its width and height
const sourceURL = new URL(imageURL);
sourceURL.searchParams.delete('width');
sourceURL.searchParams.delete('height');
const sourceFetch = fetch(sourceURL, fetchOptions);
imageURL.searchParams.delete('width');
imageURL.searchParams.delete('height');
const response = await fetch(imageURL, fetchOptions);
validateResponse(imageURL, response);

const [response, sourceResponse] = await Promise.all([imageFetch, sourceFetch]);

return imageDimensionsFromStream(response.body);
}

async function getTinyImageDataURL(imageURL, fetchOptions) {
const response = await fetch(imageURL, fetchOptions);
validateResponse(imageURL, response);

const arrayBuffer = await response.arrayBuffer();
const base64String = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
return `data:${response.headers.get('content-type')};base64,${base64String}`;
}

if (response.status === 403 || sourceResponse.status === 403) {
if (typeof options.thumbnailToken !== 'undefined') {
function validateResponse(imageURL, response) {
if (response.status === 403) {
if (imageURL.searchParams.has('token')) {
throw new Error(
`[@mux/blurup] Error fetching thumbnail. 403: Forbidden. The thumbnailToken option may be invalid. See https://docs.mux.com/guides/video/secure-video-playback for more information.`
);
Expand All @@ -73,41 +106,15 @@ export async function createBlurUp(playbackId, options) {
`[@mux/blurup] Error fetching thumbnail. 403: Forbidden. This Playback ID may require a thumbnail token. See https://docs.mux.com/guides/video/secure-video-playback for more information.`
);
}
} else if (response.status >= 400 ) {
} else if (response.status >= 400) {
throw new Error(
`[@mux/blurhash] Error fetching thumbnail. ${response.status}: ${response.statusText}`
);
} else if (sourceResponse.status >= 400 ) {
throw new Error(
`[@mux/blurhash] Error fetching thumbnail. ${sourceResponse.status}: ${sourceResponse.statusText}`
);
}

// first, let's get the small image and blur it up a bit
const arrayBuffer = await response.arrayBuffer();
const base64String = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
const imageDataURL = `data:${response.headers.get('content-type')};base64,${base64String}`;
const blurDataURL =
blur == 0
? imageDataURL
: `data:image/svg+xml;charset=utf-8,${svgBlurImage(imageDataURL, svgWidth, svgHeight, blur)}`;

// next, let's get the image size from the source image response
const { width, height } = await imageDimensionsFromStream(sourceResponse.body);
const aspectRatio = width / height;

return {
width,
height,
aspectRatio,
imageURL,
imageDataURL,
blurDataURL,
};
}

function svgBlurImage(tinyImageDataURL, width, height, stdDeviation) {
const svg = /*html*/`<svg xmlns="http://www.w3.org/2000/svg" ${width ? `width="${width}"` : ''} ${height ? `height="${height}"` : ''}><filter id="b" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation="${stdDeviation}"/><feComponentTransfer><feFuncA type="discrete" tableValues="1 1"/></feComponentTransfer></filter><g filter="url(#b)"><image width="100%" height="100%" preserveAspectRatio="xMidYMid slice" href="${tinyImageDataURL}"/></g></svg>`;
const svg = /*html*/ `<svg xmlns="http://www.w3.org/2000/svg" ${width ? `width="${width}"` : ''} ${height ? `height="${height}"` : ''}><filter id="b" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation="${stdDeviation}"/><feComponentTransfer><feFuncA type="discrete" tableValues="1 1"/></feComponentTransfer></filter><g filter="url(#b)"><image width="100%" height="100%" preserveAspectRatio="xMidYMid slice" href="${tinyImageDataURL}"/></g></svg>`;
return svg.replace(/#/g, '%23');
}

Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ <h1>Mux BlurUp Playground</h1>
quality: document.querySelector('#quality').value,
});

console.log(`BlurUp for ${playbackId}`)
console.log(`BlurUp for ${playbackId}`);
console.log(JSON.stringify(blurUp, null, 2));

blurUpImg.style.aspectRatio = `${blurUp.aspectRatio}`;
Expand Down

0 comments on commit 00bb4e6

Please sign in to comment.