fix(client-server): rclone ve miniplayer düzeltmeleri
This commit is contained in:
@@ -1,10 +1,23 @@
|
||||
<script>
|
||||
import { onMount, afterUpdate, tick } from "svelte";
|
||||
import { musicPlayer, togglePlay, playNext, playPrevious, stopPlayback } from "../stores/musicPlayerStore.js";
|
||||
import { useLocation } from "svelte-routing";
|
||||
import { API, withToken } from "../utils/api.js";
|
||||
import { cleanFileName } from "../utils/filename.js";
|
||||
|
||||
let videoEl = null;
|
||||
let titleWrap = null;
|
||||
let titleInner = null;
|
||||
let marqueeShift = "0px";
|
||||
let marqueeDuration = "0s";
|
||||
let marqueeEnabled = false;
|
||||
let dragX = 0;
|
||||
let dragY = 0;
|
||||
let dragging = false;
|
||||
let dragStartX = 0;
|
||||
let dragStartY = 0;
|
||||
let originX = 0;
|
||||
let originY = 0;
|
||||
const location = useLocation();
|
||||
$: isMusicRoute = ($location?.pathname || "").startsWith("/music");
|
||||
|
||||
@@ -39,22 +52,96 @@
|
||||
return withToken(`${API}/stream/${item.infoHash}?index=${index}`);
|
||||
}
|
||||
|
||||
const HARD_SYNC_THRESHOLD = 0.2;
|
||||
const SOFT_SYNC_THRESHOLD = 0.05;
|
||||
|
||||
$: if (videoEl && $musicPlayer.currentTrack && $musicPlayer.isPlaying) {
|
||||
const target = $musicPlayer.currentTime || 0;
|
||||
if (Number.isFinite(target) && Math.abs(videoEl.currentTime - target) > 0.6) {
|
||||
if (!Number.isFinite(target)) {
|
||||
videoEl.playbackRate = 1;
|
||||
} else {
|
||||
const delta = target - (videoEl.currentTime || 0);
|
||||
if (Math.abs(delta) > HARD_SYNC_THRESHOLD) {
|
||||
videoEl.currentTime = target;
|
||||
} else if (Math.abs(delta) > SOFT_SYNC_THRESHOLD) {
|
||||
videoEl.playbackRate = delta > 0 ? 1.02 : 0.98;
|
||||
} else {
|
||||
videoEl.playbackRate = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function updateMarquee() {
|
||||
if (!titleWrap || !titleInner) return;
|
||||
await tick();
|
||||
const wrapW = titleWrap.clientWidth || 0;
|
||||
const textW = titleInner.scrollWidth || 0;
|
||||
if (textW > wrapW + 2) {
|
||||
const shift = Math.max(textW - wrapW, 0);
|
||||
marqueeShift = `${shift}px`;
|
||||
marqueeDuration = `${Math.max(8, shift / 25)}s`;
|
||||
marqueeEnabled = true;
|
||||
} else {
|
||||
marqueeShift = "0px";
|
||||
marqueeDuration = "0s";
|
||||
marqueeEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
updateMarquee();
|
||||
});
|
||||
|
||||
afterUpdate(() => {
|
||||
updateMarquee();
|
||||
});
|
||||
|
||||
$: if ($musicPlayer.currentTrack?.id) {
|
||||
setTimeout(updateMarquee, 0);
|
||||
}
|
||||
|
||||
|
||||
$: if (videoEl && $musicPlayer.isPlaying) {
|
||||
videoEl.play().catch(() => undefined);
|
||||
} else if (videoEl) {
|
||||
videoEl.pause();
|
||||
}
|
||||
|
||||
function shouldIgnoreDrag(target) {
|
||||
if (!target) return false;
|
||||
return Boolean(target.closest("button, a, input, textarea, select, [data-no-drag]"));
|
||||
}
|
||||
|
||||
function startDrag(event) {
|
||||
if (shouldIgnoreDrag(event.target)) return;
|
||||
dragging = true;
|
||||
dragStartX = event.clientX;
|
||||
dragStartY = event.clientY;
|
||||
originX = dragX;
|
||||
originY = dragY;
|
||||
window.addEventListener("pointermove", onDrag);
|
||||
window.addEventListener("pointerup", endDrag, { once: true });
|
||||
}
|
||||
|
||||
function onDrag(event) {
|
||||
if (!dragging) return;
|
||||
dragX = originX + (event.clientX - dragStartX);
|
||||
dragY = originY + (event.clientY - dragStartY);
|
||||
}
|
||||
|
||||
function endDrag() {
|
||||
dragging = false;
|
||||
window.removeEventListener("pointermove", onDrag);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $musicPlayer.currentTrack && !isMusicRoute}
|
||||
<div class="mini-player" aria-label="Mini music player">
|
||||
<div
|
||||
class="mini-player {dragging ? 'dragging' : ''}"
|
||||
aria-label="Mini music player"
|
||||
on:pointerdown={startDrag}
|
||||
style="transform: translate({dragX}px, {dragY}px);"
|
||||
>
|
||||
<div class="mini-top"></div>
|
||||
|
||||
<div class="mini-cover">
|
||||
@@ -78,8 +165,16 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div class="mini-meta">
|
||||
<div class="mini-user-name">
|
||||
<div class="mini-user-name" bind:this={titleWrap}>
|
||||
{#key $musicPlayer.currentTrack?.id}
|
||||
<span
|
||||
class="marquee {marqueeEnabled ? 'active' : ''}"
|
||||
bind:this={titleInner}
|
||||
style="--marquee-shift: {marqueeShift}; --marquee-duration: {marqueeDuration};"
|
||||
>
|
||||
{cleanFileName($musicPlayer.currentTrack.title)}
|
||||
</span>
|
||||
{/key}
|
||||
</div>
|
||||
<div class="mini-user-handle">{sourceLabel($musicPlayer.currentTrack)}</div>
|
||||
</div>
|
||||
@@ -98,20 +193,20 @@
|
||||
</div>
|
||||
|
||||
<div class="mini-controls">
|
||||
<button class="mini-btn" on:click={playPrevious} title="Önceki">
|
||||
<button class="mini-btn" on:click={playPrevious} title="Önceki" data-no-drag>
|
||||
<i class="fa-solid fa-backward-step"></i>
|
||||
</button>
|
||||
<button class="mini-btn main" on:click={togglePlay} title="Oynat/Durdur">
|
||||
<button class="mini-btn main" on:click={togglePlay} title="Oynat/Durdur" data-no-drag>
|
||||
{#if $musicPlayer.isPlaying}
|
||||
<i class="fa-solid fa-pause"></i>
|
||||
{:else}
|
||||
<i class="fa-solid fa-play"></i>
|
||||
{/if}
|
||||
</button>
|
||||
<button class="mini-btn" on:click={playNext} title="Sonraki">
|
||||
<button class="mini-btn" on:click={playNext} title="Sonraki" data-no-drag>
|
||||
<i class="fa-solid fa-forward-step"></i>
|
||||
</button>
|
||||
<button class="mini-btn ghost" on:click={stopPlayback} title="Kapat">
|
||||
<button class="mini-btn ghost" on:click={stopPlayback} title="Kapat" data-no-drag>
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -132,6 +227,12 @@
|
||||
color: #f6f6f6;
|
||||
backdrop-filter: blur(14px);
|
||||
z-index: 50;
|
||||
cursor: grab;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.mini-player.dragging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.mini-top {
|
||||
@@ -140,7 +241,7 @@
|
||||
|
||||
.mini-meta {
|
||||
text-align: left;
|
||||
margin: 0 4px 8px;
|
||||
margin: 0 4px 8px -4px;
|
||||
}
|
||||
|
||||
.mini-user-name {
|
||||
@@ -149,6 +250,19 @@
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mini-user-name .marquee {
|
||||
display: inline-block;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.mini-user-name .marquee.active {
|
||||
animation: mini-marquee var(--marquee-duration) linear infinite;
|
||||
}
|
||||
|
||||
.mini-user-handle {
|
||||
@@ -156,6 +270,37 @@
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
@keyframes mini-marquee {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
10% {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
60% {
|
||||
transform: translateX(calc(-1 * var(--marquee-shift)));
|
||||
opacity: 1;
|
||||
}
|
||||
80% {
|
||||
transform: translateX(calc(-1 * var(--marquee-shift)));
|
||||
opacity: 1;
|
||||
}
|
||||
84% {
|
||||
transform: translateX(calc(-1 * var(--marquee-shift)));
|
||||
opacity: 0;
|
||||
}
|
||||
86% {
|
||||
transform: translateX(0);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.mini-cover {
|
||||
margin: -16px -16px 12px;
|
||||
width: calc(100% + 32px);
|
||||
@@ -164,7 +309,7 @@
|
||||
overflow: hidden;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
box-shadow: 0 18px 30px rgba(0, 0, 0, 0.35);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
@@ -626,6 +626,25 @@
|
||||
alert(err?.message || "GDrive taşıma başarısız oldu.");
|
||||
}
|
||||
}
|
||||
|
||||
async function setCategory(entry, category) {
|
||||
if (!entry?.name) return;
|
||||
try {
|
||||
const resp = await apiFetch("/api/file/category", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ path: entry.name, category })
|
||||
});
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
if (!resp.ok || !data?.ok) {
|
||||
alert(data?.error || "Kategori güncellenemedi.");
|
||||
return;
|
||||
}
|
||||
await loadFiles();
|
||||
} catch (err) {
|
||||
alert(err?.message || "Kategori güncellenemedi.");
|
||||
}
|
||||
}
|
||||
function formatSize(bytes) {
|
||||
if (!bytes) return "0 MB";
|
||||
if (bytes < 1e6) return (bytes / 1e3).toFixed(1) + " KB";
|
||||
@@ -1301,12 +1320,18 @@
|
||||
}
|
||||
|
||||
function matchFile(file) {
|
||||
try {
|
||||
if (!file || file.isDirectory) {
|
||||
closeMenu();
|
||||
return;
|
||||
}
|
||||
// Dosya adını al (path'in son kısmı)
|
||||
const fileName = file.name.split('/').pop();
|
||||
const rawName = file?.name || file?.displayName || "";
|
||||
const fileName = rawName.split("/").pop();
|
||||
if (!fileName) {
|
||||
showToast("Eşleştirilecek dosya adı bulunamadı.", "error");
|
||||
closeMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
// Önce dizi kontrolü yap (SxxExx formatı)
|
||||
const seriesMatch = fileName.match(/S(\d{1,2})E(\d{1,2})/i);
|
||||
@@ -1340,6 +1365,10 @@
|
||||
tick().then(() => {
|
||||
searchMetadata();
|
||||
});
|
||||
} catch (err) {
|
||||
showToast("Eşleştirme penceresi açılamadı.", "error");
|
||||
closeMenu();
|
||||
}
|
||||
}
|
||||
|
||||
function closeMatchModal() {
|
||||
@@ -1370,6 +1399,10 @@
|
||||
params.set("year", matchYear);
|
||||
}
|
||||
|
||||
const token = localStorage.getItem("token");
|
||||
if (token) {
|
||||
params.set("token", token);
|
||||
}
|
||||
const response = await apiFetch(`/api/search/metadata?${params}`);
|
||||
|
||||
if (response.ok) {
|
||||
@@ -1486,8 +1519,6 @@
|
||||
function closeMenu() {
|
||||
activeMenu = null;
|
||||
deleteConfirmPending = false;
|
||||
showMatchModal = false;
|
||||
matchingFile = null;
|
||||
}
|
||||
|
||||
// Klasör oluşturma fonksiyonları
|
||||
@@ -2395,6 +2426,20 @@
|
||||
<i class="fa-solid fa-wand-magic-sparkles"></i>
|
||||
<span>Eşleştir</span>
|
||||
</button>
|
||||
<button
|
||||
class="menu-item"
|
||||
on:click|stopPropagation={() => setCategory(activeMenu, "music")}
|
||||
>
|
||||
<i class="fa-solid fa-music"></i>
|
||||
<span>Kategori: Müzik</span>
|
||||
</button>
|
||||
<button
|
||||
class="menu-item"
|
||||
on:click|stopPropagation={() => setCategory(activeMenu, "auto")}
|
||||
>
|
||||
<i class="fa-solid fa-rotate-left"></i>
|
||||
<span>Kategori: Otomatik</span>
|
||||
</button>
|
||||
<div class="menu-divider"></div>
|
||||
<button
|
||||
class="menu-item"
|
||||
@@ -3247,12 +3292,14 @@
|
||||
.name {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
line-height: 1.3;
|
||||
max-height: calc(1.3em * 2);
|
||||
min-height: calc(1.3em * 2);
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
}
|
||||
@@ -3823,7 +3870,8 @@
|
||||
.folder-info {
|
||||
margin-top: 4px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
text-align: left;
|
||||
padding: 0 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.folder-name {
|
||||
@@ -3835,8 +3883,9 @@
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
text-align: center;
|
||||
text-align: left;
|
||||
max-height: calc(1.25em * 2);
|
||||
min-height: calc(1.25em * 2);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@@ -241,8 +241,10 @@
|
||||
throw new Error(data?.error || `HTTP ${resp.status}`);
|
||||
}
|
||||
showToast("Cache temizlendi.", "success");
|
||||
await loadRcloneStatus();
|
||||
} catch (err) {
|
||||
error = err?.message || "Cache temizlenemedi.";
|
||||
showToast(error, "error");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@ export function showToast(message, type = 'success', duration = 3000) {
|
||||
}
|
||||
|
||||
// Yeni toast'ı göster
|
||||
toast.update({ message, type, visible: true });
|
||||
toast.set({ message, type, visible: true });
|
||||
|
||||
// Belirli süre sonra gizle
|
||||
toastTimeout = setTimeout(() => {
|
||||
toast.update({ message: null, type: 'success', visible: false });
|
||||
toast.set({ message: null, type: 'success', visible: false });
|
||||
}, duration);
|
||||
}
|
||||
|
||||
@@ -723,6 +723,16 @@ function readInfoForRoot(rootFolder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function getInfoPathForRoot(rootFolder) {
|
||||
const safe = sanitizeRelative(rootFolder);
|
||||
if (!safe) return null;
|
||||
const localPath = path.join(DOWNLOAD_DIR, safe, INFO_FILENAME);
|
||||
if (fs.existsSync(localPath)) return localPath;
|
||||
const gdrivePath = path.join(GDRIVE_ROOT, safe, INFO_FILENAME);
|
||||
if (fs.existsSync(gdrivePath)) return gdrivePath;
|
||||
return localPath;
|
||||
}
|
||||
|
||||
function sanitizeRelative(relPath) {
|
||||
return relPath.replace(/^[\\/]+/, "");
|
||||
}
|
||||
@@ -1109,7 +1119,27 @@ async function runRcloneCacheClean() {
|
||||
const body = await resp.text();
|
||||
return { ok: false, error: `Rclone RC hata: ${body || resp.status}` };
|
||||
}
|
||||
return { ok: true, method: "rc", restarted: false };
|
||||
// RC refresh sonrası cache dizinini temizle (mount düşmeden)
|
||||
try {
|
||||
if (fs.existsSync(RCLONE_VFS_CACHE_DIR)) {
|
||||
const entries = fs.readdirSync(RCLONE_VFS_CACHE_DIR);
|
||||
for (const entry of entries) {
|
||||
const target = path.join(RCLONE_VFS_CACHE_DIR, entry);
|
||||
fs.rmSync(target, { recursive: true, force: true });
|
||||
}
|
||||
} else {
|
||||
fs.mkdirSync(RCLONE_VFS_CACHE_DIR, { recursive: true });
|
||||
}
|
||||
} catch (cleanupErr) {
|
||||
return {
|
||||
ok: false,
|
||||
error: cleanupErr?.message || String(cleanupErr)
|
||||
};
|
||||
}
|
||||
const remaining = fs.existsSync(RCLONE_VFS_CACHE_DIR)
|
||||
? fs.readdirSync(RCLONE_VFS_CACHE_DIR).length
|
||||
: 0;
|
||||
return { ok: true, method: "rc+fs", restarted: false, remaining };
|
||||
}
|
||||
|
||||
if (wasRunning && !RCLONE_RC_ENABLED) {
|
||||
@@ -1119,7 +1149,8 @@ async function runRcloneCacheClean() {
|
||||
// RC kapalıysa dosya sisteminden temizle
|
||||
fs.rmSync(RCLONE_VFS_CACHE_DIR, { recursive: true, force: true });
|
||||
fs.mkdirSync(RCLONE_VFS_CACHE_DIR, { recursive: true });
|
||||
return { ok: true, method: "fs", restarted: false };
|
||||
const remaining = fs.readdirSync(RCLONE_VFS_CACHE_DIR).length;
|
||||
return { ok: true, method: "fs", restarted: false, remaining };
|
||||
} catch (err) {
|
||||
return { ok: false, error: err?.message || String(err) };
|
||||
}
|
||||
@@ -1294,6 +1325,10 @@ function startRcloneMount(settings) {
|
||||
rcloneProcess.stderr.on("data", (data) => {
|
||||
const msg = data.toString().trim();
|
||||
if (msg) {
|
||||
if (msg.includes("Dir.Remove not empty")) {
|
||||
console.info(`ℹ️ rclone: ${msg}`);
|
||||
return;
|
||||
}
|
||||
rcloneLastLogMessage = msg;
|
||||
// NOTICE ve INFO seviyesindeki loglar hata değil
|
||||
// Sadece ERROR, FATAL, CRITICAL seviyesindekileri "son hata" olarak işaretle
|
||||
@@ -5934,7 +5969,8 @@ function writeInfoForRoot(rootFolder, info) {
|
||||
if (!rootFolder || !info) return;
|
||||
const safe = sanitizeRelative(rootFolder);
|
||||
if (!safe) return;
|
||||
const target = path.join(DOWNLOAD_DIR, safe, INFO_FILENAME);
|
||||
const target = getInfoPathForRoot(safe);
|
||||
if (!target) return;
|
||||
try {
|
||||
info.updatedAt = Date.now();
|
||||
fs.writeFileSync(target, JSON.stringify(info, null, 2), "utf-8");
|
||||
@@ -8319,7 +8355,8 @@ app.get("/api/files", requireAuth, (req, res) => {
|
||||
const seriesEpisodeInfo = relWithinRoot
|
||||
? info.seriesEpisodes?.[relWithinRoot] || null
|
||||
: null;
|
||||
let mediaCategory = fileMeta?.type || null;
|
||||
let mediaCategory =
|
||||
fileMeta?.categoryOverride || fileMeta?.type || null;
|
||||
if (!mediaCategory) {
|
||||
const canInheritFromInfo = !relWithinRoot || isVideo;
|
||||
if (canInheritFromInfo && info.type) {
|
||||
@@ -8370,6 +8407,45 @@ app.get("/api/files", requireAuth, (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// --- 🏷️ Dosya kategori override ---
|
||||
app.post("/api/file/category", requireAuth, (req, res) => {
|
||||
try {
|
||||
const relPath = sanitizeRelative(String(req.body?.path || ""));
|
||||
const category = String(req.body?.category || "").toLowerCase();
|
||||
if (!relPath) return res.status(400).json({ error: "Geçersiz yol" });
|
||||
if (!category) return res.status(400).json({ error: "Geçersiz kategori" });
|
||||
const rootFolder = rootFromRelPath(relPath);
|
||||
if (!rootFolder) return res.status(400).json({ error: "Kök bulunamadı" });
|
||||
const info = readInfoForRoot(rootFolder);
|
||||
if (!info) return res.status(404).json({ error: "info.json bulunamadı" });
|
||||
const segments = relPathToSegments(relPath);
|
||||
const relWithinRoot = segments.slice(1).join("/");
|
||||
|
||||
if (!info.files) info.files = {};
|
||||
if (!relWithinRoot) {
|
||||
if (category === "auto") delete info.type;
|
||||
else info.type = category;
|
||||
writeInfoForRoot(rootFolder, info);
|
||||
return res.json({ ok: true });
|
||||
}
|
||||
|
||||
if (!info.files[relWithinRoot]) {
|
||||
return res.status(404).json({ error: "Dosya kaydı bulunamadı" });
|
||||
}
|
||||
|
||||
if (category === "auto") {
|
||||
delete info.files[relWithinRoot].categoryOverride;
|
||||
} else {
|
||||
info.files[relWithinRoot].categoryOverride = category;
|
||||
}
|
||||
writeInfoForRoot(rootFolder, info);
|
||||
return res.json({ ok: true });
|
||||
} catch (err) {
|
||||
console.error("❌ Kategori güncelleme hatası:", err);
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// --- 🗑️ Çöp listesi API (.trash flag sistemi) ---
|
||||
app.get("/api/trash", requireAuth, (req, res) => {
|
||||
try {
|
||||
@@ -10338,18 +10414,25 @@ function collectMusicEntries() {
|
||||
if (!fileKeys.length) continue;
|
||||
|
||||
let targetPath = info.primaryVideoPath;
|
||||
if (targetPath && files[targetPath]?.type !== "music") {
|
||||
const primaryMeta = targetPath ? files[targetPath] : null;
|
||||
const primaryCategory =
|
||||
primaryMeta?.categoryOverride || primaryMeta?.type || null;
|
||||
if (targetPath && primaryCategory !== "music") {
|
||||
targetPath = null;
|
||||
}
|
||||
if (!targetPath) {
|
||||
targetPath =
|
||||
fileKeys.find((key) => files[key]?.type === "music") || fileKeys[0];
|
||||
fileKeys.find(
|
||||
(key) =>
|
||||
(files[key]?.categoryOverride || files[key]?.type) === "music"
|
||||
) || fileKeys[0];
|
||||
}
|
||||
if (!targetPath) continue;
|
||||
const fileMeta = files[targetPath];
|
||||
// Hedef dosya çöpteyse atla
|
||||
if (isPathTrashed(folder, targetPath, false)) continue;
|
||||
const mediaType = fileMeta?.type || info.type || null;
|
||||
const mediaType =
|
||||
fileMeta?.categoryOverride || fileMeta?.type || info.type || null;
|
||||
if (mediaType !== "music") continue;
|
||||
const absMusic = resolveStoragePath(`${folder}/${targetPath}`);
|
||||
if (!absMusic) continue;
|
||||
|
||||
Reference in New Issue
Block a user