-
Notifications
You must be signed in to change notification settings - Fork 8
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
Feat/parse codec #14
Merged
Merged
Feat/parse codec #14
Changes from 34 commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
2c771b3
feat: intial codec parsing by container work
brandonocasey d9a0c82
parse webm/mkv
brandonocasey a33daa6
parse ogg
brandonocasey 13e075e
testing
brandonocasey bca9231
final parsing
brandonocasey 6d5bf92
fix content generation
brandonocasey 5ebc6be
better format gen, seed file, and tests
brandonocasey bd64001
more parsing
brandonocasey 7ef60bc
final tests
brandonocasey e813a49
ignore test data file in istanbul
brandonocasey 3ad918f
break ts code into helper
brandonocasey ba8ccc7
parse blocks
brandonocasey f2fd732
refactor
brandonocasey 58b3f65
test block decoder
brandonocasey 79156c9
minor fixes
brandonocasey 3be5018
ac-3/ec-3 with id3 parsing
brandonocasey 9688126
aac/mp3 with metadata
brandonocasey e86e899
math based byte helpers
brandonocasey 9194c05
more helpers and fixes
brandonocasey 973d050
minor fixes and doc updates
brandonocasey d98f7d6
small fixes
brandonocasey f0eabaf
fix: parse multiple id3 sections from (#21)
brandonocasey e077a4c
fix: handle m2ts with no streams
brandonocasey eafd07a
Merge branch 'main' into feat/parse-codec
brandonocasey e9fe7c0
linter error
brandonocasey 54090c7
test fixes
brandonocasey c372919
ie 11 fixes
brandonocasey e465480
various fixes
brandonocasey 52b8913
better cli for parse blocks
brandonocasey b13386d
rename parse-container-codec to parse-format
brandonocasey 6416079
document mp4 helpers, uncommited code
brandonocasey 1dea015
update comment
brandonocasey f461479
break on invalid box
brandonocasey f670fe3
allow pipe to parse-format
brandonocasey 5973797
merge stdin with other streams, null check lastResult
brandonocasey File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
There are no files selected for viewing
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,332 @@ | ||
#!/usr/bin/env node | ||
/* eslint-disable no-console */ | ||
const shelljs = require('shelljs'); | ||
const childProcess = require('child_process'); | ||
const path = require('path'); | ||
|
||
const baseDir = path.join(__dirname, '..', 'test', 'fixtures', 'formats'); | ||
const DURATION = '0.01s'; | ||
const INPUT_FILE = path.join(__dirname, 'big-buck-bunny.mp4'); | ||
|
||
shelljs.rm('-rf', baseDir); | ||
|
||
const promiseSpawn = function(bin, args, options = {}) { | ||
process.setMaxListeners(1000); | ||
|
||
return new Promise((resolve, reject) => { | ||
const child = childProcess.spawn(bin, args, options); | ||
|
||
let stdout = ''; | ||
let stderr = ''; | ||
let out = ''; | ||
|
||
child.stdout.on('data', function(chunk) { | ||
stdout += chunk; | ||
out += chunk; | ||
}); | ||
|
||
child.stderr.on('data', function(chunk) { | ||
stderr += chunk; | ||
out += chunk; | ||
}); | ||
|
||
const kill = () => child.kill(); | ||
|
||
process.on('SIGINT', kill); | ||
process.on('SIGQUIT', kill); | ||
process.on('exit', kill); | ||
|
||
child.on('close', (status) => resolve({ | ||
cmd: [bin].concat(args), | ||
status, | ||
out: out.toString(), | ||
stderr: stderr.toString(), | ||
stdout: stdout.toString() | ||
})); | ||
}); | ||
}; | ||
|
||
const ffmpeg = (args) => promiseSpawn('ffmpeg', [ | ||
'-hide_banner', | ||
'-loglevel', 'error', | ||
'-y', | ||
'-i', INPUT_FILE, | ||
'-t', DURATION | ||
].concat(args)); | ||
|
||
const audioCodecs = [ | ||
{audioCodec: 'aac', args: ['-c:a', 'aac', '-metadata', 'title="Big Buck Bunny"']}, | ||
{audioCodec: 'mp4a.40.2', args: ['-c:a', 'aac']}, | ||
{audioCodec: 'mp4a.40.5', args: ['-c:a', 'aac', '-profile:a', 'aac_he']}, | ||
{audioCodec: 'mp4a.40.29', args: ['-c:a', 'aac', '-profile:a', 'aac_he_v2']}, | ||
{audioCodec: 'mp4a.40.34', args: ['-c:a', 'mp3']}, | ||
{audioCodec: 'mp3', args: ['-c:a', 'mp3', '-metadata', 'title="Big Buck Bunny"']}, | ||
{audioCodec: 'opus', args: ['-c:a', 'libopus']}, | ||
{audioCodec: 'ac-3', args: ['-c:a', 'ac3']}, | ||
{audioCodec: 'ec-3', args: ['-c:a', 'eac3']}, | ||
{audioCodec: 'vorbis', args: ['-c:a', 'libvorbis']}, | ||
{audioCodec: 'flac', args: ['-c:a', 'flac']}, | ||
{audioCodec: 'alac', args: ['-c:a', 'alac']}, | ||
{audioCodec: 'speex', args: ['-c:a', 'speex']} | ||
]; | ||
|
||
const videoCodecs = [ | ||
// TODO: use another encoder, ffmpeg does not support codecPrivate for vp09 | ||
// TODO: generate more formats | ||
// profile.level.depth.chroma.[color-primary].[transferchar].[matrixco].[blacklevel] | ||
{videoCodec: 'vp09.01.00.00.00.00.00.20.00', args: ['-c:v', 'vp9']}, | ||
{videoCodec: 'vp8', args: ['-c:v', 'vp8']}, | ||
{videoCodec: 'theora', args: ['-c:v', 'theora']}, | ||
{videoCodec: 'avc1.42c00d', args: ['-c:v', 'libx264', '-profile:v', 'baseline', '-level', '1.3']}, | ||
{videoCodec: 'avc1.4d401e', args: ['-c:v', 'libx264', '-profile:v', 'main', '-level', '3.0']}, | ||
{videoCodec: 'avc1.640028', args: ['-c:v', 'libx264', '-profile:v', 'high', '-level', '4.0']}, | ||
|
||
// https://trac.ffmpeg.org/ticket/2901 | ||
// aka profile is first 4 bits, level is second 4 bits | ||
{videoCodec: 'mp4v.20.9', args: ['-c:v', 'mpeg4', '-profile:v', '0', '-level', '9']}, | ||
{videoCodec: 'mp4v.20.240', args: ['-c:v', 'mpeg4', '-profile:v', '15', '-level', '0']}, | ||
{videoCodec: 'hvc1.1.6.H120.90', args: ['-c:v', 'libx265', '-tag:v', 'hvc1', '-x265-params', 'profile=main12:level-idc=4.0']}, | ||
{videoCodec: 'hev1.1.6.H150.90', args: ['-c:v', 'libx265', '-x265-params', 'profile=main12:level-idc=5.0']}, | ||
{videoCodec: 'hev1.1.6.L60.90', args: ['-c:v', 'libx265', '-x265-params', 'profile=main12:level-idc=4.0:no-high-tier']}, | ||
{videoCodec: 'hev1.1.6.H120.90', args: ['-c:v', 'libx265', '-x265-params', 'profile=main12:level-idc=4.0']}, | ||
{videoCodec: 'hev1.4.10.H120.9c.8', args: ['-c:v', 'libx265', '-pix_fmt', 'yuv444p10', '-x265-params', 'profile=main12:level-idc=4.0']}, | ||
|
||
// TODO: generate more av1 formats | ||
{videoCodec: 'av01.0.00M.08.0.110', args: ['-strict', 'experimental', '-c:v', 'av1', '-cpu-used', '8']} | ||
]; | ||
|
||
const buildCodecs = (changeFn) => { | ||
const allCodecs = []; | ||
|
||
const find = ({audioCodec, videoCodec}) => | ||
allCodecs.find((c) => c.audioCodec === audioCodec && c.videoCodec === videoCodec); | ||
|
||
videoCodecs.forEach(function({args, videoCodec}) { | ||
args = args.slice(); | ||
args.unshift('-an'); | ||
const changed = changeFn({args, videoCodec}); | ||
|
||
if (changed && !find(changed)) { | ||
allCodecs.push(changed); | ||
} | ||
}); | ||
|
||
audioCodecs.forEach(function({args, audioCodec}) { | ||
args = args.slice(); | ||
args.unshift('-vn'); | ||
const changed = changeFn({args, audioCodec}); | ||
|
||
if (changed && !find(changed)) { | ||
allCodecs.push(changed); | ||
} | ||
}); | ||
|
||
videoCodecs.forEach(function(video) { | ||
audioCodecs.forEach(function(audio) { | ||
const changed = changeFn({ | ||
audioCodec: audio.audioCodec, | ||
videoCodec: video.videoCodec, | ||
args: video.args.slice().concat(audio.args.slice()) | ||
}); | ||
|
||
if (changed && !find(changed)) { | ||
allCodecs.push(changed); | ||
} | ||
}); | ||
}); | ||
|
||
return allCodecs; | ||
}; | ||
|
||
const containerCodecs = { | ||
mp4: buildCodecs((c) => { | ||
if (c.audioCodec && (/^(alac|flac|opus|speex)/).test(c.audioCodec)) { | ||
return null; | ||
} | ||
|
||
if (c.videoCodec && (/^(vp8|theora)/).test(c.videoCodec)) { | ||
return null; | ||
} | ||
|
||
return c; | ||
}), | ||
mov: buildCodecs((c) => { | ||
if (c.audioCodec && (/^(flac|opus)/).test(c.audioCodec)) { | ||
return null; | ||
} | ||
|
||
if (c.videoCodec && (/^(vp8|vp9|vp09|av01)/.test(c.videoCodec))) { | ||
return null; | ||
} | ||
|
||
return c; | ||
}), | ||
mkv: buildCodecs((c) => { | ||
// hvc1 is an mp4 only codec designation | ||
if (c.videoCodec && (/^hvc1/).test(c.videoCodec)) { | ||
return null; | ||
} | ||
|
||
// ffmpeg does not support codecPrivate for vp9 | ||
// so we can only use the base codec | ||
if (c.videoCodec && (/^vp09|vp9/).test(c.videoCodec)) { | ||
c.videoCodec = 'vp9'; | ||
} | ||
|
||
return c; | ||
}), | ||
// TODO: should webm support more content types?? | ||
webm: buildCodecs((c) => { | ||
if (c.videoCodec && !(/^(av01|vp8|vp09|vp9)/).test(c.videoCodec)) { | ||
return null; | ||
} | ||
|
||
// ffmpeg does not support codecPrivate for vp9 | ||
// so we can only use the base codec | ||
if (c.videoCodec && (/^vp09|vp9/).test(c.videoCodec)) { | ||
c.videoCodec = 'vp9'; | ||
} | ||
|
||
if (c.audioCodec && !(/^(vorbis|opus)/).test(c.audioCodec)) { | ||
return null; | ||
} | ||
|
||
return c; | ||
}), | ||
avi: buildCodecs((c) => { | ||
if (c.videoCodec && (/^(hvc1)/).test(c.videoCodec)) { | ||
return null; | ||
} | ||
|
||
// verify that a correctly tagged avi file works | ||
// ffmpeg doesn't do this... | ||
if (c.videoCodec && c.videoCodec === 'hev1.4.10.H120.9c.8') { | ||
c.args.push('-tag:v', 'HEVC'); | ||
} | ||
|
||
if (c.audioCodec && (/^(opus|alac)/).test(c.audioCodec)) { | ||
return null; | ||
} | ||
|
||
// avi does not support codec parameters | ||
const match = c.videoCodec && c.videoCodec.match(/^(av01|vp09|vp9)/); | ||
|
||
if (match && match[1]) { | ||
if (match[1] === 'vp09') { | ||
c.videoCodec = 'vp9'; | ||
} else { | ||
c.videoCodec = match[1]; | ||
} | ||
} | ||
|
||
return c; | ||
}), | ||
ts: buildCodecs((c) => { | ||
if (c.videoCodec && (/^(vp8|vp09|vp9|theora|hvc1|av01)/).test(c.videoCodec)) { | ||
return null; | ||
} | ||
|
||
if (c.audioCodec && (/^(mp4a.40.29|mp4a.40.5|alac|vorbis|flac|speex)/).test(c.audioCodec)) { | ||
return null; | ||
} | ||
|
||
// ts does not support codec parameters | ||
const match = c.videoCodec && c.videoCodec.match(/^(mp4v.20)/); | ||
|
||
if (match && match[1]) { | ||
c.videoCodec = match[1]; | ||
} | ||
|
||
return c; | ||
}), | ||
ogg: buildCodecs((c) => { | ||
// ogg only supports theora/vp8 video | ||
if (c.videoCodec && !(/^(vp8|theora)/).test(c.videoCodec)) { | ||
return null; | ||
} | ||
|
||
// ogg only supports flac, opus, speex, vorbis audio | ||
if (c.audioCodec && !(/^(flac|opus|speex|vorbis)/).test(c.audioCodec)) { | ||
return null; | ||
} | ||
|
||
return c; | ||
}), | ||
wav: buildCodecs((c) => { | ||
// wav does not support video | ||
if (c.videoCodec || !c.audioCodec) { | ||
return null; | ||
} | ||
|
||
if ((/^(alac|opus)/).test(c.audioCodec)) { | ||
return null; | ||
} | ||
|
||
return c; | ||
|
||
}), | ||
aac: [ | ||
{audioCodec: 'aac', args: ['-vn', '-metadata', 'title="aac"']} | ||
], | ||
mp3: [ | ||
{audioCodec: 'mp3', args: ['-vn', '-metadata', 'title="mp3"']} | ||
], | ||
ac3: [ | ||
{audioCodec: 'ac-3', args: ['-vn', '-c:a', 'ac3', '-metadata', 'title="ac3"']}, | ||
{audioCodec: 'ec-3', args: ['-vn', '-c:a', 'eac3', '-metadata', 'title="eac3"']} | ||
], | ||
flac: [ | ||
{audioCodec: 'flac', args: ['-vn', '-c:a', 'flac', '-metadata', 'title="flac"']} | ||
], | ||
h264: buildCodecs((c) => { | ||
// h264 only supports hevc video content | ||
if (c.audioCodec || !c.videoCodec) { | ||
return null; | ||
} | ||
|
||
if (!(/^avc1/).test(c.videoCodec)) { | ||
return null; | ||
} | ||
|
||
return c; | ||
}), | ||
h265: buildCodecs((c) => { | ||
// h265 only supports hevc video content | ||
if (c.audioCodec || !c.videoCodec) { | ||
return null; | ||
} | ||
|
||
if (!(/^hev1/).test(c.videoCodec)) { | ||
return null; | ||
} | ||
|
||
return c; | ||
}) | ||
}; | ||
|
||
let total = 0; | ||
|
||
const promises = Object.keys(containerCodecs).map((container) => { | ||
const codecs = containerCodecs[container]; | ||
const containerPath = path.join(baseDir, container); | ||
|
||
shelljs.mkdir('-p', containerPath); | ||
|
||
return Promise.all(codecs.map((codec) => new Promise((resolve, reject) => { | ||
const fileName = [codec.videoCodec, codec.audioCodec].filter(Boolean).join(',') + '.' + container; | ||
const filePath = path.join(containerPath, fileName); | ||
|
||
return resolve(ffmpeg([].concat(codec.args).concat([filePath])).then(function(result) { | ||
if (result.status !== 0) { | ||
console.log(result.cmd.join(' ')); | ||
console.log(`FAIL: ${fileName} ${result.out}`); | ||
return; | ||
} | ||
total++; | ||
})); | ||
}))); | ||
}); | ||
|
||
Promise.all(promises).then(function(args) { | ||
console.log(`Wrote ${total} files!`); | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this something important to have? Maybe we can just generate a one off file for it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not super important, but it would certainly help verify that we are doing parsing correctly, it would be good to get eventually.