This commit is contained in:
payacom
2026-08-31 20:45:41 +03:30
parent fa9f23c61d
commit 3f416a7a60
367 changed files with 621895 additions and 6443 deletions

View File

@@ -0,0 +1,98 @@
const fs = require("fs-extra");
const path = require("path");
const { exec } = require("child_process");
const util = require("util");
const PostModel = require("../models/PostModel");
const execPromise = util.promisify(exec);
async function runFfmpeg(command) {
try {
await execPromise(command);
return true;
} catch (error) {
console.error("[postVideoProcessing]", error.message);
return false;
}
}
/** faststart بدون re-encode — شروع پخش سریع‌تر */
async function optimizeVideoFaststart(filePath) {
if (!(await fs.pathExists(filePath))) return;
const dir = path.dirname(filePath);
const base = path.basename(filePath);
const tempPath = path.join(dir, `faststart-${Date.now()}-${base}`);
const ok = await runFfmpeg(
`ffmpeg -i "${filePath}" -c copy -movflags +faststart "${tempPath}" -y`
);
if (!ok || !(await fs.pathExists(tempPath))) return;
try {
await fs.rename(tempPath, filePath);
console.log(`[postVideoProcessing] faststart applied: ${base}`);
} catch (error) {
await fs.remove(tempPath).catch(() => {});
console.error("[postVideoProcessing] faststart rename failed:", error.message);
}
}
/** thumbnail برای poster در گرید و ریلز */
async function generateVideoThumbnail(filePath, postId) {
if (!(await fs.pathExists(filePath))) return null;
const thumbDir = path.join(path.dirname(filePath), "..", "thumbnails");
await fs.ensureDir(thumbDir);
const thumbName = `${postId}.jpg`;
const thumbPath = path.join(thumbDir, thumbName);
const ok = await runFfmpeg(
`ffmpeg -ss 00:00:00.5 -i "${filePath}" -vframes 1 -q:v 3 -y "${thumbPath}"`
);
if (!ok || !(await fs.pathExists(thumbPath))) return null;
// مسیر نسبی قابل سرو از /storage/posts/thumbnails
return `/posts/thumbnails/${thumbName}`;
}
async function processPostVideoInBackground(filePath, postId) {
if (!filePath || !postId) return;
const existing = await PostModel.findById(postId).select('files post_images').lean();
const hasCustomCover =
existing?.files?.some((file) => file.type === 'image') ||
(existing?.post_images?.length ?? 0) > 0;
if (hasCustomCover) {
console.log(`[postVideoProcessing] skip thumbnail (custom cover) post=${postId}`);
await optimizeVideoFaststart(filePath);
return;
}
console.log(`[postVideoProcessing] start post=${postId}`);
await optimizeVideoFaststart(filePath);
const relativeThumb = await generateVideoThumbnail(filePath, postId);
if (!relativeThumb) return;
try {
await PostModel.findByIdAndUpdate(postId, {
$set: {
post_images: [relativeThumb],
},
});
console.log(`[postVideoProcessing] thumbnail saved post=${postId} path=${relativeThumb}`);
} catch (error) {
console.error("[postVideoProcessing] db update failed:", error.message);
}
}
module.exports = {
processPostVideoInBackground,
optimizeVideoFaststart,
generateVideoThumbnail,
};