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

Rework mpeg4 check, add support for some more mpeg4 and ISO base media formats #206

Merged
merged 9 commits into from May 4, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Binary file added fixture/fixture.3g2
Binary file not shown.
Binary file added fixture/fixture.f4a
Binary file not shown.
Binary file added fixture/fixture.f4b
Binary file not shown.
Binary file added fixture/fixture.f4p
Binary file not shown.
Binary file added fixture/fixture.f4v
Binary file not shown.
Binary file added fixture/fixture.m4b
Binary file not shown.
Binary file added fixture/fixture.m4p
Binary file not shown.
File renamed without changes.
12 changes: 10 additions & 2 deletions index.d.ts
Expand Up @@ -21,7 +21,6 @@ declare namespace fileType {
| '7z'
| 'dmg'
| 'mp4'
| 'm4v'
| 'mid'
| 'mkv'
| 'webm'
Expand Down Expand Up @@ -73,6 +72,7 @@ declare namespace fileType {
| 'pptx'
| 'xlsx'
| '3gp'
| '3g2'
| 'jp2'
| 'jpm'
| 'jpx'
Expand All @@ -99,7 +99,15 @@ declare namespace fileType {
| 'lnk'
| 'alias'
| 'voc'
| 'ac3';
| 'ac3'
| 'm4a'
| 'm4b'
| 'm4p'
| 'm4v'
| 'f4a'
| 'f4b'
| 'f4p'
| 'f4v';

interface FileTypeResult {
/**
Expand Down
134 changes: 60 additions & 74 deletions index.js
Expand Up @@ -266,25 +266,70 @@ const fileType = input => {
};
}

if (check([0x33, 0x67, 0x70, 0x35]) || // 3gp5
(
check([0x0, 0x0, 0x0]) && check([0x66, 0x74, 0x79, 0x70], {offset: 4}) &&
(
check([0x6D, 0x70, 0x34, 0x31], {offset: 8}) || // MP41
check([0x6D, 0x70, 0x34, 0x32], {offset: 8}) || // MP42
check([0x69, 0x73, 0x6F, 0x6D], {offset: 8}) || // ISOM
check([0x69, 0x73, 0x6F, 0x32], {offset: 8}) || // ISO2
check([0x6D, 0x6D, 0x70, 0x34], {offset: 8}) || // MMP4
check([0x4D, 0x34, 0x56], {offset: 8}) || // M4V
check([0x64, 0x61, 0x73, 0x68], {offset: 8}) // DASH
)
)) {
// `mov` format variants
if (
check([0x66, 0x72, 0x65, 0x65], {offset: 4}) || // `free`
check([0x6D, 0x64, 0x61, 0x74], {offset: 4}) || // `mdat` MJPEG
check([0x6D, 0x6F, 0x6F, 0x76], {offset: 4}) || // `moov`
check([0x77, 0x69, 0x64, 0x65], {offset: 4}) // `wide`
) {
return {
ext: 'mp4',
mime: 'video/mp4'
ext: 'mov',
mime: 'video/quicktime'
};
}

// File Type Box (https://en.wikipedia.org/wiki/ISO_base_media_file_format)
// It's not required to be first, but it's recommended to be. Almost all ISO base media files start with `ftyp` box.
// `ftyp` box must contain a brand major identifier, which must consist of ISO 8859-1 printable characters.
// Here we check for 8859-1 printable characters (for simplicity, it's a mask which also catches one non-printable character).
if (
check([0x66, 0x74, 0x79, 0x70], {offset: 4}) && // `ftyp`
(buffer[8] & 0x60) !== 0x00 && (buffer[9] & 0x60) !== 0x00 && (buffer[10] & 0x60) !== 0x00 && (buffer[11] & 0x60) !== 0x00 // Brand major
) {
// They all can have MIME `video/mp4` except `application/mp4` special-case which is hard to detect.
// For some cases, we're specific, everything else falls to `video/mp4` with `mp4` extension.
const brandMajor = buffer.toString('utf8', 8, 12);
switch (brandMajor) {
case 'mif1':
return {ext: 'heic', mime: 'image/heif'};
case 'msf1':
return {ext: 'heic', mime: 'image/heif-sequence'};
case 'heic': case 'heix':
return {ext: 'heic', mime: 'image/heic'};
case 'hevc': case 'hevx':
return {ext: 'heic', mime: 'image/heic-sequence'};
case 'qt ':
return {ext: 'mov', mime: 'video/quicktime'};
case 'M4V ': case 'M4VH': case 'M4VP':
return {ext: 'm4v', mime: 'video/x-m4v'};
case 'M4P ':
return {ext: 'm4p', mime: 'video/mp4'};
case 'M4B ':
return {ext: 'm4b', mime: 'audio/mp4'};
case 'M4A ':
return {ext: 'm4a', mime: 'audio/x-m4a'};
case 'F4V ':
return {ext: 'f4v', mime: 'video/mp4'};
case 'F4P ':
return {ext: 'f4p', mime: 'video/mp4'};
case 'F4A ':
return {ext: 'f4a', mime: 'audio/mp4'};
case 'F4B ':
return {ext: 'f4b', mime: 'audio/mp4'};
default:
if (brandMajor.startsWith('3g')) {
if (brandMajor.startsWith('3g2')) {
return {ext: '3g2', mime: 'video/3gpp2'};
}

return {ext: '3gp', mime: 'video/3gpp'};
}

return {ext: 'mp4', mime: 'video/mp4'};
}
}

if (check([0x4D, 0x54, 0x68, 0x64])) {
return {
ext: 'mid',
Expand Down Expand Up @@ -317,18 +362,6 @@ const fileType = input => {
}
}

if (check([0x0, 0x0, 0x0, 0x14, 0x66, 0x74, 0x79, 0x70, 0x71, 0x74, 0x20, 0x20]) ||
check([0x66, 0x72, 0x65, 0x65], {offset: 4}) || // Type: `free`
check([0x66, 0x74, 0x79, 0x70, 0x71, 0x74, 0x20, 0x20], {offset: 4}) ||
check([0x6D, 0x64, 0x61, 0x74], {offset: 4}) || // MJPEG
check([0x6D, 0x6F, 0x6F, 0x76], {offset: 4}) || // Type: `moov`
check([0x77, 0x69, 0x64, 0x65], {offset: 4})) {
return {
ext: 'mov',
mime: 'video/quicktime'
};
}

// RIFF file format which might be AVI, WAV, QCP, etc
if (check([0x52, 0x49, 0x46, 0x46])) {
if (check([0x41, 0x56, 0x49], {offset: 8})) {
Expand Down Expand Up @@ -402,13 +435,6 @@ const fileType = input => {
};
}

if (check([0x66, 0x74, 0x79, 0x70, 0x33, 0x67], {offset: 4})) {
return {
ext: '3gp',
mime: 'video/3gpp'
};
}

// Check for MPEG header at different starting offsets
for (let start = 0; start < 2 && start < (buffer.length - 16); start++) {
if (
Expand Down Expand Up @@ -449,15 +475,6 @@ const fileType = input => {
}
}

if (
check([0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x41], {offset: 4})
) {
return { // MPEG-4 layer 3 (audio)
ext: 'm4a',
mime: 'audio/mp4' // RFC 4337
};
}

// Needs to be before `ogg` check
if (check([0x4F, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64], {offset: 28})) {
return {
Expand Down Expand Up @@ -830,37 +847,6 @@ const fileType = input => {
};
}

// File Type Box (https://en.wikipedia.org/wiki/ISO_base_media_file_format)
if (check([0x66, 0x74, 0x79, 0x70], {offset: 4})) {
if (check([0x6D, 0x69, 0x66, 0x31], {offset: 8})) {
return {
ext: 'heic',
mime: 'image/heif'
};
}

if (check([0x6D, 0x73, 0x66, 0x31], {offset: 8})) {
return {
ext: 'heic',
mime: 'image/heif-sequence'
};
}

if (check([0x68, 0x65, 0x69, 0x63], {offset: 8}) || check([0x68, 0x65, 0x69, 0x78], {offset: 8})) {
return {
ext: 'heic',
mime: 'image/heic'
};
}

if (check([0x68, 0x65, 0x76, 0x63], {offset: 8}) || check([0x68, 0x65, 0x76, 0x78], {offset: 8})) {
return {
ext: 'heic',
mime: 'image/heic-sequence'
};
}
}

if (check([0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A])) {
return {
ext: 'ktx',
Expand Down
12 changes: 10 additions & 2 deletions package.json
Expand Up @@ -58,7 +58,6 @@
"7z",
"dmg",
"mp4",
"m4v",
"mid",
"mkv",
"webm",
Expand Down Expand Up @@ -124,7 +123,16 @@
"lnk",
"alias",
"voc",
"ac3"
"ac3",
"3g2",
"m4a",
"m4b",
"m4p",
"m4v",
"f4a",
"f4b",
"f4p",
"f4v"
],
"devDependencies": {
"@types/node": "^11.12.2",
Expand Down
12 changes: 10 additions & 2 deletions readme.md
Expand Up @@ -142,7 +142,6 @@ Type: [`stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream
- [`7z`](https://en.wikipedia.org/wiki/7z)
- [`dmg`](https://en.wikipedia.org/wiki/Apple_Disk_Image)
- [`mp4`](https://en.wikipedia.org/wiki/MPEG-4_Part_14#Filename_extensions)
- [`m4v`](https://en.wikipedia.org/wiki/M4V)
- [`mid`](https://en.wikipedia.org/wiki/MIDI)
- [`mkv`](https://en.wikipedia.org/wiki/Matroska)
- [`webm`](https://en.wikipedia.org/wiki/WebM)
Expand Down Expand Up @@ -193,7 +192,6 @@ Type: [`stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream
- [`docx`](https://en.wikipedia.org/wiki/Office_Open_XML)
- [`pptx`](https://en.wikipedia.org/wiki/Office_Open_XML)
- [`xlsx`](https://en.wikipedia.org/wiki/Office_Open_XML)
- [`3gp`](https://en.wikipedia.org/wiki/3GP_and_3G2)
- [`jp2`](https://en.wikipedia.org/wiki/JPEG_2000) - JPEG 2000
- [`jpm`](https://en.wikipedia.org/wiki/JPEG_2000) - JPEG 2000
- [`jpx`](https://en.wikipedia.org/wiki/JPEG_2000) - JPEG 2000
Expand Down Expand Up @@ -221,6 +219,16 @@ Type: [`stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream
- [`alias`](https://en.wikipedia.org/wiki/Alias_%28Mac_OS%29) - macOS Alias file
- [`voc`](https://wiki.multimedia.cx/index.php/Creative_Voice) - Creative Voice File
- [`ac3`](https://www.atsc.org/standard/a522012-digital-audio-compression-ac-3-e-ac-3-standard-12172012/) - ATSC A/52 Audio File
- [`3gp`](https://en.wikipedia.org/wiki/3GP_and_3G2#3GP) - Multimedia container format defined by the Third Generation Partnership Project (3GPP) for 3G UMTS multimedia services
- [`3g2`](https://en.wikipedia.org/wiki/3GP_and_3G2#3G2) - Multimedia container format defined by the 3GPP2 for 3G CDMA2000 multimedia services
- [`m4v`](https://en.wikipedia.org/wiki/M4V) - MPEG-4 Visual bitstreams
- [`m4p`](https://en.wikipedia.org/wiki/MPEG-4_Part_14#Filename_extensions) - MPEG-4 files with audio streams encrypted by FairPlay Digital Rights Management as were sold through the iTunes Store
- [`m4a`](https://en.wikipedia.org/wiki/M4A) - Audio-only MPEG-4 files
- [`m4b`](https://en.wikipedia.org/wiki/M4B) - Audiobook and podcast MPEG-4 files, which also contain metadata including chapter markers, images, and hyperlinks
- [`f4v`](https://en.wikipedia.org/wiki/Flash_Video) - ISO base media file format used by Adobe Flash Player
- [`f4p`](https://en.wikipedia.org/wiki/Flash_Video) - ISO base media file format protected by Adobe Access DRM used by Adobe Flash Player
- [`f4a`](https://en.wikipedia.org/wiki/Flash_Video) - Audio-only ISO base media file format used by Adobe Flash Player
- [`f4b`](https://en.wikipedia.org/wiki/Flash_Video) - Audiobook and podcast ISO base media file format used by Adobe Flash Player

*SVG isn't included as it requires the whole file to be read, but you can get it [here](https://github.com/sindresorhus/is-svg).*

Expand Down
11 changes: 9 additions & 2 deletions test.js
Expand Up @@ -82,6 +82,7 @@ const types = [
'pptx',
'xlsx',
'3gp',
'3g2',
'jp2',
'jpm',
'jpx',
Expand All @@ -108,7 +109,14 @@ const types = [
'lnk',
'alias',
'voc',
'ac3'
'ac3',
'm4v',
'm4p',
'm4b',
'f4v',
'f4p',
'f4b',
'f4a'
];

// Define an entry here only if the fixture has a different
Expand Down Expand Up @@ -152,7 +160,6 @@ const names = {
'fixture-isom',
'fixture-isomv2',
'fixture-mp4v2',
'fixture-m4v',
'fixture-dash',
'fixture-aac-adts'
],
Expand Down