vid support

This commit is contained in:
Patryk Koreń
2026-07-13 22:41:24 +02:00
parent fa9e3b5ce7
commit 6f66824571
11 changed files with 307 additions and 35 deletions

2
back/.gitignore vendored
View File

@@ -1,6 +1,8 @@
# dependencies (bun install)
node_modules
downloads
downloads_vids
frames.txt
*.mp3
*.mp4

View File

@@ -1,27 +1,78 @@
import { execa } from "execa";
import ffmpegPath from "ffmpeg-static";
import { globSync, mkdirSync, readdirSync, rmSync, statSync, writeFile, writeFileSync } from "node:fs";
import path from "node:path";
import { LOCAL_DIR, LOCAL_DIR_VID, LOCAL_DIR_VID_EXTRACTED } from "./nextcloud";
export async function encodeFrames({
inputPattern,
output,
fps = 30,
}: {
inputPattern: string;
output: string;
fps?: number;
}) {
export async function createVid(
fps: number,
) {
const entries = readdirSync(LOCAL_DIR_VID);
const videos = (
await Promise.all(
entries.map(async (entry) => {
const fullPath = path.join(LOCAL_DIR_VID, entry);
const info = statSync(fullPath);
if (!info.isFile()) {
return null;
}
if (!entry.toLowerCase().endsWith(".mp4")) {
return null;
}
return fullPath;
})
)
).filter(Boolean) as string[];
for (const file of globSync(`${LOCAL_DIR_VID_EXTRACTED}/*`)) {
rmSync(file, { recursive: true, force: true });
}
await Promise.all(
videos.map((video) =>
extractFrames(video, LOCAL_DIR_VID_EXTRACTED, 1)
)
);
encodeFrames([
`${LOCAL_DIR}/*.jpg`,
`${LOCAL_DIR}/*.png`,
`${LOCAL_DIR_VID_EXTRACTED}/*.jpg`,
], path.resolve("./out.mp4"), fps)
}
export async function encodeFrames(
inputPatterns: string[],
output: string,
fps: number = 30,
) {
console.log("kleje filmik");
const files = inputPatterns
.flatMap(pattern => globSync(pattern))
.sort();
const concatFile = files
.map(file => `file '${file}'\nduration ${1/fps}`)
.join("\n");
writeFileSync("./frames.txt", concatFile);
await execa(ffmpegPath!, [
"-y",
"-pattern_type", "glob",
"-framerate", fps.toString(),
"-i", inputPattern.toString(),
// "-pattern_type", "glob",
// "-framerate", fps.toString(),
"-f", "concat",
"-safe", "0",
"-i", "./frames.txt",
"-i", path.resolve("./music.mp3"),
"-vf",
"transpose=2,scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2",
`transpose=2,scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2`,
// "-autorotate",
"-c:v", "libx264",
@@ -37,3 +88,28 @@ export async function encodeFrames({
]);
console.log("filmik sklejony");
}
export async function extractFrames(
input: string,
outputDir: string,
targetFps: number,
): Promise<void> {
if (!ffmpegPath) {
throw new Error("ffmpeg-static binary not found.");
}
mkdirSync(outputDir, { recursive: true });
const fileName = path.basename(path.resolve(input));
const outputPattern = path.join(outputDir, `${fileName}_frame_%06d.jpg`);
await execa(ffmpegPath!, [
"-y", "-i",
input,
"-vf",
`fps=${targetFps}`,
"-q:v",
"2", // JPEG quality (2 = very high)
outputPattern,
])
}

View File

@@ -1,12 +1,19 @@
import { createClient } from "webdav";
import { createClient, type FileStat } from "webdav";
import fs from "fs/promises";
import path from "path";
export const REMOTE_DIR = "/";
export const LOCAL_DIR = path.resolve("./downloads");
export const LOCAL_DIR_VID = path.resolve("./downloads_vids");
export const LOCAL_DIR_VID_EXTRACTED = path.join(path.resolve("./downloads_vids"), "extracted");
export async function downloadMissing(remoteDir: string = REMOTE_DIR) {
await fs.mkdir(LOCAL_DIR, { recursive: true })
await Promise.all([
fs.mkdir(LOCAL_DIR, { recursive: true }),
fs.mkdir(LOCAL_DIR_VID, { recursive: true }),
fs.mkdir(LOCAL_DIR_VID_EXTRACTED, { recursive: true }),
]);
const client = createClient(
"https://nextcloud.papryk.com/public.php/dav/files/ckkL3Pg4X4Ye6pz",
{
@@ -14,16 +21,48 @@ export async function downloadMissing(remoteDir: string = REMOTE_DIR) {
password: "",
}
);
const contents = await client.getDirectoryContents(remoteDir);
const exisitngFiles = await fs.readdir(LOCAL_DIR);
const [
existingImgFiles,
existingVidFiles,
] = await Promise.all([
fs.readdir(LOCAL_DIR),
fs.readdir(LOCAL_DIR_VID),
]);
const missing = contents.filter(f => !exisitngFiles.includes(f.basename));
const existingImgs = new Set(existingImgFiles);
const existingVids = new Set(existingVidFiles);
console.log(missing);
const missingImages = contents.filter(
({ basename, mime }) =>
mime?.startsWith("image/") &&
!existingImgs.has(basename)
);
const missingVideos = contents.filter(
({ basename, mime }) =>
mime?.startsWith("video/") &&
!existingVids.has(basename)
);
await Promise.all([
downloadToDir(missingImages, LOCAL_DIR),
downloadToDir(missingVideos, LOCAL_DIR_VID),
]);
}
async function downloadToDir(files: FileStat[], dir: string) {
const client = createClient(
"https://nextcloud.papryk.com/public.php/dav/files/ckkL3Pg4X4Ye6pz",
{
username: "ckkL3Pg4X4Ye6pz",
password: "",
}
);
let i = 0;
for (const item of missing) {
for (const item of files) {
const result = await client.getFileContents(item.filename, {
format: "binary",
});
@@ -51,12 +90,10 @@ export async function downloadMissing(remoteDir: string = REMOTE_DIR) {
buffer = Buffer.from(result as any);
}
const localPath = path.resolve(path.join(LOCAL_DIR, item.filename))
const localPath = path.resolve(path.join(dir, item.filename))
await fs.writeFile(localPath, buffer);
i++;
console.log(`downloaded ${i}/${missing.length} ${item.basename}`)
console.log(`downloaded ${i}/${files.length} ${item.basename}`)
}
}

View File

@@ -2,7 +2,7 @@
import { Router } from "express";
import { downloadMissing, LOCAL_DIR } from "../lib/nextcloud";
import path from "node:path";
import { encodeFrames } from "../lib/crateClip";
import { createVid, encodeFrames } from "../lib/crateClip";
import fs from "node:fs"
const router = Router();
@@ -25,13 +25,11 @@ router.get("/video", async (req, res) => {
router.post("/createclip", async (req, res) => {
try {
await encodeFrames({
inputPattern: `${LOCAL_DIR}/*.jpg`,
output: path.resolve("./out.mp4"),
fps: 8,
});
console.log("create clip request")
await createVid(8)
res.status(200).end();
} catch {
} catch (e) {
console.log(e)
res.status(501).end();
}
})

View File

@@ -10,3 +10,4 @@ declare module "express-serve-static-core" {
} | string | jwt.JwtPayload;
}
}