full
This commit is contained in:
98
utils/postVideoProcessing.js
Normal file
98
utils/postVideoProcessing.js
Normal 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,
|
||||
};
|
||||
Reference in New Issue
Block a user