Skip to content

Commit

Permalink
feat: encode lib
Browse files Browse the repository at this point in the history
  • Loading branch information
aidenlx committed May 15, 2024
1 parent 40f56ec commit 8a7af67
Show file tree
Hide file tree
Showing 13 changed files with 171 additions and 9 deletions.
37 changes: 37 additions & 0 deletions apps/app/src/lib/encode/ffmpeg-bin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Platform } from "obsidian";

const version = "7.0";
const pkg = "@aidenlx/ffmpeg-static@0.1.0";

export const getBinaryName = () => {
if (!Platform.isDesktopApp) return null;
return (
`ffmpeg-${version}-${process.platform}-${process.arch}` +
(process.platform === "win32" ? ".exe" : "")
);
};

const toBinaryURL = (binaryName: string) =>
`https://www.unpkg.com/${pkg}/bin/${binaryName}.gz`;

export async function isPlatformSupported(): Promise<boolean> {
const binaryName = getBinaryName();
if (!binaryName) return false;
const url = toBinaryURL(binaryName);
const resp = await fetch(url, { method: "HEAD" });
if (resp.status === 404) return false;
if (!resp.ok) throw new Error(`Failed to fetch ${url}: ${resp.statusText}`);
return true;
}

export async function downloadBinary(): Promise<ArrayBuffer> {
const binaryName = getBinaryName();
if (!binaryName) throw new Error("Not desktop app");

const url = toBinaryURL(binaryName);
const resp = await fetch(url);
if (!resp.ok) throw new Error(`Failed to fetch ${url}: ${resp.statusText}`);
const stream = resp.body?.pipeThrough(new DecompressionStream("gzip"));
if (!stream) throw new Error("No stream");
return await new Response(stream).arrayBuffer();
}
92 changes: 92 additions & 0 deletions apps/app/src/lib/encode/ffmpeg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { getSpawn } from "@/lib/require";

// Dedicated function to convert Node.js Buffer to ArrayBuffer
function bufferToArrayBuffer(buffer: Buffer): ArrayBuffer {
return buffer.buffer.slice(
buffer.byteOffset,
buffer.byteOffset + buffer.byteLength,
);
}

export class FFmpegError extends Error {
constructor(message: string, public stderr: string[]) {
super(message);
}
}

export function ffmpeg(opts: {
args: string[];
binary: string;
pipe: true;
}): Promise<ArrayBuffer>;
export function ffmpeg(opts: {
args: string[];
binary: string;
pipe?: false;
}): Promise<void>;
export function ffmpeg({
args,
binary: ffmpeg,
pipe = false,
}: {
args: string[];
binary: string;
pipe?: boolean;
}): Promise<ArrayBuffer | void> {
const spawn = getSpawn();
if (!spawn) throw new Error("Not desktop app");
return new Promise<ArrayBuffer | void>((resolve, reject) => {
console.debug(`Running FFmpeg with args: ${args.join(" ")}`);
if (!pipe) console.debug("stdout to /dev/null");
const stdout: Buffer[] = [];
function handleStdErr(process: { stderr: NodeJS.ReadableStream }) {
const stderr: string[] = [];
process.stderr.on("data", (data) => {
const message = data.toString("utf-8");
console.debug(`> FFMPEG: ${message}`);
stderr.push(message);
});
return stderr;
}
if (pipe) {
const process = spawn(ffmpeg, args, {
// Ignore stdin, pipe stdout and stderr
stdio: ["ignore", "pipe", "pipe"],
});
const stderr = handleStdErr(process);
process.stdout.on("data", (data) => stdout.push(data));
process.on("error", (err) =>
reject(
new FFmpegError(`FFmpeg error (${err.name}): ${err.message}`, stderr),
),
);
process.on("close", (code) => {
if (code === 0) {
const output = Buffer.concat(stdout);
resolve(bufferToArrayBuffer(output));
} else {
reject(new FFmpegError(`FFmpeg error (code: ${code})`, stderr));
}
});
} else {
const process = spawn(ffmpeg, args, {
stdio: ["ignore", "ignore", "pipe"],
});
process.stderr.on("data", (data: Buffer) =>
console.debug(data.toString("utf-8")),
);
process.on("error", (err) => reject(new Error(`FFmpeg error: ${err}`)));
process.on("close", (code) => {
if (code === 0) {
resolve();
} else {
reject(
new Error(
`FFmpeg exited with code ${code}, see debug logs for output`,
),
);
}
});
}
});
}
28 changes: 28 additions & 0 deletions apps/app/src/lib/encode/to-whisper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ffmpeg } from "./ffmpeg";

// Function to convert MP4 to MP3 using FFmpeg
export async function prepareWhisper(
path: string,
{ binary }: { binary: string },
): Promise<Blob> {
const mp3Data = await ffmpeg({
args: [
/* eslint-disable prettier/prettier */
"-i", path,
"-vn",
"-map_metadata", "-1",
// opus, bitrate: 16 kbps, audio rate: 12 kHz, mono audio
"-c:a", "libopus",
"-f", "opus",
"-b:a", "16k",
"-ar", "12000",
"-ac", "1",
"-af", "speechnorm",
"pipe:1",
/* eslint-enable prettier/prettier */
],
binary,
pipe: true,
});
return new Blob([mp3Data], { type: "audio/opus" });
}
2 changes: 1 addition & 1 deletion apps/app/src/lib/iter-sibling.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path from "@/lib/path";
import { getFsPromise } from "@/web/session/utils";
import { getFsPromise } from "@/lib/require";
import type { FileInfo } from "./file-info";
import { toFileInfo } from "./file-info";

Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/lib/picker.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import mime from "mime";
import { Platform } from "obsidian";
import { getMediaExts } from "@/info/media-type";
import { getDialog } from "@/web/session/utils";
import { getDialog } from "@/lib/require";

/**
* @returns absolute path of the picked file, or null if canceled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export function getFsPromise() {
return (fs ??= require("fs/promises")) as typeof import("node:fs/promises");
}

export function getSpawn() {
if (!Platform.isDesktopApp) return null;
return require("child_process").spawn as typeof import("child_process").spawn;
}

export async function readFile(filePath: string) {
const fs = getFsPromise();
if (!fs) throw new Error("fs not available");
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/login/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import type { SupportedMediaHost } from "@/info/supported";
import { showAtButton } from "@/lib/menu";
import { getPartition } from "@/lib/remote-player/const";
import { getSession, getWebContents } from "@/web/session/utils";
import { getSession, getWebContents } from "@/lib/require";

export class LoginModal extends Modal {
navEl = this.contentEl.insertAdjacentElement(
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/settings/tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import {
} from "obsidian";
import { getGroupedLangExtra } from "@/lib/lang/lang";
import { showAtButton } from "@/lib/menu";
import { getDialog } from "@/lib/require";
import { LoginModal } from "@/login/modal";
import type MxPlugin from "@/mx-main";
import "./style.global.less";
import type { BilibiliQuality } from "@/web/session/bilibili";
import { bilibiliQualityLabels } from "@/web/session/bilibili";
import { getDialog } from "@/web/session/utils";
import type { MxSettings, OpenLinkBehavior } from "./def";

export class MxSettingTabs extends PluginSettingTab {
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/switcher/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { checkMediaType } from "@/info/media-type";
import { MediaURL } from "@/info/media-url";
import path from "@/lib/path";
import { pickMediaFile } from "@/lib/picker";
import { getFsPromise } from "@/lib/require";
import { toURL } from "@/lib/url";
import type MxPlugin from "@/mx-main";
import { getFsPromise } from "@/web/session/utils";
import { FileProtocolModal } from "./protocol-select";

const avId = /^av(?<id>\d+)$/i;
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/transcript/handle/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import type {
} from "@/info/track-info";
import { isSupportedCaptionExt } from "@/info/track-info";
import type { FileInfo } from "@/lib/file-info";
import { readFile } from "@/lib/require";
import type MxPlugin from "@/mx-main";
import { readFile } from "@/web/session/utils";
import { parseTrack } from "../stringify";
import {
resolveInvaultMediaForTrack,
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/web/bili-req/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import preloadLoader from "inline:./scripts/preload-patch";
import userScript from "inline:./scripts/userscript";
import { Component, Platform, WorkspaceWindow } from "obsidian";
import path from "@/lib/path";
import { evalInMainPs, getFsPromise, getUserDataPath } from "@/lib/require";
import type MxPlugin from "@/mx-main";
import { evalInMainPs, getFsPromise, getUserDataPath } from "../session/utils";
import { buildPreloadLoader, channelId } from "./channel";
import { BILI_REQ_STORE, replaceEnv } from "./const";

Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/web/session/bilibili.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { RequestUrlParam, RequestUrlResponse } from "obsidian";
import { Platform, requestUrl } from "obsidian";
import { getUserAgent } from "@/lib/remote-player/ua";
import { getSession } from "./utils";
import { getSession } from "@/lib/require";

export const enum BilibiliQuality {
/** 240P 极速
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/web/session/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Platform } from "obsidian";
import { getSession } from "@/lib/require";
import type MxPlugin from "@/mx-main";
import { modifyBilibiliSession } from "./bilibili";
import { getSession } from "./utils";

export async function modifySession(this: MxPlugin) {
if (!Platform.isDesktopApp) return;
Expand Down

0 comments on commit 8a7af67

Please sign in to comment.