Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@andrewstart/av-encoder

Package Overview
Dependencies
Maintainers
1
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@andrewstart/av-encoder - npm Package Compare versions

Comparing version 1.0.9 to 2.0.0

97

dist/encodeAudio.js
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });

@@ -6,7 +9,6 @@ const ffmpeg = require("ffmpeg-cli");

const fs = require("fs-extra");
const fast_glob_1 = __importDefault(require("fast-glob"));
const commander_1 = require("commander");
const JSON5 = require("json5");
const utils_1 = require("./utils");
const hasha = require("hasha");
const INPUT_TYPES = new Set(['.wav', '.aif', '.m4a', '.mp3', '.ogg', '.opus', '.flac']);
const CACHE_FILE = '.aveaudiocache';

@@ -16,3 +18,3 @@ // opus - Opus (Opus Interactive Audio Codec) (decoders: opus libopus ) (encoders: opus libopus )

async function main() {
var _a, _b;
var _a;
const program = new commander_1.Command();

@@ -38,33 +40,57 @@ program

}
const baseSrc = path.resolve(cwd, config.audio.baseSrc);
const baseDest = path.resolve(cwd, config.audio.baseDest);
const defaults = config.audio.default || { opusTargetBitrate: '32k', mp3Quality: '9' };
const cache = await utils_1.readCache(CACHE_FILE);
const defaults = config.audio.default || { opusTargetBitrate: '32k', mp3Quality: '9', mono: false };
const cache = new utils_1.Cache(config.audio.cache || CACHE_FILE);
await cache.load();
for (const group of config.audio.folders) {
const srcFolder = path.resolve(baseSrc, group.src);
if (!await fs.pathExists(srcFolder)) {
console.log(`** Source folder does not exist: ${srcFolder} **`);
continue;
}
const destFolder = path.resolve(baseDest, group.dest);
const destFolder = path.resolve(cwd, group.dest);
await fs.ensureDir(destFolder);
const files = await fs.readdir(srcFolder);
for (const file of files) {
// skip files we don't consider input (primarily to ignore .DS_Store files and other garbage)
if (!INPUT_TYPES.has(path.extname(file)))
continue;
const cacheId = path.join(group.src, file);
const fileSrc = path.resolve(srcFolder, file);
const hash = await hasha.fromFile(fileSrc, { algorithm: 'md5' });
let overwrite = false;
if (!cache.has(cacheId) || hash !== cache.get(cacheId).hash) {
overwrite = true;
const changed = await utils_1.filterChanged(await fast_glob_1.default(group.src, { cwd }), async (file) => {
var _a;
const id = path.basename(file, path.extname(file));
const override = (_a = group.overrides) === null || _a === void 0 ? void 0 : _a[id];
const currentSettings = Object.assign({}, defaults, group, override);
delete currentSettings.src;
delete currentSettings.dest;
delete currentSettings.overrides;
const oldSettings = cache.getSettings(id) || currentSettings;
const changed = { opus: false, mp3: false, caf: false };
if (currentSettings.mono != oldSettings.mono) {
changed.opus = changed.caf = changed.mp3 = true;
}
const override = (_a = group.overrides) === null || _a === void 0 ? void 0 : _a[file];
if (currentSettings.opusTargetBitrate != oldSettings.opusTargetBitrate) {
changed.opus = true;
changed.caf = true;
}
if (currentSettings.mp3Quality != oldSettings.mp3Quality) {
changed.mp3 = true;
}
if (await cache.isDifferent(file, cwd, currentSettings)) {
changed.opus = changed.caf = changed.mp3 = true;
}
else {
const targetBase = path.resolve(destFolder, id);
const targetOpus = targetBase + '.opus';
const targetCaf = targetBase + '.caf';
const targetMp3 = targetBase + '.mp3';
if (!await fs.pathExists(targetOpus)) {
changed.opus = true;
}
if (!await fs.pathExists(targetCaf)) {
changed.caf = true;
}
if (!await fs.pathExists(targetMp3)) {
changed.mp3 = true;
}
}
return Object.keys(changed).filter(k => changed[k]);
});
for (const file of changed) {
const id = path.basename(file.item, path.extname(file.item));
const fileSrc = file.item;
const override = (_a = group.overrides) === null || _a === void 0 ? void 0 : _a[id];
const settings = Object.assign({}, defaults, group, override);
delete settings.src;
delete settings.dest;
const lastSettings = (_b = cache.get(cacheId)) === null || _b === void 0 ? void 0 : _b.settings;
cache.set(cacheId, hash, settings);
const targetBase = path.resolve(destFolder, file.slice(0, -4));
delete settings.overrides;
const targetBase = path.resolve(destFolder, id);
const targetOpus = targetBase + '.opus';

@@ -75,11 +101,11 @@ const targetCaf = targetBase + '.caf';

let encodes = [];
if (overwrite || settings.opusTargetBitrate != lastSettings.opusTargetBitrate || !await fs.pathExists(targetOpus)) {
if (file.modes.includes('opus')) {
writes.push(`-c:a libopus -b:a ${settings.opusTargetBitrate} "${targetOpus}"`);
encodes.push('opus');
}
if (overwrite || settings.opusTargetBitrate != lastSettings.opusTargetBitrate || !await fs.pathExists(targetCaf)) {
if (file.modes.includes('caf')) {
writes.push(`-c:a libopus -b:a ${settings.opusTargetBitrate} "${targetCaf}"`);
encodes.push('caf');
}
if (overwrite || settings.mp3Quality != lastSettings.mp3Quality || !await fs.pathExists(targetMp3)) {
if (file.modes.includes('mp3')) {
writes.push(`-c:a libmp3lame -q:a ${settings.mp3Quality} "${targetMp3}"`);

@@ -89,8 +115,8 @@ encodes.push('mp3');

if (!writes.length) {
console.log(`${file} - skipped, already up to date`);
console.log(`${file.item} - skipped, already up to date`);
continue;
}
try {
const result = await ffmpeg.run(`-y -i "${fileSrc}" ${writes.join(' ')}`);
console.log(`${file} - encoded to ${encodes.join(',')}`);
const result = await ffmpeg.run(`-y -i "${fileSrc}" ${settings.mono ? '-ac 1' : ''} ${writes.join(' ')}`);
console.log(`${file.item} - encoded to ${encodes.join(',')}`);
if (result) {

@@ -105,4 +131,5 @@ console.log(result);

}
await utils_1.writeCache(cache, CACHE_FILE);
cache.purgeUnseen();
await cache.save();
}
main();
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });

@@ -6,10 +9,9 @@ const ffmpeg = require("ffmpeg-cli");

const fs = require("fs-extra");
const fast_glob_1 = __importDefault(require("fast-glob"));
const commander_1 = require("commander");
const JSON5 = require("json5");
const utils_1 = require("./utils");
const hasha = require("hasha");
const INPUT_TYPES = new Set(['.mov', '.mp4']);
const CACHE_FILE = '.avevideocache';
async function main() {
var _a, _b, _c, _d, _e, _f;
var _a, _b, _c, _d, _e;
const program = new commander_1.Command();

@@ -40,71 +42,81 @@ program

}
const baseSrc = path.resolve(cwd, config.video.baseSrc);
const baseDest = path.resolve(cwd, config.video.baseDest);
const defaults = config.video.default || { audioOut: null, quality: 28, width: 1280 };
const cache = await utils_1.readCache(CACHE_FILE);
const cache = new utils_1.Cache(config.video.cache || CACHE_FILE);
await cache.load();
for (const group of config.video.folders) {
const srcFolder = path.resolve(baseSrc, group.src);
const destFolder = path.resolve(baseDest, group.dest);
const destFolder = path.resolve(cwd, group.dest);
await fs.ensureDir(destFolder);
const files = await fs.readdir(srcFolder);
for (const file of files) {
// skip files we don't consider input (primarily to ignore .DS_Store files and other garbage)
if (!INPUT_TYPES.has(path.extname(file)))
continue;
const cacheId = path.join(group.src, file);
const fileSrc = path.resolve(srcFolder, file);
const hash = await hasha.fromFile(fileSrc, { algorithm: 'md5' });
let overwrite = false;
if (!cache.has(cacheId) || hash !== cache.get(cacheId).hash) {
overwrite = true;
const changed = await utils_1.filterChanged(await fast_glob_1.default(group.src, { cwd }), async (file) => {
var _a;
const id = path.basename(file, path.extname(file));
const override = (_a = group.overrides) === null || _a === void 0 ? void 0 : _a[id];
const currentSettings = Object.assign({}, defaults, group, override);
delete currentSettings.src;
delete currentSettings.dest;
delete currentSettings.overrides;
delete currentSettings.audioOut;
const oldSettings = cache.getSettings(id) || currentSettings;
let changed = false;
if (currentSettings.quality != oldSettings.quality || currentSettings.width != oldSettings.width) {
changed = true;
}
const override = (_a = group.overrides) === null || _a === void 0 ? void 0 : _a[file];
if (await cache.isDifferent(file, cwd, currentSettings)) {
changed = true;
}
else if (!changed) {
const targetBase = path.resolve(destFolder, id);
const target = targetBase + '.mp4';
const targetCaf = targetBase + '.caf';
const targetMp3 = targetBase + '.mp3';
if (!await fs.pathExists(target)) {
changed = true;
}
}
return changed ? [''] : null;
});
for (const file of changed) {
const id = path.basename(file.item, path.extname(file.item));
const fileSrc = file.item;
const override = (_a = group.overrides) === null || _a === void 0 ? void 0 : _a[id];
const settings = Object.assign({}, defaults, group, override);
delete settings.audioOut;
const audioOut = settings.audioOut;
delete settings.src;
delete settings.dest;
const lastSettings = (_b = cache.get(cacheId)) === null || _b === void 0 ? void 0 : _b.settings;
cache.set(cacheId, hash, settings);
const target = path.resolve(destFolder, file.slice(0, -4) + '.mp4');
const audioOut = (override === null || override === void 0 ? void 0 : override.audioOut) || group.audioOut || defaults.audioOut;
if (overwrite || settings.quality != lastSettings.quality || settings.width != lastSettings.width || !await fs.pathExists(target)) {
try {
// `-pix_fmt yuv420p` is for Quicktime compatibility (w/h must be divisible by 2)
// `-profile:v baseline -level 3.0` is for Android compatibility - doesn't support higher profiles
// `-movflags +faststart` allows play while downloading
// `scale=1280:-2` scales down the video to 1280 wide, height as a multiple of 2
const result = await ffmpeg.run(`-y -i "${fileSrc}" -c:v libx264 -pix_fmt yuv420p -profile:v baseline -level 3.0 -crf ${(_d = (_c = override === null || override === void 0 ? void 0 : override.quality) !== null && _c !== void 0 ? _c : group.quality) !== null && _d !== void 0 ? _d : defaults.quality} -preset veryslow -vf scale=${(_f = (_e = override === null || override === void 0 ? void 0 : override.width) !== null && _e !== void 0 ? _e : group.width) !== null && _f !== void 0 ? _f : defaults.width}:-2 ${audioOut ? '-an' : 'c:a aac'} -strict experimental -movflags +faststart -threads 0 "${target}"`);
console.log(`${file} - encoded to mp4`);
if (result) {
console.log(result);
}
delete settings.overrides;
delete settings.audioOut;
const target = path.resolve(destFolder, id + '.mp4');
try {
// `-pix_fmt yuv420p` is for Quicktime compatibility (w/h must be divisible by 2)
// `-profile:v baseline -level 3.0` is for Android compatibility - doesn't support higher profiles
// `-movflags +faststart` allows play while downloading
// `scale=1280:-2` scales down the video to 1280 wide, height as a multiple of 2
const result = await ffmpeg.run(`-y -i "${fileSrc}" -c:v libx264 -pix_fmt yuv420p -profile:v baseline -level 3.0 -crf ${(_c = (_b = override === null || override === void 0 ? void 0 : override.quality) !== null && _b !== void 0 ? _b : group.quality) !== null && _c !== void 0 ? _c : defaults.quality} -preset veryslow -vf scale=${(_e = (_d = override === null || override === void 0 ? void 0 : override.width) !== null && _d !== void 0 ? _d : group.width) !== null && _e !== void 0 ? _e : defaults.width}:-2 ${audioOut ? '-an' : 'c:a aac'} -strict experimental -movflags +faststart -threads 0 "${target}"`);
console.log(`${file} - encoded to mp4`);
if (result) {
console.log(result);
}
catch (e) {
console.log('Error:\n', e);
}
}
else {
console.log(`${file} - skipped, output exists`);
catch (e) {
console.log('Error:\n', e);
}
if (audioOut) {
const audioDest = path.resolve(baseSrc, audioOut);
const audioDest = path.resolve(cwd, audioOut);
await fs.ensureDir(audioDest);
const audioTarget = path.resolve(audioDest, file.slice(0, -4) + '.wav');
if (overwrite || !await fs.pathExists(audioTarget)) {
try {
const audioResult = await ffmpeg.run(`-y -i "${fileSrc}" -c:a pcm_f32le "${audioTarget}"`);
console.log(`${file} - encoded to wav`);
if (audioResult) {
console.log(audioResult);
}
const audioTarget = path.resolve(audioDest, id + '.wav');
try {
const audioResult = await ffmpeg.run(`-y -i "${fileSrc}" -c:a pcm_f32le "${audioTarget}"`);
console.log(`${file} - encoded to wav`);
if (audioResult) {
console.log(audioResult);
}
catch (e) {
console.log('Error:\n', e);
}
}
catch (e) {
console.log('Error:\n', e);
}
}
}
}
await utils_1.writeCache(cache, CACHE_FILE);
cache.purgeUnseen();
await cache.save();
}
main();
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Cache = exports.writeCache = exports.readCache = void 0;
exports.Cache = exports.filterChanged = void 0;
const path = require("path");
const fs = require("fs-extra");
async function readCache(file) {
file = path.resolve(process.cwd(), file);
if (!await fs.pathExists(file)) {
return new Cache('');
const hasha = require("hasha");
async function filterChanged(input, test) {
const out = [];
for (const item of input) {
const result = await test(item);
if (result && result.length) {
out.push({ item, modes: result });
}
}
const text = await fs.readFile(file, 'utf8');
return new Cache(text);
return out;
}
exports.readCache = readCache;
async function writeCache(cache, file) {
file = path.resolve(process.cwd(), file);
await fs.writeFile(file, cache.toString());
}
exports.writeCache = writeCache;
exports.filterChanged = filterChanged;
class Cache {
constructor(text) {
this.map = new Map();
const lines = text.split(/\r?\n/);
constructor(cachePath) {
this.hashes = new Map();
this.unseen = new Set();
this.cachePath = path.resolve(process.cwd(), cachePath);
}
getSettings(fileId) {
if (!this.hashes.has(fileId)) {
return null;
}
return this.hashes.get(fileId).settings;
}
/** Doesn't compare settings changes, just ingests them for saving. Only checks for src file changes. */
async isDifferent(filePath, rootDir, settings) {
const absPath = path.resolve(rootDir, filePath);
const hash = await hasha.fromFile(absPath, { algorithm: 'md5' });
const filename = path.basename(filePath, path.extname(filePath));
// if not present in cache, return true (add to hashes)
// if present, remove from unseen, compare hash with hasha, and update hashes if changed
let changed = true;
if (this.hashes.has(filename)) {
const data = this.hashes.get(filename);
if (data.hash == hash) {
changed = false;
}
}
if (changed) {
this.hashes.set(filename, { hash, settings });
}
this.unseen.delete(filename);
return changed;
}
async load() {
if (!(await fs.pathExists(this.cachePath))) {
return;
}
const file = await fs.readFile(this.cachePath, 'utf8');
const lines = file.split(/\r?\n/);
for (let line of lines) {
if (!line)
continue;
let file;
let fileId;
if (line[0] == '"') {
file = line.substring(1, line.indexOf('"', 1));
fileId = line.substring(1, line.indexOf('"', 1));
line = line.substring(line.indexOf('"', 1) + 2);
}
else {
file = line.substring(0, line.indexOf(' ', 1));
fileId = line.substring(0, line.indexOf(' ', 1));
line = line.substring(line.indexOf(' ') + 1);

@@ -39,22 +71,22 @@ }

const settings = JSON.parse(line);
this.map.set(file, { hash, settings });
this.hashes.set(fileId, { hash, settings });
this.unseen.add(fileId);
}
}
has(file) {
return this.map.has(file);
}
set(file, hash, settings) {
this.map.set(file, { hash, settings });
}
get(file) {
return this.map.get(file);
}
toString() {
async save() {
let text = '';
for (const [file, data] of this.map.entries()) {
text += `${file.includes(' ') ? `"${file}"` : file} ${data.hash} ${JSON.stringify(data.settings)}\n`;
for (const [fileId, data] of this.hashes.entries()) {
text += `${fileId.includes(' ') ? `"${fileId}"` : fileId} ${data.hash} ${JSON.stringify(data.settings)}\n`;
}
return text;
await fs.writeFile(this.cachePath, text);
}
purgeUnseen() {
const missing = Array.from(this.unseen.values());
for (const id of missing) {
this.hashes.delete(id);
}
this.unseen.clear();
return missing;
}
}
exports.Cache = Cache;
{
"name": "@andrewstart/av-encoder",
"version": "1.0.9",
"version": "2.0.0",
"description": "Encodes and compresses audio/video for widely supported web formats",

@@ -14,3 +14,4 @@ "bin": {

"build": "tsc",
"prepublishOnly": "npm run build"
"prepublishOnly": "npm run build",
"test": "node ./dist/encodeAudio.js -c test/test.json5"
},

@@ -33,2 +34,3 @@ "repository": {

"commander": "^8.1.0",
"fast-glob": "^3.2.11",
"ffmpeg-cli": "^2.7.5",

@@ -35,0 +37,0 @@ "fs-extra": "^10.0.0",

@@ -17,6 +17,4 @@ # av-encoder

audio: {
// root folder for all src paths
baseSrc: 'src/audio',
// root folder for all dest paths
baseDest: 'assets/audio',
// Optional, name a cache file to use instead of the default. This allows only changed files to be rerun in later runs.
cache: "path/to/.cachefile",
// default properties for audio encoding, if not specified

@@ -28,13 +26,15 @@ default: {

mp3Quality: '9',
// True to force downmixing to mono. False or omit to leave as-is
mono: true,
},
// list of source folders and destinations. Source folders are not recursive, and destinations can be
// list of source folders and destinations. Source folders are globs, and destinations can be
// shared.
folders: [
{
src: 'sfx',
dest: 'sfx',
src: 'src/sfx/*.wav',
dest: 'assets/sfx',
},
{
src: 'sfx/loops',
dest: 'sfx/loops',
src: ['src/sfx/loops/*.wav'],
dest: 'assets/sfx/loops',
// each folder can have specific encoding properties

@@ -45,7 +45,7 @@ opusTargetBitrate: '48k',

{
src: 'vo',
dest: 'vo',
src: 'src/vo/**/*.wav',
dest: 'assets/vo',
// you can also override settings for individual files if so desired
overrides: {
'intro.wav': {
'intro': {
opusTargetBitrate: '48k',

@@ -60,4 +60,4 @@ mp3Quality: '7'

video: {
baseSrc: 'src/video',
baseDest: 'assets/video',
// Optional, name a cache file to use instead of the default. This allows only changed files to be rerun in later runs.
cache: "path/to/.cachefile",
default: {

@@ -80,2 +80,2 @@ // MP4 quality: 0 is lossless, 23 is default, and 51 is worst possible. 18-28 is a sane range.

### Project cache files
Files for audio & video caches to track which files need to be encoded and which don't will be created in your project. `.aveaudiocache` and `.avevidiocache` will be created when encoding audio and video, respectively. If your output files are tracked with version control, then these cache files should be tracked as well.
Files for audio & video caches to track which files need to be encoded and which don't will be created in your project. `.aveaudiocache` and `.avevidiocache` (or your project values) will be created when encoding audio and video, respectively. If your output files are tracked with version control, then these cache files should be tracked as well.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc