kes-kopyala-yapıştır eklendi
This commit is contained in:
269
server/server.js
269
server/server.js
@@ -517,20 +517,59 @@ function parseFrameRate(value) {
|
||||
return Number.isFinite(num) ? num : null;
|
||||
}
|
||||
|
||||
async function extractMediaInfo(filePath) {
|
||||
async function extractMediaInfo(filePath, retryCount = 0) {
|
||||
if (!filePath || !fs.existsSync(filePath)) return null;
|
||||
|
||||
// Farklı ffprobe stratejileri
|
||||
const strategies = [
|
||||
// Standart yöntem
|
||||
`${FFPROBE_PATH} -v quiet -print_format json -show_format -show_streams "${filePath}"`,
|
||||
// Daha toleranslı yöntem
|
||||
`${FFPROBE_PATH} -v error -print_format json -show_format -show_streams "${filePath}"`,
|
||||
// Sadece format bilgisi
|
||||
`${FFPROBE_PATH} -v error -print_format json -show_format "${filePath}"`,
|
||||
// En basit yöntem
|
||||
`${FFPROBE_PATH} -v fatal -print_format json -show_format -show_streams "${filePath}"`
|
||||
];
|
||||
|
||||
const strategyIndex = Math.min(retryCount, strategies.length - 1);
|
||||
const cmd = strategies[strategyIndex];
|
||||
|
||||
return new Promise((resolve) => {
|
||||
exec(
|
||||
`${FFPROBE_PATH} -v quiet -print_format json -show_format -show_streams "${filePath}"`,
|
||||
cmd,
|
||||
{ maxBuffer: FFPROBE_MAX_BUFFER },
|
||||
(err, stdout) => {
|
||||
(err, stdout, stderr) => {
|
||||
if (err) {
|
||||
console.warn(
|
||||
`⚠️ ffprobe çalıştırılamadı (${filePath}): ${err.message}`
|
||||
);
|
||||
// Hata durumunda yeniden dene
|
||||
if (retryCount < strategies.length - 1) {
|
||||
console.warn(
|
||||
`⚠️ ffprobe çalıştırılamadı (${filePath}): ${err.message}. Yeniden deneme (${retryCount + 1}/${strategies.length - 1})`
|
||||
);
|
||||
setTimeout(() => extractMediaInfo(filePath, retryCount + 1).then(resolve), 1000 * (retryCount + 1));
|
||||
return;
|
||||
}
|
||||
|
||||
// Tüm denemeler başarısız oldu
|
||||
console.error(`❌ ffprobe çalıştırılamadı (${filePath}): ${err.message}`);
|
||||
if (stderr) {
|
||||
console.error(`🔍 ffprobe stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
// Dosya boyutu kontrolü
|
||||
try {
|
||||
const stats = fs.statSync(filePath);
|
||||
const fileSizeMB = stats.size / (1024 * 1024);
|
||||
if (fileSizeMB < 1) {
|
||||
console.warn(`⚠️ Dosya çok küçük olabilir (${fileSizeMB.toFixed(2)}MB): ${filePath}`);
|
||||
}
|
||||
} catch (statErr) {
|
||||
console.warn(`⚠️ Dosya bilgileri alınamadı: ${statErr.message}`);
|
||||
}
|
||||
|
||||
return resolve(null);
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(stdout);
|
||||
const streams = Array.isArray(parsed?.streams) ? parsed.streams : [];
|
||||
@@ -577,8 +616,17 @@ async function extractMediaInfo(filePath) {
|
||||
|
||||
resolve(mediaInfo);
|
||||
} catch (parseErr) {
|
||||
console.warn(
|
||||
`⚠️ ffprobe çıktısı parse edilemedi (${filePath}): ${parseErr.message}`
|
||||
// Parse hatası durumunda yeniden dene
|
||||
if (retryCount < strategies.length - 1) {
|
||||
console.warn(
|
||||
`⚠️ ffprobe çıktısı parse edilemedi (${filePath}): ${parseErr.message}. Yeniden deneme (${retryCount + 1}/${strategies.length - 1})`
|
||||
);
|
||||
setTimeout(() => extractMediaInfo(filePath, retryCount + 1).then(resolve), 1000 * (retryCount + 1));
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(
|
||||
`❌ ffprobe çıktısı parse edilemedi (${filePath}): ${parseErr.message}`
|
||||
);
|
||||
resolve(null);
|
||||
}
|
||||
@@ -587,27 +635,65 @@ async function extractMediaInfo(filePath) {
|
||||
});
|
||||
}
|
||||
|
||||
function queueVideoThumbnail(fullPath, relPath) {
|
||||
function queueVideoThumbnail(fullPath, relPath, retryCount = 0) {
|
||||
const { relThumb, absThumb } = getVideoThumbnailPaths(relPath);
|
||||
if (fs.existsSync(absThumb) || generatingThumbnails.has(absThumb)) return;
|
||||
|
||||
ensureDirForFile(absThumb);
|
||||
markGenerating(absThumb, true);
|
||||
|
||||
const cmd = `ffmpeg -y -ss ${VIDEO_THUMBNAIL_TIME} -i "${fullPath}" -frames:v 1 -vf "scale=320:-1" -q:v 2 "${absThumb}"`;
|
||||
exec(cmd, (err) => {
|
||||
// Farklı ffmpeg stratejileri deneme
|
||||
const strategies = [
|
||||
// Standart yöntem
|
||||
`ffmpeg -y -ss ${VIDEO_THUMBNAIL_TIME} -i "${fullPath}" -frames:v 1 -vf "scale=320:-1" -q:v 2 "${absThumb}"`,
|
||||
// Daha toleranslı yöntem - hata ayıklama modu
|
||||
`ffmpeg -y -ss ${VIDEO_THUMBNAIL_TIME} -i "${fullPath}" -frames:v 1 -vf "scale=320:-1" -q:v 2 -err_detect ignore_err "${absThumb}"`,
|
||||
// Dosya sonundan başlayarak deneme
|
||||
`ffmpeg -y -ss 5 -i "${fullPath}" -frames:v 1 -vf "scale=320:-1" -q:v 2 -avoid_negative_ts make_zero "${absThumb}"`,
|
||||
// En basit yöntem - sadece kare yakalama
|
||||
`ffmpeg -y -i "${fullPath}" -frames:v 1 -vf "scale=320:-1" -q:v 3 "${absThumb}"`
|
||||
];
|
||||
|
||||
const strategyIndex = Math.min(retryCount, strategies.length - 1);
|
||||
const cmd = strategies[strategyIndex];
|
||||
|
||||
exec(cmd, (err, stdout, stderr) => {
|
||||
markGenerating(absThumb, false);
|
||||
|
||||
if (err) {
|
||||
console.warn(`⚠️ Video thumbnail oluşturulamadı (${fullPath}): ${err.message}`);
|
||||
// Hata durumunda yeniden dene
|
||||
if (retryCount < strategies.length - 1) {
|
||||
console.warn(`⚠️ Video thumbnail oluşturulamadı (${fullPath}): ${err.message}. Yeniden deneme (${retryCount + 1}/${strategies.length - 1})`);
|
||||
setTimeout(() => queueVideoThumbnail(fullPath, relPath, retryCount + 1), 1000 * (retryCount + 1));
|
||||
return;
|
||||
}
|
||||
|
||||
// Tüm denemeler başarısız oldu
|
||||
console.error(`❌ Video thumbnail oluşturulamadı (${fullPath}): ${err.message}`);
|
||||
if (stderr) {
|
||||
console.error(`🔍 ffmpeg stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
// Bozuk dosya kontrolü
|
||||
try {
|
||||
const stats = fs.statSync(fullPath);
|
||||
const fileSizeMB = stats.size / (1024 * 1024);
|
||||
if (fileSizeMB < 1) {
|
||||
console.warn(`⚠️ Dosya çok küçük olabilir (${fileSizeMB.toFixed(2)}MB): ${fullPath}`);
|
||||
}
|
||||
} catch (statErr) {
|
||||
console.warn(`⚠️ Dosya bilgileri alınamadı: ${statErr.message}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`🎞️ Video thumbnail oluşturuldu: ${absThumb}`);
|
||||
const root = rootFromRelPath(relPath);
|
||||
if (root) broadcastFileUpdate(root);
|
||||
});
|
||||
}
|
||||
|
||||
function queueImageThumbnail(fullPath, relPath) {
|
||||
function queueImageThumbnail(fullPath, relPath, retryCount = 0) {
|
||||
const { relThumb, absThumb } = getImageThumbnailPaths(relPath);
|
||||
if (fs.existsSync(absThumb) || generatingThumbnails.has(absThumb)) return;
|
||||
|
||||
@@ -616,15 +702,39 @@ function queueImageThumbnail(fullPath, relPath) {
|
||||
|
||||
const outputExt = path.extname(absThumb).toLowerCase();
|
||||
const needsQuality = outputExt === ".jpg" || outputExt === ".jpeg";
|
||||
const qualityArgs = needsQuality ? ' -q:v 5' : "";
|
||||
|
||||
// Farklı ffmpeg stratejileri deneme
|
||||
const strategies = [
|
||||
// Standart yöntem
|
||||
`ffmpeg -y -i "${fullPath}" -vf "scale=320:-1"${needsQuality ? ' -q:v 5' : ''} "${absThumb}"`,
|
||||
// Daha toleranslı yöntem
|
||||
`ffmpeg -y -i "${fullPath}" -vf "scale=320:-1"${needsQuality ? ' -q:v 7' : ''} -err_detect ignore_err "${absThumb}"`,
|
||||
// En basit yöntem
|
||||
`ffmpeg -y -i "${fullPath}" -vf "scale=320:-1" "${absThumb}"`
|
||||
];
|
||||
|
||||
const cmd = `ffmpeg -y -i "${fullPath}" -vf "scale=320:-1"${qualityArgs} "${absThumb}"`;
|
||||
exec(cmd, (err) => {
|
||||
const strategyIndex = Math.min(retryCount, strategies.length - 1);
|
||||
const cmd = strategies[strategyIndex];
|
||||
|
||||
exec(cmd, (err, stdout, stderr) => {
|
||||
markGenerating(absThumb, false);
|
||||
|
||||
if (err) {
|
||||
console.warn(`⚠️ Resim thumbnail oluşturulamadı (${fullPath}): ${err.message}`);
|
||||
// Hata durumunda yeniden dene
|
||||
if (retryCount < strategies.length - 1) {
|
||||
console.warn(`⚠️ Resim thumbnail oluşturulamadı (${fullPath}): ${err.message}. Yeniden deneme (${retryCount + 1}/${strategies.length - 1})`);
|
||||
setTimeout(() => queueImageThumbnail(fullPath, relPath, retryCount + 1), 1000 * (retryCount + 1));
|
||||
return;
|
||||
}
|
||||
|
||||
// Tüm denemeler başarısız oldu
|
||||
console.error(`❌ Resim thumbnail oluşturulamadı (${fullPath}): ${err.message}`);
|
||||
if (stderr) {
|
||||
console.error(`🔍 ffmpeg stderr: ${stderr}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`🖼️ Resim thumbnail oluşturuldu: ${absThumb}`);
|
||||
const root = rootFromRelPath(relPath);
|
||||
if (root) broadcastFileUpdate(root);
|
||||
@@ -6085,8 +6195,129 @@ app.patch("/api/folder", requireAuth, (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// --- 📋 Dosya/klasör kopyalama endpoint'i ---
|
||||
app.post("/api/file/copy", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { sourcePath, targetDirectory } = req.body || {};
|
||||
if (!sourcePath) {
|
||||
return res.status(400).json({ error: "sourcePath gerekli" });
|
||||
}
|
||||
|
||||
const normalizedSource = normalizeTrashPath(sourcePath);
|
||||
if (!normalizedSource) {
|
||||
return res.status(400).json({ error: "Geçersiz sourcePath" });
|
||||
}
|
||||
|
||||
const sourceFullPath = path.join(DOWNLOAD_DIR, normalizedSource);
|
||||
if (!fs.existsSync(sourceFullPath)) {
|
||||
return res.status(404).json({ error: "Kaynak öğe bulunamadı" });
|
||||
}
|
||||
|
||||
const sourceStats = fs.statSync(sourceFullPath);
|
||||
const isDirectory = sourceStats.isDirectory();
|
||||
|
||||
const normalizedTargetDir = targetDirectory
|
||||
? normalizeTrashPath(targetDirectory)
|
||||
: "";
|
||||
|
||||
if (normalizedTargetDir) {
|
||||
const targetDirFullPath = path.join(DOWNLOAD_DIR, normalizedTargetDir);
|
||||
if (!fs.existsSync(targetDirFullPath)) {
|
||||
return res.status(404).json({ error: "Hedef klasör bulunamadı" });
|
||||
}
|
||||
}
|
||||
|
||||
const posixPath = path.posix;
|
||||
const sourceName = posixPath.basename(normalizedSource);
|
||||
const newRelativePath = normalizedTargetDir
|
||||
? posixPath.join(normalizedTargetDir, sourceName)
|
||||
: sourceName;
|
||||
|
||||
if (newRelativePath === normalizedSource) {
|
||||
return res.json({ success: true, unchanged: true });
|
||||
}
|
||||
|
||||
const newFullPath = path.join(DOWNLOAD_DIR, newRelativePath);
|
||||
if (fs.existsSync(newFullPath)) {
|
||||
return res
|
||||
.status(409)
|
||||
.json({ error: "Hedef konumda aynı isimde bir öğe zaten var" });
|
||||
}
|
||||
|
||||
const destinationParent = path.dirname(newFullPath);
|
||||
if (!fs.existsSync(destinationParent)) {
|
||||
fs.mkdirSync(destinationParent, { recursive: true });
|
||||
}
|
||||
|
||||
// Kopyalama işlemi
|
||||
try {
|
||||
copyFolderRecursiveSync(sourceFullPath, newFullPath);
|
||||
} catch (copyErr) {
|
||||
console.error("❌ Copy error:", copyErr);
|
||||
return res.status(500).json({
|
||||
error: "Kopyalama işlemi başarısız: " + copyErr.message
|
||||
});
|
||||
}
|
||||
|
||||
const sourceRoot = rootFromRelPath(normalizedSource);
|
||||
const destRoot = rootFromRelPath(newRelativePath);
|
||||
|
||||
// Kopyalanan öğe için yeni thumbnails oluştur
|
||||
if (!isDirectory) {
|
||||
const mimeType = mime.lookup(newFullPath) || "";
|
||||
if (mimeType.startsWith("video/")) {
|
||||
queueVideoThumbnail(newFullPath, newRelativePath);
|
||||
} else if (mimeType.startsWith("image/")) {
|
||||
queueImageThumbnail(newFullPath, newRelativePath);
|
||||
}
|
||||
}
|
||||
|
||||
// Hedef root için file update bildirimi gönder
|
||||
if (destRoot) {
|
||||
broadcastFileUpdate(destRoot);
|
||||
}
|
||||
if (sourceRoot && sourceRoot !== destRoot) {
|
||||
broadcastFileUpdate(sourceRoot);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`📋 Öğe kopyalandı: ${normalizedSource} -> ${newRelativePath}`
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
newPath: newRelativePath,
|
||||
rootFolder: destRoot || null,
|
||||
isDirectory,
|
||||
copied: true
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("❌ File copy error:", err);
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Recursive klasör kopyalama fonksiyonu
|
||||
function copyFolderRecursiveSync(source, target) {
|
||||
// Kaynak öğenin istatistiklerini al
|
||||
const stats = fs.statSync(source);
|
||||
|
||||
// Eğer kaynak bir dosyaysa, direkt kopyala
|
||||
if (stats.isFile()) {
|
||||
// Hedef dizinin var olduğundan emin ol
|
||||
const targetDir = path.dirname(target);
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
fs.mkdirSync(targetDir, { recursive: true });
|
||||
}
|
||||
fs.copyFileSync(source, target);
|
||||
return;
|
||||
}
|
||||
|
||||
// Kaynak bir klasörse
|
||||
if (!stats.isDirectory()) {
|
||||
throw new Error(`Kaynak ne dosya ne de klasör: ${source}`);
|
||||
}
|
||||
|
||||
// Hedef klasörü oluştur
|
||||
if (!fs.existsSync(target)) {
|
||||
fs.mkdirSync(target, { recursive: true });
|
||||
@@ -6101,9 +6332,9 @@ function copyFolderRecursiveSync(source, target) {
|
||||
const targetPath = path.join(target, file);
|
||||
|
||||
// Dosya istatistiklerini al
|
||||
const stats = fs.statSync(sourcePath);
|
||||
const itemStats = fs.statSync(sourcePath);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
if (itemStats.isDirectory()) {
|
||||
// Alt klasörse recursive olarak kopyala
|
||||
copyFolderRecursiveSync(sourcePath, targetPath);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user