UI Değişiklikleri
This commit is contained in:
@@ -1158,13 +1158,28 @@ async function openVideoAtIndex(index) {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
overflow-x: auto;
|
||||
padding: 10px 2px 14px;
|
||||
scrollbar-width: thin;
|
||||
overflow-y: hidden;
|
||||
padding: 12px;
|
||||
scrollbar-width: none; /* Scroll bar'ı tamamen gizle */
|
||||
-ms-overflow-style: none; /* IE ve Edge için */
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
border-radius: 14px;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
/* Scroll bar'ı webkit tarayıcılar için gizle */
|
||||
.season-picker::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.season-picker::-webkit-scrollbar-track {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.season-picker::-webkit-scrollbar-thumb {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.season-picker button {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: #f5f5f5;
|
||||
@@ -1183,7 +1198,7 @@ async function openVideoAtIndex(index) {
|
||||
}
|
||||
|
||||
.season-picker button.selected {
|
||||
background: #fdce45;
|
||||
background: #f5b333;
|
||||
color: #101010;
|
||||
border-color: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
@@ -1583,7 +1598,7 @@ async function openVideoAtIndex(index) {
|
||||
|
||||
.season-picker {
|
||||
gap: 8px;
|
||||
padding: 8px 2px 12px;
|
||||
padding: 12px;
|
||||
min-height: 70px;
|
||||
}
|
||||
|
||||
@@ -1671,7 +1686,7 @@ async function openVideoAtIndex(index) {
|
||||
|
||||
.season-picker {
|
||||
gap: 6px;
|
||||
padding: 6px 1px 10px;
|
||||
padding: 10px;
|
||||
min-height: 64px;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,4 +16,5 @@ services:
|
||||
PASSWORD: ${PASSWORD}
|
||||
TMDB_API_KEY: ${TMDB_API_KEY}
|
||||
TVDB_API_KEY: ${TVDB_API_KEY}
|
||||
FANART_TV_API_KEY: ${FANART_TV_API_KEY}
|
||||
VIDEO_THUMBNAIL_TIME: ${VIDEO_THUMBNAIL_TIME}
|
||||
|
||||
150
server/server.js
150
server/server.js
@@ -57,6 +57,8 @@ const TVDB_USER_TOKEN = "mock_api_key"
|
||||
const TVDB_BASE_URL = "https://api4.thetvdb.com/v4";
|
||||
const TVDB_IMAGE_BASE =
|
||||
process.env.TVDB_IMAGE_BASE || "https://artworks.thetvdb.com";
|
||||
const FANART_TV_API_KEY = process.env.FANART_TV_API_KEY || null;
|
||||
const FANART_TV_BASE_URL = "https://webservice.fanart.tv/v3";
|
||||
const FFPROBE_PATH = process.env.FFPROBE_PATH || "ffprobe";
|
||||
const FFPROBE_MAX_BUFFER =
|
||||
Number(process.env.FFPROBE_MAX_BUFFER) > 0
|
||||
@@ -107,6 +109,40 @@ async function downloadTvdbImage(imagePath, targetPath) {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchFanartTvImages(thetvdbId) {
|
||||
if (!FANART_TV_API_KEY || !thetvdbId) return null;
|
||||
const url = `${FANART_TV_BASE_URL}/tv/${thetvdbId}?api_key=${FANART_TV_API_KEY}`;
|
||||
|
||||
try {
|
||||
const resp = await fetch(url);
|
||||
if (!resp.ok) {
|
||||
console.warn(`⚠️ Fanart.tv isteği başarısız (${url}): ${resp.status}`);
|
||||
return null;
|
||||
}
|
||||
const data = await resp.json();
|
||||
console.log("🖼️ Fanart.tv backdrop araması:", { thetvdbId, hasShowbackground: Boolean(data.showbackground) });
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.warn(`⚠️ Fanart.tv isteği hatası (${url}): ${err.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadFanartTvImage(imageUrl, targetPath) {
|
||||
if (!imageUrl) return false;
|
||||
try {
|
||||
const resp = await fetch(imageUrl);
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||
ensureDirForFile(targetPath);
|
||||
const arr = await resp.arrayBuffer();
|
||||
fs.writeFileSync(targetPath, Buffer.from(arr));
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.warn(`⚠️ Fanart.tv görsel indirilemedi (${imageUrl}): ${err.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function titleCase(value) {
|
||||
if (!value) return "";
|
||||
return value
|
||||
@@ -218,6 +254,64 @@ function normalizeTvdbSeason(raw) {
|
||||
};
|
||||
}
|
||||
|
||||
function toDimension(value) {
|
||||
const num = Number(value);
|
||||
return Number.isFinite(num) ? num : null;
|
||||
}
|
||||
|
||||
function artworkKind(value) {
|
||||
return String(value || "").toLowerCase();
|
||||
}
|
||||
|
||||
function isBackgroundArtwork(entry) {
|
||||
const candidates = [
|
||||
entry?.type,
|
||||
entry?.artworkType,
|
||||
entry?.artworkTypeSlug,
|
||||
entry?.type2,
|
||||
entry?.name,
|
||||
entry?.artwork
|
||||
];
|
||||
return candidates
|
||||
.map(artworkKind)
|
||||
.some((kind) =>
|
||||
kind.includes("background") ||
|
||||
kind.includes("fanart") ||
|
||||
kind.includes("landscape") ||
|
||||
kind === "1"
|
||||
);
|
||||
}
|
||||
|
||||
function selectBackgroundArtwork(entries) {
|
||||
if (!Array.isArray(entries) || !entries.length) return null;
|
||||
const candidates = entries.filter(isBackgroundArtwork);
|
||||
if (!candidates.length) return null;
|
||||
|
||||
const normalized = candidates.map((entry) => {
|
||||
const width = toDimension(entry.width);
|
||||
const height = toDimension(entry.height);
|
||||
const area = width && height ? width * height : null;
|
||||
return { entry, width, height, area };
|
||||
});
|
||||
|
||||
const landscape = normalized
|
||||
.filter((item) => item.width && item.height && item.width >= item.height)
|
||||
.sort((a, b) => (b.area || 0) - (a.area || 0));
|
||||
if (landscape.length) return landscape[0].entry;
|
||||
|
||||
normalized.sort((a, b) => (b.area || 0) - (a.area || 0));
|
||||
return normalized[0].entry;
|
||||
}
|
||||
|
||||
async function fetchTvdbArtworks(seriesId, typeSlug) {
|
||||
if (!seriesId || !typeSlug) return [];
|
||||
const resp = await tvdbFetch(`/series/${seriesId}/artworks/${typeSlug}`);
|
||||
if (!resp) return [];
|
||||
if (Array.isArray(resp.data)) return resp.data;
|
||||
if (Array.isArray(resp.artworks)) return resp.artworks;
|
||||
return [];
|
||||
}
|
||||
|
||||
function infoFilePath(savePath) {
|
||||
return path.join(savePath, INFO_FILENAME);
|
||||
}
|
||||
@@ -1546,20 +1640,36 @@ async function ensureSeriesData(
|
||||
return type.includes("poster") || type === "series" || type === "2";
|
||||
}) || artworksRaw[0];
|
||||
|
||||
const backdropArtwork = artworksRaw.find((a) => {
|
||||
const type = String(
|
||||
a?.type ||
|
||||
a?.artworkType ||
|
||||
a?.type2 ||
|
||||
a?.name ||
|
||||
a?.artwork
|
||||
).toLowerCase();
|
||||
return (
|
||||
type.includes("fanart") ||
|
||||
type.includes("background") ||
|
||||
type === "1"
|
||||
);
|
||||
});
|
||||
// Fanart.tv'den backdrop al
|
||||
let backdropImage = null;
|
||||
const fanartData = await fetchFanartTvImages(seriesData.id);
|
||||
if (fanartData?.showbackground && Array.isArray(fanartData.showbackground) && fanartData.showbackground.length > 0) {
|
||||
// İlk showbackground resmini al
|
||||
const firstBackdrop = fanartData.showbackground[0];
|
||||
backdropImage = firstBackdrop.url;
|
||||
}
|
||||
|
||||
// Eğer Fanart.tv'de backdrop yoksa, TVDB'i fallback olarak kullan
|
||||
if (!backdropImage) {
|
||||
let backdropArtwork = selectBackgroundArtwork(artworksRaw);
|
||||
if (!backdropArtwork) {
|
||||
const backgroundArtworks = await fetchTvdbArtworks(seriesData.id, "background");
|
||||
backdropArtwork = selectBackgroundArtwork(backgroundArtworks);
|
||||
}
|
||||
if (!backdropArtwork) {
|
||||
const fanartArtworks = await fetchTvdbArtworks(seriesData.id, "fanart");
|
||||
backdropArtwork = selectBackgroundArtwork(fanartArtworks);
|
||||
}
|
||||
|
||||
if (backdropArtwork) {
|
||||
backdropImage = backdropArtwork?.image ||
|
||||
backdropArtwork?.file ||
|
||||
backdropArtwork?.fileName ||
|
||||
backdropArtwork?.thumbnail ||
|
||||
backdropArtwork?.url ||
|
||||
null;
|
||||
}
|
||||
}
|
||||
|
||||
const posterImage =
|
||||
posterArtwork?.image ||
|
||||
@@ -1568,13 +1678,6 @@ async function ensureSeriesData(
|
||||
posterArtwork?.thumbnail ||
|
||||
posterArtwork?.url ||
|
||||
null;
|
||||
const backdropImage =
|
||||
backdropArtwork?.image ||
|
||||
backdropArtwork?.file ||
|
||||
backdropArtwork?.fileName ||
|
||||
backdropArtwork?.thumbnail ||
|
||||
backdropArtwork?.url ||
|
||||
null;
|
||||
|
||||
const posterPath = path.join(showDir, "poster.jpg");
|
||||
const backdropPath = path.join(showDir, "backdrop.jpg");
|
||||
@@ -1583,8 +1686,13 @@ async function ensureSeriesData(
|
||||
await downloadTvdbImage(posterImage, posterPath);
|
||||
}
|
||||
if (backdropImage && !fs.existsSync(backdropPath)) {
|
||||
// Eğer backdropImage Fanart.tv'den geldiyse, onun indirme fonksiyonunu kullan
|
||||
if (fanartData?.showbackground && backdropImage.startsWith('http')) {
|
||||
await downloadFanartTvImage(backdropImage, backdropPath);
|
||||
} else {
|
||||
await downloadTvdbImage(backdropImage, backdropPath);
|
||||
}
|
||||
}
|
||||
|
||||
const seasonPaths = seasonAssetPaths(rootFolder, seriesInfo.season);
|
||||
const seasonsRaw = Array.isArray(container.seasons)
|
||||
|
||||
Reference in New Issue
Block a user