UI Değişiklikleri
This commit is contained in:
@@ -1158,13 +1158,28 @@ async function openVideoAtIndex(index) {
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
padding: 10px 2px 14px;
|
overflow-y: hidden;
|
||||||
scrollbar-width: thin;
|
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);
|
background: rgba(0, 0, 0, 0.25);
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
min-height: 80px;
|
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 {
|
.season-picker button {
|
||||||
background: rgba(255, 255, 255, 0.06);
|
background: rgba(255, 255, 255, 0.06);
|
||||||
color: #f5f5f5;
|
color: #f5f5f5;
|
||||||
@@ -1183,7 +1198,7 @@ async function openVideoAtIndex(index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.season-picker button.selected {
|
.season-picker button.selected {
|
||||||
background: #fdce45;
|
background: #f5b333;
|
||||||
color: #101010;
|
color: #101010;
|
||||||
border-color: rgba(0, 0, 0, 0.15);
|
border-color: rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
@@ -1583,7 +1598,7 @@ async function openVideoAtIndex(index) {
|
|||||||
|
|
||||||
.season-picker {
|
.season-picker {
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 8px 2px 12px;
|
padding: 12px;
|
||||||
min-height: 70px;
|
min-height: 70px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1671,7 +1686,7 @@ async function openVideoAtIndex(index) {
|
|||||||
|
|
||||||
.season-picker {
|
.season-picker {
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
padding: 6px 1px 10px;
|
padding: 10px;
|
||||||
min-height: 64px;
|
min-height: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,4 +16,5 @@ services:
|
|||||||
PASSWORD: ${PASSWORD}
|
PASSWORD: ${PASSWORD}
|
||||||
TMDB_API_KEY: ${TMDB_API_KEY}
|
TMDB_API_KEY: ${TMDB_API_KEY}
|
||||||
TVDB_API_KEY: ${TVDB_API_KEY}
|
TVDB_API_KEY: ${TVDB_API_KEY}
|
||||||
|
FANART_TV_API_KEY: ${FANART_TV_API_KEY}
|
||||||
VIDEO_THUMBNAIL_TIME: ${VIDEO_THUMBNAIL_TIME}
|
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_BASE_URL = "https://api4.thetvdb.com/v4";
|
||||||
const TVDB_IMAGE_BASE =
|
const TVDB_IMAGE_BASE =
|
||||||
process.env.TVDB_IMAGE_BASE || "https://artworks.thetvdb.com";
|
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_PATH = process.env.FFPROBE_PATH || "ffprobe";
|
||||||
const FFPROBE_MAX_BUFFER =
|
const FFPROBE_MAX_BUFFER =
|
||||||
Number(process.env.FFPROBE_MAX_BUFFER) > 0
|
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) {
|
function titleCase(value) {
|
||||||
if (!value) return "";
|
if (!value) return "";
|
||||||
return value
|
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) {
|
function infoFilePath(savePath) {
|
||||||
return path.join(savePath, INFO_FILENAME);
|
return path.join(savePath, INFO_FILENAME);
|
||||||
}
|
}
|
||||||
@@ -1546,20 +1640,36 @@ async function ensureSeriesData(
|
|||||||
return type.includes("poster") || type === "series" || type === "2";
|
return type.includes("poster") || type === "series" || type === "2";
|
||||||
}) || artworksRaw[0];
|
}) || artworksRaw[0];
|
||||||
|
|
||||||
const backdropArtwork = artworksRaw.find((a) => {
|
// Fanart.tv'den backdrop al
|
||||||
const type = String(
|
let backdropImage = null;
|
||||||
a?.type ||
|
const fanartData = await fetchFanartTvImages(seriesData.id);
|
||||||
a?.artworkType ||
|
if (fanartData?.showbackground && Array.isArray(fanartData.showbackground) && fanartData.showbackground.length > 0) {
|
||||||
a?.type2 ||
|
// İlk showbackground resmini al
|
||||||
a?.name ||
|
const firstBackdrop = fanartData.showbackground[0];
|
||||||
a?.artwork
|
backdropImage = firstBackdrop.url;
|
||||||
).toLowerCase();
|
}
|
||||||
return (
|
|
||||||
type.includes("fanart") ||
|
// Eğer Fanart.tv'de backdrop yoksa, TVDB'i fallback olarak kullan
|
||||||
type.includes("background") ||
|
if (!backdropImage) {
|
||||||
type === "1"
|
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 =
|
const posterImage =
|
||||||
posterArtwork?.image ||
|
posterArtwork?.image ||
|
||||||
@@ -1568,13 +1678,6 @@ async function ensureSeriesData(
|
|||||||
posterArtwork?.thumbnail ||
|
posterArtwork?.thumbnail ||
|
||||||
posterArtwork?.url ||
|
posterArtwork?.url ||
|
||||||
null;
|
null;
|
||||||
const backdropImage =
|
|
||||||
backdropArtwork?.image ||
|
|
||||||
backdropArtwork?.file ||
|
|
||||||
backdropArtwork?.fileName ||
|
|
||||||
backdropArtwork?.thumbnail ||
|
|
||||||
backdropArtwork?.url ||
|
|
||||||
null;
|
|
||||||
|
|
||||||
const posterPath = path.join(showDir, "poster.jpg");
|
const posterPath = path.join(showDir, "poster.jpg");
|
||||||
const backdropPath = path.join(showDir, "backdrop.jpg");
|
const backdropPath = path.join(showDir, "backdrop.jpg");
|
||||||
@@ -1583,8 +1686,13 @@ async function ensureSeriesData(
|
|||||||
await downloadTvdbImage(posterImage, posterPath);
|
await downloadTvdbImage(posterImage, posterPath);
|
||||||
}
|
}
|
||||||
if (backdropImage && !fs.existsSync(backdropPath)) {
|
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);
|
await downloadTvdbImage(backdropImage, backdropPath);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const seasonPaths = seasonAssetPaths(rootFolder, seriesInfo.season);
|
const seasonPaths = seasonAssetPaths(rootFolder, seriesInfo.season);
|
||||||
const seasonsRaw = Array.isArray(container.seasons)
|
const seasonsRaw = Array.isArray(container.seasons)
|
||||||
|
|||||||
Reference in New Issue
Block a user