Skip to content

Commit

Permalink
feat: provide dimensions for MP4 (#145)
Browse files Browse the repository at this point in the history
* feat: provide dimensions for MP4 (wip)

* chore(test): increase coverage

* chore(test): increase coverage

* chore(test): increase coverage to 100%

* chore(test): simplify and extend
  • Loading branch information
dominique-pfister authored Jun 16, 2023
1 parent c772986 commit dea5031
Show file tree
Hide file tree
Showing 11 changed files with 647 additions and 4 deletions.
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"type": "module",
"types": "src/index.d.ts",
"scripts": {
"test": "c8 mocha",
"test": "c8 mocha --spec=test/*.test.js --spec=test/mp4/*.test.js",
"lint": "eslint .",
"semantic-release": "semantic-release",
"docs": "npx typedoc --options .typedoc.cjs",
Expand Down Expand Up @@ -53,7 +53,6 @@
},
"mocha": {
"require": "test/setup-env.js",
"spec": "test/*.test.js",
"reporter": "mocha-multi-reporters",
"reporter-options": "configFile=.mocha-multi.json"
},
Expand Down
18 changes: 16 additions & 2 deletions src/MediaHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import mime from 'mime';
import { CopyObjectCommand, HeadObjectCommand, S3Client } from '@aws-sdk/client-s3';
import sizeOf from 'image-size';
import { Upload } from '@aws-sdk/lib-storage';
import { Parser } from './mp4/Parser.js';
import pkgJson from './package.cjs';

sizeOf.disableFS(true);
Expand Down Expand Up @@ -301,6 +302,16 @@ export default class MediaHandler {
if (!data) {
return {};
}
const info = new Parser(data, this._log).parse();
if (info) {
return {
width: String(info.width),
height: String(info.height),
duration: String(info.duration),
type: info.mimeType,
};
}

try {
const dimensions = sizeOf(data);
this._log.info(`[${c}] detected dimensions: ${dimensions.type} ${dimensions.width} x ${dimensions.height}`);
Expand Down Expand Up @@ -580,11 +591,14 @@ export default class MediaHandler {
const buffers = [];
if (!blob.meta.width) {
if (blob.data) {
const { width, height } = this._getDimensions(blob.data, c);
const { width, height, duration } = this._getDimensions(blob.data, c);
if (width) {
blob.meta.width = width;
blob.meta.height = height;
}
if (duration) {
blob.meta.duration = duration;
}
} else {
// create transform stream and store the first mb
const capture = new Transform({
Expand Down Expand Up @@ -743,7 +757,7 @@ export default class MediaHandler {
} = blob;
const ext = mime.getExtension(blob.contentType) || 'bin';
let fragment = '';
if (blob.meta && blob.meta.width && blob.meta.height) {
if (blob.meta && blob.meta.width && blob.meta.height && !blob.contentType?.match(/^video\/[^/]+$/)) {
fragment = `#width=${blob.meta.width}&height=${blob.meta.height}`;
}
blob.uri = `https://${ref}--${repo}--${owner}.hlx.page/media_${hash}.${ext}${fragment}`;
Expand Down
49 changes: 49 additions & 0 deletions src/mp4/Atom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2023 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

/**
* Generic Atom
*/
export class Atom {
constructor(parent, buf, type, pos) {
this.parent = parent;
this.buf = buf;
this.type = type;
this.pos = pos || 0;
this.children = [];

parent?.children.push(this);
}

/**
* Assert that an atom has at least that length.
*
* @param {String} name name
* @param {Number} num number of bytes expected
*/
assertLength(name, num) {
const { buf, pos } = this;
if (buf.length < num) {
throw new Error(`[${name}] ${this.getPath()} (${pos}): atom should contain at least ${num} bytes, found: ${buf.length}`);
}
}

getPath() {
const { parent, type } = this;
return `${parent?.getPath() || ''}/${type}`;
}

toString() {
const { buf, type, pos } = this;
return `${type}: [${pos}-${pos + buf.length}]`;
}
}
28 changes: 28 additions & 0 deletions src/mp4/FTYPAtom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2023 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

/* eslint-disable no-bitwise */

import { Atom } from './Atom.js';

/**
* File type atom.
*/
export class FTYPAtom extends Atom {
parseContent(context) {
const { buf } = this;
const { name } = context;

this.assertLength(name, 4);
context.filetype = buf.toString('ascii', 0, 4);
}
}
32 changes: 32 additions & 0 deletions src/mp4/HDLRAtom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2023 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

/* eslint-disable no-bitwise */

import { Atom } from './Atom.js';

/**
* Handler reference atom.
*/
export class HDLRAtom extends Atom {
parseContent(context) {
const { buf, pos } = this;
const { tracks, name } = context;

this.assertLength(name, 24);
if (tracks.length === 0) {
throw new Error(`[${name}] ${this.getPath()} (${pos}): no tracks added by 'tkhd'`);
}
const track = tracks[tracks.length - 1];
track.subtype = buf.toString('ascii', 8, 12);
}
}
35 changes: 35 additions & 0 deletions src/mp4/MVHDAtom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2023 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import { Atom } from './Atom.js';

/**
* Movie header atom.
*/
export class MVHDAtom extends Atom {
parseContent(context) {
const { buf, pos } = this;
const { movie, name, log } = context;

this.assertLength(name, 100);

const version = buf.readUInt8();
let timescale = buf.readInt32BE(12);
if (timescale <= 0) {
log.warn(`[${name}] ${this.getPath()} (${pos + 12}): invalid time scale ${timescale}, defaulting to 1`);
timescale = 1;
}
const duration = version === 1
? Number(buf.readBigUInt64BE(16))
: buf.readUInt32BE(16);
movie.duration = Math.round(duration / timescale);
}
}
Loading

0 comments on commit dea5031

Please sign in to comment.