@m5kdev/backend 0.9.3 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/modules/auth/auth.lib.d.cts +2 -2
- package/dist/src/modules/auth/auth.lib.d.mts +2 -2
- package/dist/src/modules/billing/billing.repository.d.cts +11 -11
- package/dist/src/modules/billing/billing.service.d.cts +7 -7
- package/dist/src/modules/video/video.service.cjs +18 -3
- package/dist/src/modules/video/video.service.cjs.map +1 -1
- package/dist/src/modules/video/video.service.mjs +18 -3
- package/dist/src/modules/video/video.service.mjs.map +1 -1
- package/package.json +3 -3
|
@@ -3279,13 +3279,13 @@ declare function createBetterAuth<O extends Orm, S extends Schema, E extends Ema
|
|
|
3279
3279
|
$Infer: {
|
|
3280
3280
|
body: ({
|
|
3281
3281
|
permission: {
|
|
3282
|
-
readonly user?: ("
|
|
3282
|
+
readonly user?: ("update" | "set-role" | "create" | "delete" | "list" | "get" | "ban" | "impersonate" | "set-password")[] | undefined;
|
|
3283
3283
|
readonly session?: ("delete" | "list" | "revoke")[] | undefined;
|
|
3284
3284
|
};
|
|
3285
3285
|
permissions?: never | undefined;
|
|
3286
3286
|
} | {
|
|
3287
3287
|
permissions: {
|
|
3288
|
-
readonly user?: ("
|
|
3288
|
+
readonly user?: ("update" | "set-role" | "create" | "delete" | "list" | "get" | "ban" | "impersonate" | "set-password")[] | undefined;
|
|
3289
3289
|
readonly session?: ("delete" | "list" | "revoke")[] | undefined;
|
|
3290
3290
|
};
|
|
3291
3291
|
permission?: never | undefined;
|
|
@@ -3279,13 +3279,13 @@ declare function createBetterAuth<O extends Orm, S extends Schema, E extends Ema
|
|
|
3279
3279
|
$Infer: {
|
|
3280
3280
|
body: ({
|
|
3281
3281
|
permission: {
|
|
3282
|
-
readonly user?: ("
|
|
3282
|
+
readonly user?: ("update" | "set-role" | "create" | "delete" | "list" | "get" | "ban" | "impersonate" | "set-password")[] | undefined;
|
|
3283
3283
|
readonly session?: ("delete" | "list" | "revoke")[] | undefined;
|
|
3284
3284
|
};
|
|
3285
3285
|
permissions?: never | undefined;
|
|
3286
3286
|
} | {
|
|
3287
3287
|
permissions: {
|
|
3288
|
-
readonly user?: ("
|
|
3288
|
+
readonly user?: ("update" | "set-role" | "create" | "delete" | "list" | "get" | "ban" | "impersonate" | "set-password")[] | undefined;
|
|
3289
3289
|
readonly session?: ("delete" | "list" | "revoke")[] | undefined;
|
|
3290
3290
|
};
|
|
3291
3291
|
permission?: never | undefined;
|
|
@@ -4,7 +4,7 @@ import * as _$drizzle_orm_sqlite_core0 from "drizzle-orm/sqlite-core";
|
|
|
4
4
|
import { LibSQLDatabase } from "drizzle-orm/libsql";
|
|
5
5
|
import { InferSelectModel } from "drizzle-orm";
|
|
6
6
|
import { BillingSchema } from "@m5kdev/commons/modules/billing/billing.schema";
|
|
7
|
-
import { Stripe } from "stripe";
|
|
7
|
+
import { Stripe as Stripe$1 } from "stripe";
|
|
8
8
|
import { StripePlan } from "@m5kdev/commons/modules/billing/billing.types";
|
|
9
9
|
|
|
10
10
|
//#region src/modules/billing/billing.repository.d.ts
|
|
@@ -2713,7 +2713,7 @@ declare const schema: {
|
|
|
2713
2713
|
type Schema = typeof schema;
|
|
2714
2714
|
type Orm = LibSQLDatabase<Schema>;
|
|
2715
2715
|
declare class BillingRepository extends BaseTableRepository<Orm, Schema, Record<string, never>, Schema["subscriptions"]> {
|
|
2716
|
-
stripe: Stripe;
|
|
2716
|
+
stripe: Stripe$1;
|
|
2717
2717
|
plans: StripePlan[];
|
|
2718
2718
|
trial?: StripePlan;
|
|
2719
2719
|
constructor(options: {
|
|
@@ -2721,7 +2721,7 @@ declare class BillingRepository extends BaseTableRepository<Orm, Schema, Record<
|
|
|
2721
2721
|
schema: Schema;
|
|
2722
2722
|
table: Schema["subscriptions"];
|
|
2723
2723
|
libs: {
|
|
2724
|
-
stripe: Stripe;
|
|
2724
|
+
stripe: Stripe$1;
|
|
2725
2725
|
};
|
|
2726
2726
|
config: {
|
|
2727
2727
|
trial?: StripePlan;
|
|
@@ -2730,7 +2730,7 @@ declare class BillingRepository extends BaseTableRepository<Orm, Schema, Record<
|
|
|
2730
2730
|
});
|
|
2731
2731
|
hasTrial(): boolean;
|
|
2732
2732
|
getPlanByPriceId(priceId: string): StripePlan | undefined;
|
|
2733
|
-
getCustomerByEmail(email: string): ServerResultAsync<Stripe.Customer | null>;
|
|
2733
|
+
getCustomerByEmail(email: string): ServerResultAsync<Stripe$1.Customer | null>;
|
|
2734
2734
|
getUserByCustomerId(customerId: string): ServerResultAsync<InferSelectModel<Schema["users"]> | null>;
|
|
2735
2735
|
createCustomer({
|
|
2736
2736
|
email,
|
|
@@ -2740,8 +2740,8 @@ declare class BillingRepository extends BaseTableRepository<Orm, Schema, Record<
|
|
|
2740
2740
|
email: string;
|
|
2741
2741
|
name?: string;
|
|
2742
2742
|
userId: string;
|
|
2743
|
-
}): ServerResultAsync<Stripe.Customer>;
|
|
2744
|
-
createTrialSubscription(customerId: string): ServerResultAsync<Stripe.Subscription>;
|
|
2743
|
+
}): ServerResultAsync<Stripe$1.Customer>;
|
|
2744
|
+
createTrialSubscription(customerId: string): ServerResultAsync<Stripe$1.Subscription>;
|
|
2745
2745
|
createSubscription({
|
|
2746
2746
|
customerId,
|
|
2747
2747
|
priceId,
|
|
@@ -2752,7 +2752,7 @@ declare class BillingRepository extends BaseTableRepository<Orm, Schema, Record<
|
|
|
2752
2752
|
priceId: string;
|
|
2753
2753
|
quantity?: number;
|
|
2754
2754
|
trialDays?: number;
|
|
2755
|
-
}): ServerResultAsync<Stripe.Subscription>;
|
|
2755
|
+
}): ServerResultAsync<Stripe$1.Subscription>;
|
|
2756
2756
|
updateUserCustomerId({
|
|
2757
2757
|
userId,
|
|
2758
2758
|
customerId
|
|
@@ -2762,7 +2762,7 @@ declare class BillingRepository extends BaseTableRepository<Orm, Schema, Record<
|
|
|
2762
2762
|
}): ServerResultAsync<InferSelectModel<Schema["users"]>>;
|
|
2763
2763
|
getLatestSubscription(referenceId: string): ServerResultAsync<BillingSchema | null>;
|
|
2764
2764
|
getActiveSubscription(referenceId: string): ServerResultAsync<BillingSchema | null>;
|
|
2765
|
-
listInvoices(customerId: string): ServerResultAsync<Stripe.Invoice[]>;
|
|
2765
|
+
listInvoices(customerId: string): ServerResultAsync<Stripe$1.Invoice[]>;
|
|
2766
2766
|
createCheckoutSession({
|
|
2767
2767
|
customerId,
|
|
2768
2768
|
priceId,
|
|
@@ -2771,8 +2771,8 @@ declare class BillingRepository extends BaseTableRepository<Orm, Schema, Record<
|
|
|
2771
2771
|
customerId: string;
|
|
2772
2772
|
priceId: string;
|
|
2773
2773
|
userId: string;
|
|
2774
|
-
}): ServerResultAsync<Stripe.Checkout.Session>;
|
|
2775
|
-
createBillingPortalSession(customerId: string): ServerResultAsync<Stripe.BillingPortal.Session>;
|
|
2774
|
+
}): ServerResultAsync<Stripe$1.Checkout.Session>;
|
|
2775
|
+
createBillingPortalSession(customerId: string): ServerResultAsync<Stripe$1.BillingPortal.Session>;
|
|
2776
2776
|
syncStripeData({
|
|
2777
2777
|
customerId,
|
|
2778
2778
|
userId
|
|
@@ -2780,7 +2780,7 @@ declare class BillingRepository extends BaseTableRepository<Orm, Schema, Record<
|
|
|
2780
2780
|
customerId: string;
|
|
2781
2781
|
userId: string;
|
|
2782
2782
|
}): ServerResultAsync<boolean>;
|
|
2783
|
-
constructEvent(body: Buffer | string, signature: string, secret: string): ServerResult<Stripe.Event>;
|
|
2783
|
+
constructEvent(body: Buffer | string, signature: string, secret: string): ServerResult<Stripe$1.Event>;
|
|
2784
2784
|
}
|
|
2785
2785
|
//#endregion
|
|
2786
2786
|
export { BillingRepository };
|
|
@@ -4,7 +4,7 @@ import { BillingRepository } from "./billing.repository.cjs";
|
|
|
4
4
|
import { User } from "../auth/auth.lib.cjs";
|
|
5
5
|
import { BaseService } from "../base/base.service.cjs";
|
|
6
6
|
import { BillingSchema } from "@m5kdev/commons/modules/billing/billing.schema";
|
|
7
|
-
import Stripe
|
|
7
|
+
import Stripe from "stripe";
|
|
8
8
|
|
|
9
9
|
//#region src/modules/billing/billing.service.d.ts
|
|
10
10
|
declare class BillingService extends BaseService<{
|
|
@@ -18,7 +18,7 @@ declare class BillingService extends BaseService<{
|
|
|
18
18
|
email: string;
|
|
19
19
|
name?: string;
|
|
20
20
|
};
|
|
21
|
-
}): ServerResultAsync<Stripe
|
|
21
|
+
}): ServerResultAsync<Stripe.Customer>;
|
|
22
22
|
createUserHook({
|
|
23
23
|
user
|
|
24
24
|
}: {
|
|
@@ -29,7 +29,7 @@ declare class BillingService extends BaseService<{
|
|
|
29
29
|
};
|
|
30
30
|
}): ServerResultAsync<boolean>;
|
|
31
31
|
getActiveSubscription(ctx: Context): ServerResultAsync<BillingSchema | null>;
|
|
32
|
-
listInvoices(ctx: Context): ServerResultAsync<Stripe
|
|
32
|
+
listInvoices(ctx: Context): ServerResultAsync<Stripe.Invoice[]>;
|
|
33
33
|
createCheckoutSession({
|
|
34
34
|
priceId
|
|
35
35
|
}: {
|
|
@@ -38,15 +38,15 @@ declare class BillingService extends BaseService<{
|
|
|
38
38
|
user
|
|
39
39
|
}: {
|
|
40
40
|
user: User;
|
|
41
|
-
}): ServerResultAsync<Stripe
|
|
41
|
+
}): ServerResultAsync<Stripe.Checkout.Session>;
|
|
42
42
|
createBillingPortalSession({
|
|
43
43
|
user
|
|
44
44
|
}: {
|
|
45
45
|
user: User;
|
|
46
|
-
}): ServerResultAsync<Stripe
|
|
47
|
-
constructEvent(body: Buffer | string, signature: string): ServerResult<Stripe
|
|
46
|
+
}): ServerResultAsync<Stripe.BillingPortal.Session>;
|
|
47
|
+
constructEvent(body: Buffer | string, signature: string): ServerResult<Stripe.Event>;
|
|
48
48
|
syncStripeData(customerId: string, eventType?: string): ServerResultAsync<boolean>;
|
|
49
|
-
processEvent(event: Stripe
|
|
49
|
+
processEvent(event: Stripe.Event): ServerResultAsync<boolean>;
|
|
50
50
|
}
|
|
51
51
|
//#endregion
|
|
52
52
|
export { BillingService };
|
|
@@ -10,12 +10,19 @@ let node_child_process = require("node:child_process");
|
|
|
10
10
|
let ffmpeg_ffprobe_static = require("ffmpeg-ffprobe-static");
|
|
11
11
|
ffmpeg_ffprobe_static = require_runtime.__toESM(ffmpeg_ffprobe_static);
|
|
12
12
|
//#region src/modules/video/video.service.ts
|
|
13
|
-
if (!ffmpeg_ffprobe_static.default.ffmpegPath || !ffmpeg_ffprobe_static.default.ffprobePath) throw new Error("FFmpeg or FFprobe not found");
|
|
14
13
|
const uploadsDir = node_path.default.join(__dirname, "..", "uploads");
|
|
15
14
|
if (!(0, node_fs.existsSync)(uploadsDir)) (0, node_fs.mkdirSync)(uploadsDir, { recursive: true });
|
|
15
|
+
const resolveFfmpegPath = () => {
|
|
16
|
+
const envPath = process.env.FFMPEG_PATH;
|
|
17
|
+
if (envPath && (0, node_fs.existsSync)(envPath)) return envPath;
|
|
18
|
+
const staticPath = ffmpeg_ffprobe_static.default.ffmpegPath;
|
|
19
|
+
if (typeof staticPath === "string" && (0, node_fs.existsSync)(staticPath)) return staticPath;
|
|
20
|
+
return "ffmpeg";
|
|
21
|
+
};
|
|
16
22
|
const runFfmpeg = async (args) => {
|
|
17
23
|
await new Promise((resolve, reject) => {
|
|
18
|
-
const
|
|
24
|
+
const ffmpegPath = resolveFfmpegPath();
|
|
25
|
+
const child = (0, node_child_process.spawn)(ffmpegPath, [...args], { stdio: [
|
|
19
26
|
"ignore",
|
|
20
27
|
"ignore",
|
|
21
28
|
"pipe"
|
|
@@ -25,7 +32,15 @@ const runFfmpeg = async (args) => {
|
|
|
25
32
|
child.stderr?.on("data", (chunk) => {
|
|
26
33
|
stderr += chunk;
|
|
27
34
|
});
|
|
28
|
-
child.on("error", (error) =>
|
|
35
|
+
child.on("error", (error) => {
|
|
36
|
+
const details = [
|
|
37
|
+
"Failed to spawn ffmpeg.",
|
|
38
|
+
`Resolved ffmpeg path: ${ffmpegPath}`,
|
|
39
|
+
`FFMPEG_PATH: ${process.env.FFMPEG_PATH ?? "(unset)"}`,
|
|
40
|
+
`ffmpeg-ffprobe-static ffmpegPath: ${ffmpeg_ffprobe_static.default.ffmpegPath ?? "(missing)"}`
|
|
41
|
+
].join("\n");
|
|
42
|
+
reject(/* @__PURE__ */ new Error(`${details}\n\n${String(error)}`));
|
|
43
|
+
});
|
|
29
44
|
child.on("close", (code) => {
|
|
30
45
|
if (code === 0) {
|
|
31
46
|
resolve();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video.service.cjs","names":["
|
|
1
|
+
{"version":3,"file":"video.service.cjs","names":["path","ffbin","BaseService"],"sources":["../../../../src/modules/video/video.service.ts"],"sourcesContent":["import { closeSync, existsSync, mkdirSync, openSync } from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport { spawn } from \"node:child_process\";\r\n//\r\nimport ffbin from \"ffmpeg-ffprobe-static\";\r\nimport { ok } from \"neverthrow\";\r\nimport { v4 as uuidv4 } from \"uuid\";\r\nimport type { ServerResultAsync } from \"../base/base.dto\";\r\nimport { BaseService } from \"../base/base.service\";\r\n\r\nconst uploadsDir = path.join(__dirname, \"..\", \"uploads\");\r\nif (!existsSync(uploadsDir)) {\r\n mkdirSync(uploadsDir, { recursive: true });\r\n}\r\n\r\nconst resolveFfmpegPath = (): string => {\r\n const envPath = process.env.FFMPEG_PATH;\r\n if (envPath && existsSync(envPath)) return envPath;\r\n\r\n const staticPath = ffbin.ffmpegPath;\r\n if (typeof staticPath === \"string\" && existsSync(staticPath)) return staticPath;\r\n\r\n return \"ffmpeg\";\r\n};\r\n\r\nconst runFfmpeg = async (args: readonly string[]): Promise<void> => {\r\n await new Promise<void>((resolve, reject) => {\r\n const ffmpegPath = resolveFfmpegPath();\r\n const child = spawn(ffmpegPath, [...args], {\r\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\r\n });\r\n\r\n let stderr = \"\";\r\n child.stderr?.setEncoding(\"utf8\");\r\n child.stderr?.on(\"data\", (chunk: string) => {\r\n stderr += chunk;\r\n });\r\n\r\n child.on(\"error\", (error) => {\r\n const details = [\r\n \"Failed to spawn ffmpeg.\",\r\n `Resolved ffmpeg path: ${ffmpegPath}`,\r\n `FFMPEG_PATH: ${process.env.FFMPEG_PATH ?? \"(unset)\"}`,\r\n `ffmpeg-ffprobe-static ffmpegPath: ${ffbin.ffmpegPath ?? \"(missing)\"}`,\r\n ].join(\"\\n\");\r\n reject(new Error(`${details}\\n\\n${String(error)}`));\r\n });\r\n child.on(\"close\", (code) => {\r\n if (code === 0) {\r\n resolve();\r\n return;\r\n }\r\n reject(new Error(stderr || `ffmpeg exited with code ${code ?? \"unknown\"}`));\r\n });\r\n });\r\n};\r\n\r\nexport class VideoService extends BaseService<never, never> {\r\n async cut(file: string, start: number, end: number): ServerResultAsync<string> {\r\n return this.throwableAsync(async () => {\r\n const duration = end - start;\r\n const output = path.join(uploadsDir, `${uuidv4()}.mp4`);\r\n if (!existsSync(output)) {\r\n closeSync(openSync(output, \"w\"));\r\n }\r\n\r\n await runFfmpeg([\r\n \"-i\",\r\n file,\r\n \"-ss\",\r\n String(start),\r\n \"-t\",\r\n String(duration),\r\n \"-c:v\",\r\n \"libx264\",\r\n \"-c:a\",\r\n \"copy\",\r\n \"-movflags\",\r\n \"+faststart\",\r\n \"-y\",\r\n output,\r\n ]).catch((error) => {\r\n throw this.handleUnknownError(error);\r\n });\r\n\r\n return ok(output);\r\n });\r\n }\r\n\r\n async webmToWav(input: string, hz = 48000): ServerResultAsync<string> {\r\n return this.throwableAsync(async () => {\r\n const output = path.join(uploadsDir, `${uuidv4()}.wav`);\r\n if (!existsSync(output)) {\r\n closeSync(openSync(output, \"w\"));\r\n }\r\n\r\n await runFfmpeg([\r\n \"-i\",\r\n input,\r\n \"-vn\",\r\n \"-c:a\",\r\n \"pcm_s16le\",\r\n \"-ar\",\r\n String(hz),\r\n \"-ac\",\r\n \"2\",\r\n \"-f\",\r\n \"wav\",\r\n \"-y\",\r\n output,\r\n ]).catch((error) => {\r\n throw this.handleUnknownError(error);\r\n });\r\n return ok(output);\r\n });\r\n }\r\n\r\n async extractAudioMp3(input: string, kbps = 192, streamIndex = 0): ServerResultAsync<string> {\r\n return this.throwableAsync(async () => {\r\n const output = path.join(uploadsDir, `${uuidv4()}.mp3`);\r\n if (!existsSync(output)) {\r\n closeSync(openSync(output, \"w\"));\r\n }\r\n await runFfmpeg([\r\n \"-i\",\r\n input,\r\n \"-map\",\r\n `0:a:${streamIndex}`,\r\n \"-c:a\",\r\n \"libmp3lame\",\r\n \"-b:a\",\r\n `${kbps}k`,\r\n \"-y\",\r\n output,\r\n ]).catch((error) => {\r\n throw this.handleUnknownError(error);\r\n });\r\n\r\n return ok(output);\r\n });\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
@@ -6,12 +6,19 @@ import { closeSync, existsSync, mkdirSync, openSync } from "node:fs";
|
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
7
|
import ffbin from "ffmpeg-ffprobe-static";
|
|
8
8
|
//#region src/modules/video/video.service.ts
|
|
9
|
-
if (!ffbin.ffmpegPath || !ffbin.ffprobePath) throw new Error("FFmpeg or FFprobe not found");
|
|
10
9
|
const uploadsDir = path.join(__dirname, "..", "uploads");
|
|
11
10
|
if (!existsSync(uploadsDir)) mkdirSync(uploadsDir, { recursive: true });
|
|
11
|
+
const resolveFfmpegPath = () => {
|
|
12
|
+
const envPath = process.env.FFMPEG_PATH;
|
|
13
|
+
if (envPath && existsSync(envPath)) return envPath;
|
|
14
|
+
const staticPath = ffbin.ffmpegPath;
|
|
15
|
+
if (typeof staticPath === "string" && existsSync(staticPath)) return staticPath;
|
|
16
|
+
return "ffmpeg";
|
|
17
|
+
};
|
|
12
18
|
const runFfmpeg = async (args) => {
|
|
13
19
|
await new Promise((resolve, reject) => {
|
|
14
|
-
const
|
|
20
|
+
const ffmpegPath = resolveFfmpegPath();
|
|
21
|
+
const child = spawn(ffmpegPath, [...args], { stdio: [
|
|
15
22
|
"ignore",
|
|
16
23
|
"ignore",
|
|
17
24
|
"pipe"
|
|
@@ -21,7 +28,15 @@ const runFfmpeg = async (args) => {
|
|
|
21
28
|
child.stderr?.on("data", (chunk) => {
|
|
22
29
|
stderr += chunk;
|
|
23
30
|
});
|
|
24
|
-
child.on("error", (error) =>
|
|
31
|
+
child.on("error", (error) => {
|
|
32
|
+
const details = [
|
|
33
|
+
"Failed to spawn ffmpeg.",
|
|
34
|
+
`Resolved ffmpeg path: ${ffmpegPath}`,
|
|
35
|
+
`FFMPEG_PATH: ${process.env.FFMPEG_PATH ?? "(unset)"}`,
|
|
36
|
+
`ffmpeg-ffprobe-static ffmpegPath: ${ffbin.ffmpegPath ?? "(missing)"}`
|
|
37
|
+
].join("\n");
|
|
38
|
+
reject(/* @__PURE__ */ new Error(`${details}\n\n${String(error)}`));
|
|
39
|
+
});
|
|
25
40
|
child.on("close", (code) => {
|
|
26
41
|
if (code === 0) {
|
|
27
42
|
resolve();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video.service.mjs","names":["uuidv4"],"sources":["../../../../src/modules/video/video.service.ts"],"sourcesContent":["import { closeSync, existsSync, mkdirSync, openSync } from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport { spawn } from \"node:child_process\";\r\n//\r\nimport ffbin from \"ffmpeg-ffprobe-static\";\r\nimport { ok } from \"neverthrow\";\r\nimport { v4 as uuidv4 } from \"uuid\";\r\nimport type { ServerResultAsync } from \"../base/base.dto\";\r\nimport { BaseService } from \"../base/base.service\";\r\n\r\
|
|
1
|
+
{"version":3,"file":"video.service.mjs","names":["uuidv4"],"sources":["../../../../src/modules/video/video.service.ts"],"sourcesContent":["import { closeSync, existsSync, mkdirSync, openSync } from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport { spawn } from \"node:child_process\";\r\n//\r\nimport ffbin from \"ffmpeg-ffprobe-static\";\r\nimport { ok } from \"neverthrow\";\r\nimport { v4 as uuidv4 } from \"uuid\";\r\nimport type { ServerResultAsync } from \"../base/base.dto\";\r\nimport { BaseService } from \"../base/base.service\";\r\n\r\nconst uploadsDir = path.join(__dirname, \"..\", \"uploads\");\r\nif (!existsSync(uploadsDir)) {\r\n mkdirSync(uploadsDir, { recursive: true });\r\n}\r\n\r\nconst resolveFfmpegPath = (): string => {\r\n const envPath = process.env.FFMPEG_PATH;\r\n if (envPath && existsSync(envPath)) return envPath;\r\n\r\n const staticPath = ffbin.ffmpegPath;\r\n if (typeof staticPath === \"string\" && existsSync(staticPath)) return staticPath;\r\n\r\n return \"ffmpeg\";\r\n};\r\n\r\nconst runFfmpeg = async (args: readonly string[]): Promise<void> => {\r\n await new Promise<void>((resolve, reject) => {\r\n const ffmpegPath = resolveFfmpegPath();\r\n const child = spawn(ffmpegPath, [...args], {\r\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\r\n });\r\n\r\n let stderr = \"\";\r\n child.stderr?.setEncoding(\"utf8\");\r\n child.stderr?.on(\"data\", (chunk: string) => {\r\n stderr += chunk;\r\n });\r\n\r\n child.on(\"error\", (error) => {\r\n const details = [\r\n \"Failed to spawn ffmpeg.\",\r\n `Resolved ffmpeg path: ${ffmpegPath}`,\r\n `FFMPEG_PATH: ${process.env.FFMPEG_PATH ?? \"(unset)\"}`,\r\n `ffmpeg-ffprobe-static ffmpegPath: ${ffbin.ffmpegPath ?? \"(missing)\"}`,\r\n ].join(\"\\n\");\r\n reject(new Error(`${details}\\n\\n${String(error)}`));\r\n });\r\n child.on(\"close\", (code) => {\r\n if (code === 0) {\r\n resolve();\r\n return;\r\n }\r\n reject(new Error(stderr || `ffmpeg exited with code ${code ?? \"unknown\"}`));\r\n });\r\n });\r\n};\r\n\r\nexport class VideoService extends BaseService<never, never> {\r\n async cut(file: string, start: number, end: number): ServerResultAsync<string> {\r\n return this.throwableAsync(async () => {\r\n const duration = end - start;\r\n const output = path.join(uploadsDir, `${uuidv4()}.mp4`);\r\n if (!existsSync(output)) {\r\n closeSync(openSync(output, \"w\"));\r\n }\r\n\r\n await runFfmpeg([\r\n \"-i\",\r\n file,\r\n \"-ss\",\r\n String(start),\r\n \"-t\",\r\n String(duration),\r\n \"-c:v\",\r\n \"libx264\",\r\n \"-c:a\",\r\n \"copy\",\r\n \"-movflags\",\r\n \"+faststart\",\r\n \"-y\",\r\n output,\r\n ]).catch((error) => {\r\n throw this.handleUnknownError(error);\r\n });\r\n\r\n return ok(output);\r\n });\r\n }\r\n\r\n async webmToWav(input: string, hz = 48000): ServerResultAsync<string> {\r\n return this.throwableAsync(async () => {\r\n const output = path.join(uploadsDir, `${uuidv4()}.wav`);\r\n if (!existsSync(output)) {\r\n closeSync(openSync(output, \"w\"));\r\n }\r\n\r\n await runFfmpeg([\r\n \"-i\",\r\n input,\r\n \"-vn\",\r\n \"-c:a\",\r\n \"pcm_s16le\",\r\n \"-ar\",\r\n String(hz),\r\n \"-ac\",\r\n \"2\",\r\n \"-f\",\r\n \"wav\",\r\n \"-y\",\r\n output,\r\n ]).catch((error) => {\r\n throw this.handleUnknownError(error);\r\n });\r\n return ok(output);\r\n });\r\n }\r\n\r\n async extractAudioMp3(input: string, kbps = 192, streamIndex = 0): ServerResultAsync<string> {\r\n return this.throwableAsync(async () => {\r\n const output = path.join(uploadsDir, `${uuidv4()}.mp3`);\r\n if (!existsSync(output)) {\r\n closeSync(openSync(output, \"w\"));\r\n }\r\n await runFfmpeg([\r\n \"-i\",\r\n input,\r\n \"-map\",\r\n `0:a:${streamIndex}`,\r\n \"-c:a\",\r\n \"libmp3lame\",\r\n \"-b:a\",\r\n `${kbps}k`,\r\n \"-y\",\r\n output,\r\n ]).catch((error) => {\r\n throw this.handleUnknownError(error);\r\n });\r\n\r\n return ok(output);\r\n });\r\n }\r\n}\r\n"],"mappings":";;;;;;;;AAUA,MAAM,aAAa,KAAK,KAAK,WAAW,MAAM,UAAU;AACxD,IAAI,CAAC,WAAW,WAAW,CACzB,WAAU,YAAY,EAAE,WAAW,MAAM,CAAC;AAG5C,MAAM,0BAAkC;CACtC,MAAM,UAAU,QAAQ,IAAI;AAC5B,KAAI,WAAW,WAAW,QAAQ,CAAE,QAAO;CAE3C,MAAM,aAAa,MAAM;AACzB,KAAI,OAAO,eAAe,YAAY,WAAW,WAAW,CAAE,QAAO;AAErE,QAAO;;AAGT,MAAM,YAAY,OAAO,SAA2C;AAClE,OAAM,IAAI,SAAe,SAAS,WAAW;EAC3C,MAAM,aAAa,mBAAmB;EACtC,MAAM,QAAQ,MAAM,YAAY,CAAC,GAAG,KAAK,EAAE,EACzC,OAAO;GAAC;GAAU;GAAU;GAAO,EACpC,CAAC;EAEF,IAAI,SAAS;AACb,QAAM,QAAQ,YAAY,OAAO;AACjC,QAAM,QAAQ,GAAG,SAAS,UAAkB;AAC1C,aAAU;IACV;AAEF,QAAM,GAAG,UAAU,UAAU;GAC3B,MAAM,UAAU;IACd;IACA,yBAAyB;IACzB,gBAAgB,QAAQ,IAAI,eAAe;IAC3C,qCAAqC,MAAM,cAAc;IAC1D,CAAC,KAAK,KAAK;AACZ,0BAAO,IAAI,MAAM,GAAG,QAAQ,MAAM,OAAO,MAAM,GAAG,CAAC;IACnD;AACF,QAAM,GAAG,UAAU,SAAS;AAC1B,OAAI,SAAS,GAAG;AACd,aAAS;AACT;;AAEF,UAAO,IAAI,MAAM,UAAU,2BAA2B,QAAQ,YAAY,CAAC;IAC3E;GACF;;AAGJ,IAAa,eAAb,cAAkC,YAA0B;CAC1D,MAAM,IAAI,MAAc,OAAe,KAAwC;AAC7E,SAAO,KAAK,eAAe,YAAY;GACrC,MAAM,WAAW,MAAM;GACvB,MAAM,SAAS,KAAK,KAAK,YAAY,GAAGA,IAAQ,CAAC,MAAM;AACvD,OAAI,CAAC,WAAW,OAAO,CACrB,WAAU,SAAS,QAAQ,IAAI,CAAC;AAGlC,SAAM,UAAU;IACd;IACA;IACA;IACA,OAAO,MAAM;IACb;IACA,OAAO,SAAS;IAChB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACD,CAAC,CAAC,OAAO,UAAU;AAClB,UAAM,KAAK,mBAAmB,MAAM;KACpC;AAEF,UAAO,GAAG,OAAO;IACjB;;CAGJ,MAAM,UAAU,OAAe,KAAK,MAAkC;AACpE,SAAO,KAAK,eAAe,YAAY;GACrC,MAAM,SAAS,KAAK,KAAK,YAAY,GAAGA,IAAQ,CAAC,MAAM;AACvD,OAAI,CAAC,WAAW,OAAO,CACrB,WAAU,SAAS,QAAQ,IAAI,CAAC;AAGlC,SAAM,UAAU;IACd;IACA;IACA;IACA;IACA;IACA;IACA,OAAO,GAAG;IACV;IACA;IACA;IACA;IACA;IACA;IACD,CAAC,CAAC,OAAO,UAAU;AAClB,UAAM,KAAK,mBAAmB,MAAM;KACpC;AACF,UAAO,GAAG,OAAO;IACjB;;CAGJ,MAAM,gBAAgB,OAAe,OAAO,KAAK,cAAc,GAA8B;AAC3F,SAAO,KAAK,eAAe,YAAY;GACrC,MAAM,SAAS,KAAK,KAAK,YAAY,GAAGA,IAAQ,CAAC,MAAM;AACvD,OAAI,CAAC,WAAW,OAAO,CACrB,WAAU,SAAS,QAAQ,IAAI,CAAC;AAElC,SAAM,UAAU;IACd;IACA;IACA;IACA,OAAO;IACP;IACA;IACA;IACA,GAAG,KAAK;IACR;IACA;IACD,CAAC,CAAC,OAAO,UAAU;AAClB,UAAM,KAAK,mBAAmB,MAAM;KACpC;AAEF,UAAO,GAAG,OAAO;IACjB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@m5kdev/backend",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"description": "Composable Express server stack with Drizzle ORM and tRPC.",
|
|
5
5
|
"license": "GPL-3.0-only",
|
|
6
6
|
"repository": {
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"turndown": "7.2.2",
|
|
62
62
|
"uuid": "11.0.5",
|
|
63
63
|
"zod": "4.2.1",
|
|
64
|
-
"@m5kdev/
|
|
65
|
-
"@m5kdev/
|
|
64
|
+
"@m5kdev/commons": "0.9.4",
|
|
65
|
+
"@m5kdev/config": "0.9.4"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@jest/globals": "30.2.0",
|