Merge main into ph
This commit is contained in:
171
server/server.js
171
server/server.js
@@ -24,6 +24,9 @@ const torrents = new Map();
|
||||
const youtubeJobs = new Map();
|
||||
let wss;
|
||||
const PORT = process.env.PORT || 3001;
|
||||
const DEBUG_CPU = process.env.DEBUG_CPU === "1";
|
||||
const DISABLE_MEDIA_PROCESSING = process.env.DISABLE_MEDIA_PROCESSING === "1";
|
||||
const AUTO_PAUSE_ON_COMPLETE = process.env.AUTO_PAUSE_ON_COMPLETE === "1";
|
||||
|
||||
// --- İndirilen dosyalar için klasör oluştur ---
|
||||
const DOWNLOAD_DIR = path.join(__dirname, "downloads");
|
||||
@@ -115,10 +118,40 @@ const FFPROBE_MAX_BUFFER =
|
||||
: 10 * 1024 * 1024;
|
||||
const AVATAR_PATH = path.join(__dirname, "..", "client", "src", "assets", "avatar.png");
|
||||
|
||||
function getWsClientCount() {
|
||||
if (!wss) return 0;
|
||||
let count = 0;
|
||||
wss.clients.forEach((c) => {
|
||||
if (c.readyState === 1) count += 1;
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
function startCpuProfiler() {
|
||||
if (!DEBUG_CPU) return;
|
||||
const intervalMs = 5000;
|
||||
let lastUsage = process.cpuUsage();
|
||||
let lastTime = process.hrtime.bigint();
|
||||
setInterval(() => {
|
||||
const usage = process.cpuUsage();
|
||||
const now = process.hrtime.bigint();
|
||||
const deltaUser = usage.user - lastUsage.user;
|
||||
const deltaSystem = usage.system - lastUsage.system;
|
||||
const elapsedUs = Number(now - lastTime) / 1000;
|
||||
const cpuPct = elapsedUs > 0 ? ((deltaUser + deltaSystem) / elapsedUs) * 100 : 0;
|
||||
lastUsage = usage;
|
||||
lastTime = now;
|
||||
console.log(
|
||||
`📈 CPU ${(cpuPct || 0).toFixed(1)}% | torrents:${torrents.size} yt:${youtubeJobs.size} ws:${getWsClientCount()}`
|
||||
);
|
||||
}, intervalMs);
|
||||
}
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use("/downloads", express.static(DOWNLOAD_DIR));
|
||||
startCpuProfiler();
|
||||
|
||||
// --- En uygun video dosyasını seç ---
|
||||
function pickBestVideoFile(torrent) {
|
||||
@@ -1716,6 +1749,7 @@ function bytesFromHuman(value, unit = "B") {
|
||||
}
|
||||
|
||||
async function extractMediaInfo(filePath, retryCount = 0) {
|
||||
if (DISABLE_MEDIA_PROCESSING) return null;
|
||||
if (!filePath || !fs.existsSync(filePath)) return null;
|
||||
|
||||
// Farklı ffprobe stratejileri
|
||||
@@ -1834,6 +1868,7 @@ async function extractMediaInfo(filePath, retryCount = 0) {
|
||||
}
|
||||
|
||||
function queueVideoThumbnail(fullPath, relPath, retryCount = 0) {
|
||||
if (DISABLE_MEDIA_PROCESSING) return;
|
||||
const { relThumb, absThumb } = getVideoThumbnailPaths(relPath);
|
||||
if (fs.existsSync(absThumb) || generatingThumbnails.has(absThumb)) return;
|
||||
|
||||
@@ -1892,6 +1927,7 @@ function queueVideoThumbnail(fullPath, relPath, retryCount = 0) {
|
||||
}
|
||||
|
||||
function queueImageThumbnail(fullPath, relPath, retryCount = 0) {
|
||||
if (DISABLE_MEDIA_PROCESSING) return;
|
||||
const { relThumb, absThumb } = getImageThumbnailPaths(relPath);
|
||||
if (fs.existsSync(absThumb) || generatingThumbnails.has(absThumb)) return;
|
||||
|
||||
@@ -2758,6 +2794,14 @@ async function ensureMovieData(
|
||||
bestVideoPath,
|
||||
precomputedMediaInfo = null
|
||||
) {
|
||||
if (DISABLE_MEDIA_PROCESSING) {
|
||||
return {
|
||||
mediaInfo: precomputedMediaInfo || null,
|
||||
metadata: null,
|
||||
cacheKey: null,
|
||||
videoPath: bestVideoPath ? normalizeTrashPath(bestVideoPath) : null
|
||||
};
|
||||
}
|
||||
const normalizedRoot = sanitizeRelative(rootFolder);
|
||||
if (!TMDB_API_KEY) {
|
||||
return {
|
||||
@@ -3270,6 +3314,7 @@ async function ensureSeriesData(
|
||||
seriesInfo,
|
||||
mediaInfo
|
||||
) {
|
||||
if (DISABLE_MEDIA_PROCESSING) return null;
|
||||
if (!TVDB_API_KEY || !seriesInfo) {
|
||||
console.log("📺 TVDB atlandı (eksik anahtar ya da seriesInfo yok):", {
|
||||
rootFolder,
|
||||
@@ -3313,6 +3358,22 @@ async function ensureSeriesData(
|
||||
}
|
||||
}
|
||||
|
||||
if (!seriesData && candidateKeys.length) {
|
||||
for (const key of candidateKeys) {
|
||||
const candidatePaths = tvSeriesPathsByKey(key);
|
||||
if (!fs.existsSync(candidatePaths.metadata)) continue;
|
||||
try {
|
||||
seriesData = JSON.parse(fs.readFileSync(candidatePaths.metadata, "utf-8")) || {};
|
||||
existingPaths = candidatePaths;
|
||||
break;
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`⚠️ series.json okunamadı (${candidatePaths.metadata}): ${err.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const legacyPaths = tvSeriesPaths(normalizedRoot);
|
||||
if (!seriesData && fs.existsSync(legacyPaths.metadata)) {
|
||||
try {
|
||||
@@ -4571,6 +4632,7 @@ let pendingMediaRescan = { movies: false, tv: false };
|
||||
let lastMediaRescanReason = "manual";
|
||||
|
||||
function queueMediaRescan({ movies = false, tv = false, reason = "manual" } = {}) {
|
||||
if (DISABLE_MEDIA_PROCESSING) return;
|
||||
if (!movies && !tv) return;
|
||||
pendingMediaRescan.movies = pendingMediaRescan.movies || movies;
|
||||
pendingMediaRescan.tv = pendingMediaRescan.tv || tv;
|
||||
@@ -4945,7 +5007,7 @@ async function onTorrentDone({ torrent }) {
|
||||
};
|
||||
|
||||
const seriesInfo = parseSeriesInfo(file.name);
|
||||
if (seriesInfo) {
|
||||
if (seriesInfo && !DISABLE_MEDIA_PROCESSING) {
|
||||
try {
|
||||
const ensured = await ensureSeriesData(
|
||||
rootFolder,
|
||||
@@ -5032,53 +5094,53 @@ async function onTorrentDone({ torrent }) {
|
||||
infoUpdate.seriesEpisodes = seriesEpisodes;
|
||||
}
|
||||
|
||||
const ensuredMedia = await ensureMovieData(
|
||||
rootFolder,
|
||||
displayName,
|
||||
bestVideoPath,
|
||||
primaryMediaInfo
|
||||
);
|
||||
if (ensuredMedia?.mediaInfo) {
|
||||
infoUpdate.primaryMediaInfo = ensuredMedia.mediaInfo;
|
||||
if (!infoUpdate.files) infoUpdate.files = perFileMetadata;
|
||||
if (bestVideoPath) {
|
||||
const entry = infoUpdate.files[bestVideoPath] || {};
|
||||
infoUpdate.files[bestVideoPath] = {
|
||||
...entry,
|
||||
movieMatch: ensuredMedia.metadata
|
||||
? {
|
||||
id: ensuredMedia.metadata.id ?? null,
|
||||
title:
|
||||
ensuredMedia.metadata.title ||
|
||||
ensuredMedia.metadata.matched_title ||
|
||||
displayName,
|
||||
year: ensuredMedia.metadata.release_date
|
||||
? Number(
|
||||
ensuredMedia.metadata.release_date.slice(0, 4)
|
||||
)
|
||||
: ensuredMedia.metadata.matched_year || null,
|
||||
poster: ensuredMedia.metadata.poster_path || null,
|
||||
backdrop: ensuredMedia.metadata.backdrop_path || null,
|
||||
cacheKey: ensuredMedia.cacheKey || null,
|
||||
matchedAt: Date.now()
|
||||
}
|
||||
: entry.movieMatch
|
||||
};
|
||||
const movieType = determineMediaType({
|
||||
tracker: torrent.announce?.[0] || null,
|
||||
movieMatch: ensuredMedia.metadata,
|
||||
seriesEpisode: seriesEpisodes[bestVideoPath] || null,
|
||||
categories: null,
|
||||
relPath: bestVideoPath,
|
||||
audioOnly: false
|
||||
});
|
||||
perFileMetadata[bestVideoPath] = {
|
||||
...(perFileMetadata[bestVideoPath] || {}),
|
||||
type: movieType
|
||||
};
|
||||
infoUpdate.files[bestVideoPath].type = movieType;
|
||||
if (!DISABLE_MEDIA_PROCESSING) {
|
||||
const ensuredMedia = await ensureMovieData(
|
||||
rootFolder,
|
||||
displayName,
|
||||
bestVideoPath,
|
||||
primaryMediaInfo
|
||||
);
|
||||
if (ensuredMedia?.mediaInfo) {
|
||||
infoUpdate.primaryMediaInfo = ensuredMedia.mediaInfo;
|
||||
if (!infoUpdate.files) infoUpdate.files = perFileMetadata;
|
||||
if (bestVideoPath) {
|
||||
const fileEntry = infoUpdate.files[bestVideoPath] || {};
|
||||
infoUpdate.files[bestVideoPath] = {
|
||||
...fileEntry,
|
||||
movieMatch: ensuredMedia.metadata
|
||||
? {
|
||||
id: ensuredMedia.metadata.id ?? null,
|
||||
title:
|
||||
ensuredMedia.metadata.title ||
|
||||
ensuredMedia.metadata.matched_title ||
|
||||
displayName,
|
||||
year: ensuredMedia.metadata.release_date
|
||||
? Number(ensuredMedia.metadata.release_date.slice(0, 4))
|
||||
: ensuredMedia.metadata.matched_year || null,
|
||||
poster: ensuredMedia.metadata.poster_path || null,
|
||||
backdrop: ensuredMedia.metadata.backdrop_path || null,
|
||||
cacheKey: ensuredMedia.cacheKey || null,
|
||||
matchedAt: Date.now()
|
||||
}
|
||||
: fileEntry.movieMatch
|
||||
};
|
||||
const movieType = determineMediaType({
|
||||
tracker: torrent.announce?.[0] || null,
|
||||
movieMatch: ensuredMedia.metadata,
|
||||
seriesEpisode: seriesEpisodes[bestVideoPath] || null,
|
||||
categories: null,
|
||||
relPath: bestVideoPath,
|
||||
audioOnly: false
|
||||
});
|
||||
perFileMetadata[bestVideoPath] = {
|
||||
...(perFileMetadata[bestVideoPath] || {}),
|
||||
type: movieType
|
||||
};
|
||||
infoUpdate.files[bestVideoPath].type = movieType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
upsertInfoFile(entry.savePath, infoUpdate);
|
||||
broadcastFileUpdate(rootFolder);
|
||||
@@ -5086,6 +5148,13 @@ async function onTorrentDone({ torrent }) {
|
||||
// Torrent tamamlandığında disk space bilgisini güncelle
|
||||
broadcastDiskSpace();
|
||||
|
||||
if (AUTO_PAUSE_ON_COMPLETE) {
|
||||
const paused = pauseTorrentEntry(entry);
|
||||
if (paused) {
|
||||
console.log(`⏸️ Torrent otomatik durduruldu: ${torrent.infoHash}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Medya tespiti tamamlandığında özel bildirim gönder
|
||||
if (Object.keys(seriesEpisodes).length > 0 || infoUpdate.primaryMediaInfo) {
|
||||
if (wss) {
|
||||
@@ -6304,6 +6373,10 @@ app.get("/api/movies", requireAuth, (req, res) => {
|
||||
});
|
||||
|
||||
async function rebuildMovieMetadata({ clearCache = false, resetSeriesData = false } = {}) {
|
||||
if (DISABLE_MEDIA_PROCESSING) {
|
||||
console.log("🎬 Medya işlemleri kapalı; movie metadata taraması atlandı.");
|
||||
return [];
|
||||
}
|
||||
if (!TMDB_API_KEY) {
|
||||
throw new Error("TMDB API key tanımlı değil.");
|
||||
}
|
||||
@@ -7237,6 +7310,10 @@ app.get("/api/music", requireAuth, (req, res) => {
|
||||
});
|
||||
|
||||
async function rebuildTvMetadata({ clearCache = false } = {}) {
|
||||
if (DISABLE_MEDIA_PROCESSING) {
|
||||
console.log("📺 Medya işlemleri kapalı; TV metadata taraması atlandı.");
|
||||
return [];
|
||||
}
|
||||
if (!TVDB_API_KEY) {
|
||||
throw new Error("TVDB API erişimi için gerekli anahtar tanımlı değil.");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user