TMDB Entegrasyonu

This commit is contained in:
2025-10-27 06:05:34 +03:00
parent 1760441ce7
commit 66ac562bcd
9 changed files with 2292 additions and 136 deletions

View File

@@ -2,18 +2,42 @@
import { onMount, tick } from "svelte";
import { API, apiFetch } from "../utils/api.js";
import { cleanFileName } from "../utils/filename.js";
import { refreshMovieCount } from "../stores/movieStore.js";
let files = [];
let showModal = false;
let selectedVideo = null;
let subtitleURL = null;
let subtitleLang = "en";
let subtitleLabel = "Custom Subtitles";
let viewMode = "grid";
let selectedItems = new Set();
let allSelected = false;
// 🎬 Player kontrolleri
let videoEl;
let isPlaying = false;
const VIEW_KEY = "filesViewMode";
let viewMode = "grid";
if (typeof window !== "undefined") {
const storedView = window.localStorage.getItem(VIEW_KEY);
if (storedView === "grid" || storedView === "list") {
viewMode = storedView;
}
}
let selectedItems = new Set();
let allSelected = false;
let pendingPlayTarget = null;
if (typeof window !== "undefined") {
const params = new URLSearchParams(window.location.search);
const playParam = params.get("play");
if (playParam) {
try {
pendingPlayTarget = decodeURIComponent(playParam);
} catch (err) {
pendingPlayTarget = playParam;
}
params.delete("play");
const search = params.toString();
const newUrl = `${window.location.pathname}${search ? `?${search}` : ""}${window.location.hash}`;
window.history.replaceState({}, "", newUrl);
}
}
// 🎬 Player kontrolleri
let videoEl;
let isPlaying = false;
let currentTime = 0;
let duration = 0;
let volume = 1;
@@ -39,6 +63,8 @@
[...selectedItems].filter((name) => existing.has(name)),
);
allSelected = files.length > 0 && selectedItems.size === files.length;
tryAutoPlay();
refreshMovieCount();
}
function formatSize(bytes) {
if (!bytes) return "0 MB";
@@ -52,8 +78,33 @@
if (Number.isNaN(date.getTime())) return "—";
return date.toLocaleString();
}
function formatVideoCodec(info) {
if (!info) return null;
const codec = info.codec ? info.codec.toUpperCase() : null;
const resolution =
info.resolution || (info.height ? `${info.height}p` : null);
return [codec, resolution].filter(Boolean).join(" · ");
}
function formatAudioCodec(info) {
if (!info) return null;
const codec = info.codec ? info.codec.toUpperCase() : null;
let channels = null;
if (info.channelLayout) channels = info.channelLayout.toUpperCase();
else if (info.channels) {
channels =
info.channels === 6
? "5.1"
: info.channels === 2
? "2.0"
: `${info.channels}`;
}
return [codec, channels].filter(Boolean).join(" · ");
}
function toggleView() {
viewMode = viewMode === "grid" ? "list" : "grid";
if (typeof window !== "undefined") {
window.localStorage.setItem(VIEW_KEY, viewMode);
}
}
function toggleSelection(file) {
const next = new Set(selectedItems);
@@ -80,6 +131,27 @@
selectedItems = new Set();
allSelected = false;
}
function tryAutoPlay() {
if (!pendingPlayTarget || files.length === 0) return;
const normalizedTarget = pendingPlayTarget
.replace(/^\.?\//, "")
.replace(/\\/g, "/");
const candidate =
files.find((f) => {
const normalizedName = f.name
.replace(/^\.?\//, "")
.replace(/\\/g, "/");
return (
normalizedName === normalizedTarget ||
normalizedName.endsWith(normalizedTarget)
);
}) || null;
if (candidate) {
pendingPlayTarget = null;
openModal(candidate);
}
}
async function openModal(f) {
stopCurrentVideo();
videoEl = null;
@@ -251,6 +323,7 @@
selectedItems = new Set(failed);
allSelected = failed.length > 0 && failed.length === files.length;
await refreshMovieCount();
}
onMount(async () => {
await loadFiles(); // önce dosyaları getir
@@ -288,6 +361,32 @@
}
};
function handleKey(e) {
const active = document.activeElement;
const tag = active?.tagName;
const type = active?.type?.toLowerCase();
const isTextInput =
tag === "INPUT" &&
[
"text",
"search",
"email",
"password",
"number",
"url",
"tel"
].includes(type);
const isEditable =
(tag === "TEXTAREA" || isTextInput || active?.isContentEditable) ?? false;
if (e.metaKey && e.key && e.key.toLowerCase() === "backspace") {
if (isEditable) return;
if (selectedItems.size > 0) {
e.preventDefault();
deleteSelectedFiles();
}
return;
}
const isCmd = e.metaKey || e.ctrlKey;
if (isCmd && e.key.toLowerCase() === "a") {
e.preventDefault();
@@ -312,8 +411,13 @@
<section class="files" on:click={handleFilesClick}>
<div class="files-header">
<h2>Media Library</h2>
<div class="header-title">
<h2>Media Library</h2>
</div>
<div class="header-actions">
{#if files.length > 0 && selectedItems.size > 0}
<span class="selection-count">{selectedItems.size} dosya seçildi</span>
{/if}
{#if files.length > 0 && selectedItems.size > 0}
<button
class="select-all-btn"
@@ -394,6 +498,35 @@
{f.tracker ? f.tracker : "Bilinmiyor"}
</span>
</div>
{#if f.mediaInfo?.video || f.mediaInfo?.audio}
<div class="meta-line codecs">
{#if f.extension}
<span class="codec-chip file-type">
{#if f.type?.startsWith("image/")}
<i class="fa-solid fa-file-image"></i>
{:else}
<i class="fa-solid fa-file-video"></i>
{/if}
{f.extension.toUpperCase()}
</span>
{/if}
{#if f.mediaInfo?.video}
<span class="codec-chip">
<i class="fa-solid fa-film"></i>
{formatVideoCodec(f.mediaInfo.video)}
</span>
{/if}
{#if f.mediaInfo?.video && f.mediaInfo?.audio}
<span class="codec-separator">|</span>
{/if}
{#if f.mediaInfo?.audio}
<span class="codec-chip">
<i class="fa-solid fa-volume-high"></i>
{formatAudioCodec(f.mediaInfo.audio)}
</span>
{/if}
</div>
{/if}
</div>
</div>
<div class="media-type-icon">
@@ -733,6 +866,16 @@
margin-bottom: 18px;
gap: 12px;
}
.header-title {
display: flex;
align-items: flex-end;
gap: 6px;
}
.selection-count {
font-size: 13px;
color: #6a6a6a;
font-weight: 500;
}
.header-actions {
display: flex;
align-items: center;
@@ -967,6 +1110,32 @@
display: inline-block;
vertical-align: middle;
}
.meta-line.codecs {
display: flex;
align-items: center;
gap: 12px;
color: #5a5a5a;
font-size: 13px;
}
.codec-chip {
display: inline-flex;
align-items: center;
gap: 6px;
color: #4e4e4e;
font-weight: 500;
}
.codec-chip.file-type {
color: #1f1f1f;
text-transform: uppercase;
}
.codec-chip i {
color: #ffc107;
font-size: 12px;
}
.codec-separator {
color: #7a7a7a;
font-weight: 500;
}
.status-badge {
color: #2f8a4d;
font-weight: 600;

File diff suppressed because it is too large Load Diff