Skip to content

Commit 9b23b18

Browse files
committed
feat: map all missing LAME options in library
1 parent 096280c commit 9b23b18

File tree

9 files changed

+2370
-304
lines changed

9 files changed

+2370
-304
lines changed

README.md

Lines changed: 95 additions & 4 deletions
Large diffs are not rendered by default.

src/core/lame-options.ts

Lines changed: 603 additions & 65 deletions
Large diffs are not rendered by default.

src/core/lame.ts

Lines changed: 100 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,50 @@ import { LameOptions } from "./lame-options";
1212

1313
type ProgressKind = "encode" | "decode";
1414

15+
function parseEncodeProgressLine(content: string): {
16+
progress?: number;
17+
eta?: string;
18+
} | null {
19+
const progressMatch = content.match(/\((( [0-9])|([0-9]{2})|(100))%\)\|/);
20+
if (!progressMatch) {
21+
return null;
22+
}
23+
24+
const etaMatch = content.match(/[0-9]{1,2}:[0-9][0-9] /);
25+
26+
/* c8 ignore next */
27+
const progress = Number(progressMatch[1]);
28+
const eta = etaMatch ? etaMatch[0].trim() : undefined;
29+
30+
return { progress, eta };
31+
}
32+
33+
function parseDecodeProgressLine(content: string): number | null {
34+
const progressMatch = content.match(/[0-9]{1,10}\/[0-9]{1,10}/);
35+
if (!progressMatch) {
36+
return null;
37+
}
38+
39+
const [current, total] = progressMatch[0].split("/").map(Number);
40+
if (!Number.isFinite(current) || !Number.isFinite(total) || total === 0) {
41+
return NaN;
42+
}
43+
44+
return Math.floor((current / total) * 100);
45+
}
46+
47+
function normalizeCliMessage(content: string): string | null {
48+
if (
49+
content.startsWith("lame: ") ||
50+
content.startsWith("Warning: ") ||
51+
content.includes("Error ")
52+
) {
53+
return content.startsWith("lame: ") ? content : `lame: ${content}`;
54+
}
55+
56+
return null;
57+
}
58+
1559
/**
1660
* Thin wrapper around the LAME CLI that manages temp files, progress events,
1761
* and output handling while delegating the heavy lifting to the binary.
@@ -28,7 +72,8 @@ class Lame {
2872
new EventEmitter() as LameProgressEmitter;
2973

3074
private readonly options: LameOptionsBag;
31-
private readonly args: Array<string | number>;
75+
private readonly builder: LameOptions;
76+
private readonly args: string[];
3277

3378
private filePath?: string;
3479
private fileBuffer?: Buffer;
@@ -43,7 +88,8 @@ class Lame {
4388

4489
constructor(options: LameOptionsBag) {
4590
this.options = options;
46-
this.args = new LameOptions(this.options).getArguments();
91+
this.builder = new LameOptions(this.options);
92+
this.args = this.builder.getArguments();
4793
this.lamePath = resolveLameBinary();
4894
this.tempPath = join(tmpdir(), "node-lame");
4995
}
@@ -155,16 +201,23 @@ class Lame {
155201
*/
156202
private async spawnLameAndTrackProgress(
157203
inputFilePath: string,
158-
baseArgs: Array<string | number>,
204+
baseArgs: string[],
159205
type: ProgressKind,
160206
): Promise<this> {
161-
const args = [...baseArgs, "--disptime", "1"].map((value) =>
162-
String(value),
163-
);
207+
const args = [...baseArgs];
208+
209+
if (
210+
this.builder.shouldUseDefaultDisptime() &&
211+
!args.includes("--disptime")
212+
) {
213+
args.push("--disptime", "1");
214+
}
215+
216+
const normalizedArgs = args.map((value) => String(value));
164217

165218
const { outputPath, bufferOutput } =
166219
await this.prepareOutputTarget(type);
167-
const spawnArgs = [inputFilePath, outputPath, ...args];
220+
const spawnArgs = [inputFilePath, outputPath, ...normalizedArgs];
168221

169222
this.status = {
170223
started: true,
@@ -194,68 +247,46 @@ class Lame {
194247
this.status.progress,
195248
this.status.eta,
196249
]);
197-
} else if (
198-
type === "encode" &&
199-
/\((( [0-9])|([0-9]{2})|(100))%\)\|/.test(content)
200-
) {
201-
const progressMatch = content.match(
202-
/\((( [0-9])|([0-9]{2})|(100))%\)\|/,
203-
);
204-
const etaMatch = content.match(/[0-9]{1,2}:[0-9][0-9] /);
205-
206-
const progress =
207-
progressMatch && progressMatch[1]
208-
? Number(progressMatch[1])
209-
: undefined;
210-
const eta = etaMatch ? etaMatch[0].trim() : undefined;
211-
212-
if (
213-
progress !== undefined &&
214-
progress > this.status.progress
215-
) {
216-
this.status.progress = progress;
217-
}
218-
219-
if (eta) {
220-
this.status.eta = eta;
221-
}
250+
} else if (type === "encode") {
251+
const parsed = parseEncodeProgressLine(content);
252+
if (parsed) {
253+
if (
254+
parsed.progress !== undefined &&
255+
parsed.progress > this.status.progress
256+
) {
257+
this.status.progress = parsed.progress;
258+
}
222259

223-
this.emitter.emit("progress", [
224-
this.status.progress,
225-
this.status.eta,
226-
]);
227-
} else if (
228-
type === "decode" &&
229-
/[0-9]{1,10}\/[0-9]{1,10}/.test(content)
230-
) {
231-
const progressMatch = content.match(
232-
/[0-9]{1,10}\/[0-9]{1,10}/,
233-
);
260+
if (parsed.eta) {
261+
this.status.eta = parsed.eta;
262+
}
234263

235-
if (progressMatch) {
236-
const [current, total] = progressMatch[0]
237-
.split("/")
238-
.map(Number);
239-
const progress = Math.floor((current / total) * 100);
264+
this.emitter.emit("progress", [
265+
this.status.progress,
266+
this.status.eta,
267+
]);
268+
return;
269+
}
270+
}
240271

241-
if (!Number.isNaN(progress)) {
242-
this.status.progress = progress;
272+
if (type === "decode") {
273+
const parsed = parseDecodeProgressLine(content);
274+
if (parsed !== null) {
275+
if (!Number.isNaN(parsed)) {
276+
this.status.progress = parsed;
243277
}
244278

245279
this.emitter.emit("progress", [
246280
this.status.progress,
247281
this.status.eta,
248282
]);
283+
return;
249284
}
250-
} else if (
251-
content.startsWith("lame: ") ||
252-
content.startsWith("Warning: ") ||
253-
content.includes("Error ")
254-
) {
255-
const message = content.startsWith("lame: ")
256-
? content
257-
: `lame: ${content}`;
258-
this.emitter.emit("error", new Error(message));
285+
}
286+
287+
const normalized = normalizeCliMessage(content);
288+
if (normalized) {
289+
this.emitter.emit("error", new Error(normalized));
259290
}
260291
};
261292

@@ -366,9 +397,11 @@ class Lame {
366397

367398
private async ensureOutputDirectoryExists(filePath: string) {
368399
const dir = dirname(filePath);
369-
if (dir) {
370-
await mkdir(dir, { recursive: true });
400+
if (!dir || dir === ".") {
401+
return;
371402
}
403+
404+
await mkdir(dir, { recursive: true });
372405
}
373406

374407
private async generateTempFilePath(
@@ -398,4 +431,9 @@ class Lame {
398431
}
399432
}
400433

401-
export { Lame };
434+
export {
435+
Lame,
436+
normalizeCliMessage,
437+
parseDecodeProgressLine,
438+
parseEncodeProgressLine,
439+
};

src/internal/binary/resolve-binary.ts

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,67 @@
11
import { existsSync } from "node:fs";
22
import { join } from "node:path";
3+
import type { Platform } from "node:process";
34
import { fileURLToPath, pathToFileURL } from "node:url";
45

56
declare const __dirname: string | undefined;
67
declare const __filename: string | undefined;
78

9+
type ImportMetaLike = { url?: string } | undefined;
10+
11+
function deriveModuleUrl(
12+
meta: ImportMetaLike,
13+
filename: string | undefined,
14+
): string | undefined {
15+
if (meta && typeof meta.url === "string") {
16+
return meta.url;
17+
}
18+
19+
if (typeof filename === "string") {
20+
return pathToFileURL(filename).href;
21+
}
22+
23+
return undefined;
24+
}
25+
826
const moduleUrl = (() => {
27+
let meta: ImportMetaLike = undefined;
28+
929
try {
10-
const meta = import.meta as { url?: string };
11-
if (meta && typeof meta.url === "string") {
12-
return meta.url;
13-
}
30+
meta = import.meta as ImportMetaLike;
1431
} catch {
15-
// Swallow ReferenceError when import.meta is unavailable (CommonJS build).
32+
// ignore when import.meta is unavailable (CommonJS build)
1633
}
1734

18-
if (typeof __filename === "string") {
19-
return pathToFileURL(__filename).href;
20-
}
35+
const resolvedFilename = __filename;
2136

22-
return undefined;
37+
return deriveModuleUrl(meta, resolvedFilename);
2338
})();
2439

25-
const PACKAGE_ROOT =
26-
typeof __dirname === "string"
27-
? join(__dirname, "..", "..", "..")
28-
: moduleUrl != null
29-
? fileURLToPath(new URL("../../..", moduleUrl))
30-
: process.cwd();
40+
function resolvePackageRoot(
41+
moduleHref: string | undefined,
42+
dirname: string | undefined,
43+
): string {
44+
if (dirname) {
45+
return join(dirname, "..", "..", "..");
46+
}
47+
48+
if (moduleHref != null) {
49+
return fileURLToPath(new URL("../../..", moduleHref));
50+
}
51+
52+
return process.cwd();
53+
}
54+
55+
const PACKAGE_ROOT = resolvePackageRoot(moduleUrl, __dirname);
3156
const CUSTOM_BINARY_ENV = "LAME_BINARY";
3257

33-
const PLATFORM_EXECUTABLE_SUFFIX = process.platform === "win32" ? ".exe" : "";
58+
function getPlatformExecutableSuffix(platform: Platform): string {
59+
return platform === "win32" ? ".exe" : "";
60+
}
61+
62+
const PLATFORM_EXECUTABLE_SUFFIX = getPlatformExecutableSuffix(
63+
process.platform,
64+
);
3465

3566
/**
3667
* Attempt to resolve the absolute path to a bundled LAME binary.
@@ -68,4 +99,10 @@ function resolveLameBinary(): string {
6899
return resolved ?? "lame";
69100
}
70101

71-
export { resolveBundledLameBinary, resolveLameBinary };
102+
export {
103+
deriveModuleUrl,
104+
getPlatformExecutableSuffix,
105+
resolveBundledLameBinary,
106+
resolveLameBinary,
107+
resolvePackageRoot,
108+
};

0 commit comments

Comments
 (0)