kes-kopyala-yapıştır eklendi
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount, tick } from "svelte";
|
import { onMount, tick } from "svelte";
|
||||||
import { API, apiFetch, moveEntry, renameFolder } from "../utils/api.js";
|
import { API, apiFetch, moveEntry, renameFolder, copyEntry } from "../utils/api.js";
|
||||||
import { cleanFileName, extractTitleAndYear } from "../utils/filename.js";
|
import { cleanFileName, extractTitleAndYear } from "../utils/filename.js";
|
||||||
import { refreshMovieCount } from "../stores/movieStore.js";
|
import { refreshMovieCount } from "../stores/movieStore.js";
|
||||||
import { refreshTvShowCount } from "../stores/tvStore.js";
|
import { refreshTvShowCount } from "../stores/tvStore.js";
|
||||||
@@ -453,6 +453,10 @@
|
|||||||
let breadcrumbMenuPosition = { top: 0, left: 0 };
|
let breadcrumbMenuPosition = { top: 0, left: 0 };
|
||||||
let hiddenBreadcrumbs = [];
|
let hiddenBreadcrumbs = [];
|
||||||
|
|
||||||
|
// Clipboard state
|
||||||
|
let clipboardItem = null;
|
||||||
|
let clipboardOperation = null; // 'cut' veya 'copy'
|
||||||
|
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const pathParam = params.get("path");
|
const pathParam = params.get("path");
|
||||||
@@ -1542,6 +1546,81 @@
|
|||||||
cancelRenameFolder();
|
cancelRenameFolder();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clipboard fonksiyonları
|
||||||
|
function cutFile(item) {
|
||||||
|
if (!item) return;
|
||||||
|
clipboardItem = item;
|
||||||
|
clipboardOperation = 'cut';
|
||||||
|
closeMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyFile(item) {
|
||||||
|
if (!item) return;
|
||||||
|
clipboardItem = item;
|
||||||
|
clipboardOperation = 'copy';
|
||||||
|
closeMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pasteFile() {
|
||||||
|
if (!clipboardItem || !clipboardOperation) return;
|
||||||
|
|
||||||
|
const sourcePath = resolveEntryOriginalPath(clipboardItem);
|
||||||
|
const targetPath = resolveOriginalPathForDisplay(currentPath, currentOriginalPath);
|
||||||
|
|
||||||
|
if (!sourcePath || !targetPath) {
|
||||||
|
alert("Geçersiz kaynak veya hedef yolu");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedSource = normalizePath(sourcePath);
|
||||||
|
const normalizedTarget = normalizePath(targetPath);
|
||||||
|
|
||||||
|
if (!normalizedSource || !normalizedTarget) {
|
||||||
|
alert("Geçersiz kaynak veya hedef yolu");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aynı konuma yapıştırmayı engelle
|
||||||
|
const sourceParent = normalizedSource.split("/").slice(0, -1).join("/");
|
||||||
|
if (sourceParent === normalizedTarget && clipboardOperation === 'cut') {
|
||||||
|
alert("Öğe zaten bu konumda");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let result;
|
||||||
|
if (clipboardOperation === 'cut') {
|
||||||
|
// Kes işlemi - moveEntry kullan
|
||||||
|
result = await moveEntry(normalizedSource, normalizedTarget);
|
||||||
|
} else {
|
||||||
|
// Kopyala işlemi - copyEntry kullan
|
||||||
|
result = await copyEntry(normalizedSource, normalizedTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result?.success) {
|
||||||
|
const message = result?.error || "İşlem başarısız oldu";
|
||||||
|
alert(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result?.unchanged) {
|
||||||
|
await loadFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kes işleminden sonra clipboard'u temizle
|
||||||
|
if (clipboardOperation === 'cut') {
|
||||||
|
clipboardItem = null;
|
||||||
|
clipboardOperation = null;
|
||||||
|
}
|
||||||
|
// Kopyala işleminde clipboard'u koru (tekrar yapıştırmaya izin ver)
|
||||||
|
|
||||||
|
selectedItems = new Set();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("❌ Yapıştırma hatası:", err);
|
||||||
|
alert("İşlem tamamlanamadı. Lütfen tekrar dene.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
setSearchScope("files");
|
setSearchScope("files");
|
||||||
@@ -1913,6 +1992,17 @@
|
|||||||
<i class="fa-solid fa-square-check"></i>
|
<i class="fa-solid fa-square-check"></i>
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if clipboardItem && clipboardOperation}
|
||||||
|
<button
|
||||||
|
class="paste-btn"
|
||||||
|
type="button"
|
||||||
|
on:click|stopPropagation={pasteFile}
|
||||||
|
aria-label="Yapıştır"
|
||||||
|
title={clipboardOperation === 'cut' ? 'Kes ve yapıştır' : 'Kopyala ve yapıştır'}
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-paste"></i>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
<button
|
<button
|
||||||
class="create-folder-btn"
|
class="create-folder-btn"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -2154,6 +2244,21 @@
|
|||||||
<span>Yeniden adlandır</span>
|
<span>Yeniden adlandır</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="menu-divider"></div>
|
<div class="menu-divider"></div>
|
||||||
|
<button
|
||||||
|
class="menu-item"
|
||||||
|
on:click|stopPropagation={() => cutFile(activeMenu)}
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-scissors"></i>
|
||||||
|
<span>Kes</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="menu-item"
|
||||||
|
on:click|stopPropagation={() => copyFile(activeMenu)}
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-copy"></i>
|
||||||
|
<span>Kopyala</span>
|
||||||
|
</button>
|
||||||
|
<div class="menu-divider"></div>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
class="menu-item"
|
class="menu-item"
|
||||||
@@ -2170,6 +2275,21 @@
|
|||||||
<span>Eşleştir</span>
|
<span>Eşleştir</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="menu-divider"></div>
|
<div class="menu-divider"></div>
|
||||||
|
<button
|
||||||
|
class="menu-item"
|
||||||
|
on:click|stopPropagation={() => cutFile(activeMenu)}
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-scissors"></i>
|
||||||
|
<span>Kes</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="menu-item"
|
||||||
|
on:click|stopPropagation={() => copyFile(activeMenu)}
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-copy"></i>
|
||||||
|
<span>Kopyala</span>
|
||||||
|
</button>
|
||||||
|
<div class="menu-divider"></div>
|
||||||
{/if}
|
{/if}
|
||||||
<button
|
<button
|
||||||
class="menu-item delete"
|
class="menu-item delete"
|
||||||
@@ -2831,6 +2951,33 @@
|
|||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.paste-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
color: #666;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 36px;
|
||||||
|
width: 36px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paste-btn:hover {
|
||||||
|
background: #4caf50;
|
||||||
|
border-color: #45a049;
|
||||||
|
color: #fff;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.paste-btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
/* Klasör oluşturma UI elemanları */
|
/* Klasör oluşturma UI elemanları */
|
||||||
.creating-folder {
|
.creating-folder {
|
||||||
background: rgba(245, 179, 51, 0.1);
|
background: rgba(245, 179, 51, 0.1);
|
||||||
@@ -3074,6 +3221,9 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
min-height: 20px; /* Dosya isimleri için tutarlı yükseklik */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
.size {
|
.size {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -3475,6 +3625,9 @@
|
|||||||
}
|
}
|
||||||
.media-card.list-view .name {
|
.media-card.list-view .name {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
min-height: 22px; /* Liste görünümündeki dosya isimleri için tutarlı yükseklik */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
.media-card.list-view .size {
|
.media-card.list-view .size {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -3650,6 +3803,11 @@
|
|||||||
color: #2d2d2d;
|
color: #2d2d2d;
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
min-height: 40px; /* Tek ve çok satırlı isimler için aynı yükseklik */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.folder-rename-input {
|
.folder-rename-input {
|
||||||
@@ -3689,6 +3847,9 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
min-height: 24px; /* Liste görünümünde tutarlı yükseklik */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Menü düğmesi ve dropdown stilleri */
|
/* Menü düğmesi ve dropdown stilleri */
|
||||||
|
|||||||
@@ -64,3 +64,15 @@ export async function renameFolder(path, newName) {
|
|||||||
});
|
});
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function copyEntry(sourcePath, targetDirectory) {
|
||||||
|
const res = await apiFetch("/api/file/copy", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
sourcePath,
|
||||||
|
targetDirectory
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|||||||
269
server/server.js
269
server/server.js
@@ -517,20 +517,59 @@ function parseFrameRate(value) {
|
|||||||
return Number.isFinite(num) ? num : null;
|
return Number.isFinite(num) ? num : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function extractMediaInfo(filePath) {
|
async function extractMediaInfo(filePath, retryCount = 0) {
|
||||||
if (!filePath || !fs.existsSync(filePath)) return null;
|
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) => {
|
return new Promise((resolve) => {
|
||||||
exec(
|
exec(
|
||||||
`${FFPROBE_PATH} -v quiet -print_format json -show_format -show_streams "${filePath}"`,
|
cmd,
|
||||||
{ maxBuffer: FFPROBE_MAX_BUFFER },
|
{ maxBuffer: FFPROBE_MAX_BUFFER },
|
||||||
(err, stdout) => {
|
(err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.warn(
|
// Hata durumunda yeniden dene
|
||||||
`⚠️ ffprobe çalıştırılamadı (${filePath}): ${err.message}`
|
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);
|
return resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(stdout);
|
const parsed = JSON.parse(stdout);
|
||||||
const streams = Array.isArray(parsed?.streams) ? parsed.streams : [];
|
const streams = Array.isArray(parsed?.streams) ? parsed.streams : [];
|
||||||
@@ -577,8 +616,17 @@ async function extractMediaInfo(filePath) {
|
|||||||
|
|
||||||
resolve(mediaInfo);
|
resolve(mediaInfo);
|
||||||
} catch (parseErr) {
|
} catch (parseErr) {
|
||||||
console.warn(
|
// Parse hatası durumunda yeniden dene
|
||||||
`⚠️ ffprobe çıktısı parse edilemedi (${filePath}): ${parseErr.message}`
|
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);
|
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);
|
const { relThumb, absThumb } = getVideoThumbnailPaths(relPath);
|
||||||
if (fs.existsSync(absThumb) || generatingThumbnails.has(absThumb)) return;
|
if (fs.existsSync(absThumb) || generatingThumbnails.has(absThumb)) return;
|
||||||
|
|
||||||
ensureDirForFile(absThumb);
|
ensureDirForFile(absThumb);
|
||||||
markGenerating(absThumb, true);
|
markGenerating(absThumb, true);
|
||||||
|
|
||||||
const cmd = `ffmpeg -y -ss ${VIDEO_THUMBNAIL_TIME} -i "${fullPath}" -frames:v 1 -vf "scale=320:-1" -q:v 2 "${absThumb}"`;
|
// Farklı ffmpeg stratejileri deneme
|
||||||
exec(cmd, (err) => {
|
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);
|
markGenerating(absThumb, false);
|
||||||
|
|
||||||
if (err) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`🎞️ Video thumbnail oluşturuldu: ${absThumb}`);
|
console.log(`🎞️ Video thumbnail oluşturuldu: ${absThumb}`);
|
||||||
const root = rootFromRelPath(relPath);
|
const root = rootFromRelPath(relPath);
|
||||||
if (root) broadcastFileUpdate(root);
|
if (root) broadcastFileUpdate(root);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function queueImageThumbnail(fullPath, relPath) {
|
function queueImageThumbnail(fullPath, relPath, retryCount = 0) {
|
||||||
const { relThumb, absThumb } = getImageThumbnailPaths(relPath);
|
const { relThumb, absThumb } = getImageThumbnailPaths(relPath);
|
||||||
if (fs.existsSync(absThumb) || generatingThumbnails.has(absThumb)) return;
|
if (fs.existsSync(absThumb) || generatingThumbnails.has(absThumb)) return;
|
||||||
|
|
||||||
@@ -616,15 +702,39 @@ function queueImageThumbnail(fullPath, relPath) {
|
|||||||
|
|
||||||
const outputExt = path.extname(absThumb).toLowerCase();
|
const outputExt = path.extname(absThumb).toLowerCase();
|
||||||
const needsQuality = outputExt === ".jpg" || outputExt === ".jpeg";
|
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}"`;
|
const strategyIndex = Math.min(retryCount, strategies.length - 1);
|
||||||
exec(cmd, (err) => {
|
const cmd = strategies[strategyIndex];
|
||||||
|
|
||||||
|
exec(cmd, (err, stdout, stderr) => {
|
||||||
markGenerating(absThumb, false);
|
markGenerating(absThumb, false);
|
||||||
|
|
||||||
if (err) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`🖼️ Resim thumbnail oluşturuldu: ${absThumb}`);
|
console.log(`🖼️ Resim thumbnail oluşturuldu: ${absThumb}`);
|
||||||
const root = rootFromRelPath(relPath);
|
const root = rootFromRelPath(relPath);
|
||||||
if (root) broadcastFileUpdate(root);
|
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
|
// Recursive klasör kopyalama fonksiyonu
|
||||||
function copyFolderRecursiveSync(source, target) {
|
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
|
// Hedef klasörü oluştur
|
||||||
if (!fs.existsSync(target)) {
|
if (!fs.existsSync(target)) {
|
||||||
fs.mkdirSync(target, { recursive: true });
|
fs.mkdirSync(target, { recursive: true });
|
||||||
@@ -6101,9 +6332,9 @@ function copyFolderRecursiveSync(source, target) {
|
|||||||
const targetPath = path.join(target, file);
|
const targetPath = path.join(target, file);
|
||||||
|
|
||||||
// Dosya istatistiklerini al
|
// 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
|
// Alt klasörse recursive olarak kopyala
|
||||||
copyFolderRecursiveSync(sourcePath, targetPath);
|
copyFolderRecursiveSync(sourcePath, targetPath);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user