Skip to content

Commit

Permalink
feat!: Fetch full-size source image for accurate aspect ratio (#4)
Browse files Browse the repository at this point in the history
* Fetch full-size source image for accurate aspect ratio; fix quality param bug

* update quality check

* imageDimensionsFromStream
  • Loading branch information
decepulis authored Dec 13, 2024
1 parent aab3646 commit 41356e7
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 13 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ console.log(blurDataURL, aspectRatio);
| width | `string` | Width of the output blurry image placeholder. | `100%` |
| height | `string` | Height of the output blurry image placeholder. | `100%` |
| blur | `number` | Standard deviation of the Gaussian blur. | `20` |
| quality | `number` | Quality of the output image. Increase this value to see more details but also increase the length of the data URL. | `1` |
| quality | `number` | Quality of the output image. Increase this value to see more details but also increase the length of the data. Set a quality greater than or equal to 1. URL. | `1` |
| thumbnailToken | `string` | Videos with playback restrictions may require a thumbnail token. See https://docs.mux.com/guides/video/secure-video-playback for details. | `undefined` |
| type | `webp` `png` `jpg` | Image type to use in the output blurry image placeholder. | `webp` |

Expand Down
43 changes: 31 additions & 12 deletions blurup.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { imageDimensionsFromData } from 'image-dimensions';
import { imageDimensionsFromStream } from 'image-dimensions';

const defaultOptions = {
type: 'webp',
Expand Down Expand Up @@ -34,10 +34,11 @@ export async function createBlurUp(playbackId, options) {

quality = parseFloat(quality);

if (quality >= 1) {
imageURL.searchParams.set('width', 16 * quality);
imageURL.searchParams.set('height', 16 * quality);
if (isNaN(quality) || quality < 1) {
throw new Error('[@mux/blurup] Quality must be greater or equal to 1');
}
imageURL.searchParams.set('width', 16 * quality);
imageURL.searchParams.set('height', 16 * quality);

time = parseFloat(time);

Expand All @@ -49,9 +50,20 @@ export async function createBlurUp(playbackId, options) {
imageURL.searchParams.set('token', thumbnailToken);
}

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

if (response.status === 403) {
// 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);

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


if (response.status === 403 || sourceResponse.status === 403) {
if (typeof options.thumbnailToken !== 'undefined') {
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 @@ -61,21 +73,28 @@ 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 { width, height } = imageDimensionsFromData(arrayBuffer);
const aspectRatio = width / height;
const blurDataURL =
blur == 0
? imageDataURL
: `data:image/svg+xml;charset=utf-8,${svgBlurImage(imageDataURL, svgWidth, svgHeight, blur)}`;
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,
Expand Down

0 comments on commit 41356e7

Please sign in to comment.