feat(youtube): ses dosyası indirme ve hata yönetimini iyileştir
Sadece ses dosyası indirme özelliğini geliştir ve hata yönetimini güçlendir: - Ses indirme formatını m4a/mp4/webm opus önceliğiyle optimize et - İş bazında ayarları destekle ve varsayılan ayarlara geri düş - Sadece ses indirmeleri için özel extractor argümanları kullan - yt-dlp hata koduna rağmen medya dosyası bulunursa devam et - Hem ses hem video dosyalarını tespit eden yeni findYoutubeMediaFile fonksiyonu ekle - Müzik tespiti mantığını iyileştir ve ses bayrağını dikkate al
This commit is contained in:
@@ -785,8 +785,11 @@ function saveYoutubeSettings({ resolution, onlyAudio }) {
|
||||
}
|
||||
|
||||
function buildYoutubeFormat({ resolution, onlyAudio }) {
|
||||
if (onlyAudio) return "bestaudio/b";
|
||||
const match = String(resolution || YT_DEFAULT_RESOLUTION).match(/(\\d+)/);
|
||||
if (onlyAudio) {
|
||||
// Tarayıcıda çalınabilir bir ses dosyası (öncelik m4a/mp4/webm opus) indir
|
||||
return "ba[ext=m4a]/ba[ext=mp4]/ba[acodec^=opus]/bestaudio";
|
||||
}
|
||||
const match = String(resolution || YT_DEFAULT_RESOLUTION).match(/(\d+)/);
|
||||
const height = match ? Number(match[1]) : 1080;
|
||||
const safeHeight = Number.isFinite(height) && height > 0 ? height : 1080;
|
||||
return `bestvideo[height<=${safeHeight}]+bestaudio/best[height<=${safeHeight}]`;
|
||||
@@ -795,7 +798,14 @@ function buildYoutubeFormat({ resolution, onlyAudio }) {
|
||||
function launchYoutubeJob(job) {
|
||||
const binary = getYtDlpBinary();
|
||||
const jsRuntimeArg = process.env.YT_DLP_JS_RUNTIME || "node";
|
||||
const ytSettings = loadYoutubeSettings();
|
||||
const fallbackSettings = loadYoutubeSettings();
|
||||
const ytSettings = {
|
||||
resolution: job?.resolution || fallbackSettings.resolution,
|
||||
onlyAudio:
|
||||
typeof job?.onlyAudio === "boolean"
|
||||
? job.onlyAudio
|
||||
: fallbackSettings.onlyAudio
|
||||
};
|
||||
|
||||
const cookieFile =
|
||||
(YT_COOKIES_PATH && fs.existsSync(YT_COOKIES_PATH) && YT_COOKIES_PATH) ||
|
||||
@@ -803,7 +813,11 @@ function launchYoutubeJob(job) {
|
||||
|
||||
const extractorArgValue =
|
||||
YT_EXTRACTOR_ARGS ||
|
||||
(cookieFile ? "youtube:player-client=web" : "youtube:player-client=android");
|
||||
(ytSettings.onlyAudio
|
||||
? "youtube:player-client=web_safari,web,ios,mweb"
|
||||
: cookieFile
|
||||
? "youtube:player-client=web"
|
||||
: "youtube:player-client=android");
|
||||
|
||||
const formatSelector = buildYoutubeFormat(ytSettings);
|
||||
|
||||
@@ -818,6 +832,17 @@ function launchYoutubeJob(job) {
|
||||
jsRuntimeArg,
|
||||
"--extractor-args",
|
||||
extractorArgValue,
|
||||
...(ytSettings.onlyAudio
|
||||
? [
|
||||
"--extract-audio",
|
||||
"--audio-format",
|
||||
"m4a",
|
||||
"--audio-quality",
|
||||
"0",
|
||||
"--remux-audio",
|
||||
"m4a"
|
||||
]
|
||||
: []),
|
||||
...(cookieFile && fs.existsSync(cookieFile)
|
||||
? ["--cookies", cookieFile]
|
||||
: []),
|
||||
@@ -941,7 +966,8 @@ function updateYoutubeProgress(job, match) {
|
||||
|
||||
async function finalizeYoutubeJob(job, exitCode) {
|
||||
job.downloadSpeed = 0;
|
||||
if (exitCode !== 0) {
|
||||
const fallbackMedia = findYoutubeMediaFile(job.savePath, Boolean(job.onlyAudio));
|
||||
if (exitCode !== 0 && !fallbackMedia) {
|
||||
job.state = "error";
|
||||
const tail = job.debug?.logs ? job.debug.logs.slice(-8) : [];
|
||||
job.error = `yt-dlp ${exitCode} kodu ile sonlandı`;
|
||||
@@ -958,6 +984,11 @@ async function finalizeYoutubeJob(job, exitCode) {
|
||||
broadcastSnapshot();
|
||||
return;
|
||||
}
|
||||
if (exitCode !== 0 && fallbackMedia) {
|
||||
console.warn(
|
||||
`⚠️ yt-dlp çıkış kodu ${exitCode} ancak medya bulundu, devam ediliyor: ${fallbackMedia}`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
if (job.currentStage && !job.currentStage.done && job.currentStage.totalBytes) {
|
||||
@@ -966,8 +997,11 @@ async function finalizeYoutubeJob(job, exitCode) {
|
||||
}
|
||||
|
||||
const infoJson = findYoutubeInfoJson(job.savePath);
|
||||
const videoFile = findYoutubeVideoFile(job.savePath);
|
||||
if (!videoFile) {
|
||||
const mediaFile = fallbackMedia || findYoutubeMediaFile(
|
||||
job.savePath,
|
||||
Boolean(job.onlyAudio)
|
||||
);
|
||||
if (!mediaFile) {
|
||||
job.state = "error";
|
||||
job.error = "Video dosyası bulunamadı";
|
||||
console.warn("❌ yt-dlp çıktı video bulunamadı:", {
|
||||
@@ -979,10 +1013,10 @@ async function finalizeYoutubeJob(job, exitCode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const absVideo = path.join(job.savePath, videoFile);
|
||||
const stats = fs.statSync(absVideo);
|
||||
const mediaInfo = await extractMediaInfo(absVideo).catch(() => null);
|
||||
const relativeName = videoFile.replace(/\\/g, "/");
|
||||
const absMedia = path.join(job.savePath, mediaFile);
|
||||
const stats = fs.statSync(absMedia);
|
||||
const mediaInfo = await extractMediaInfo(absMedia).catch(() => null);
|
||||
const relativeName = mediaFile.replace(/\\/g, "/");
|
||||
job.files = [
|
||||
{
|
||||
index: 0,
|
||||
@@ -991,15 +1025,16 @@ async function finalizeYoutubeJob(job, exitCode) {
|
||||
}
|
||||
];
|
||||
job.selectedIndex = 0;
|
||||
job.title = deriveYoutubeTitle(videoFile, job.videoId);
|
||||
job.title = deriveYoutubeTitle(mediaFile, job.videoId);
|
||||
job.downloaded = stats.size;
|
||||
job.totalBytes = stats.size;
|
||||
job.progress = 1;
|
||||
job.state = "completed";
|
||||
job.error = null;
|
||||
|
||||
const metadataPayload = await writeYoutubeMetadata(
|
||||
job,
|
||||
absVideo,
|
||||
absMedia,
|
||||
mediaInfo,
|
||||
infoJson
|
||||
);
|
||||
@@ -1042,12 +1077,30 @@ async function finalizeYoutubeJob(job, exitCode) {
|
||||
}
|
||||
}
|
||||
|
||||
function findYoutubeVideoFile(savePath) {
|
||||
function findYoutubeMediaFile(savePath, preferAudio = false) {
|
||||
const entries = fs.readdirSync(savePath, { withFileTypes: true });
|
||||
const videos = entries
|
||||
const files = entries
|
||||
.filter((entry) => entry.isFile())
|
||||
.map((entry) => entry.name)
|
||||
.filter((name) => VIDEO_EXTS.includes(path.extname(name).toLowerCase()));
|
||||
.map((entry) => entry.name);
|
||||
|
||||
const audioExts = Array.from(MUSIC_EXTENSIONS);
|
||||
if (preferAudio) {
|
||||
const audios = files.filter((name) =>
|
||||
audioExts.includes(path.extname(name).toLowerCase())
|
||||
);
|
||||
if (audios.length) {
|
||||
audios.sort((a, b) => {
|
||||
const aSize = fs.statSync(path.join(savePath, a)).size;
|
||||
const bSize = fs.statSync(path.join(savePath, b)).size;
|
||||
return bSize - aSize;
|
||||
});
|
||||
return audios[0];
|
||||
}
|
||||
}
|
||||
|
||||
const videos = files.filter((name) =>
|
||||
VIDEO_EXTS.includes(path.extname(name).toLowerCase())
|
||||
);
|
||||
if (!videos.length) return null;
|
||||
videos.sort((a, b) => {
|
||||
const aSize = fs.statSync(path.join(savePath, a)).size;
|
||||
@@ -1075,8 +1128,7 @@ function deriveYoutubeTitle(fileName, videoId) {
|
||||
return cleaned.replace(/[-_.]+$/g, "").trim() || base;
|
||||
}
|
||||
|
||||
function isYoutubeMusic(infoJson, mediaInfo, audioOnlyFlag = false) {
|
||||
if (audioOnlyFlag) return true;
|
||||
function isYoutubeMusic(infoJson, mediaInfo) {
|
||||
const categories = Array.isArray(infoJson?.categories)
|
||||
? infoJson.categories.map((c) => String(c).toLowerCase())
|
||||
: [];
|
||||
@@ -1108,7 +1160,7 @@ async function writeYoutubeMetadata(job, videoPath, mediaInfo, infoJsonFile) {
|
||||
const categories = Array.isArray(infoJson?.categories)
|
||||
? infoJson.categories
|
||||
: null;
|
||||
const isAudioOnly = isYoutubeMusic(infoJson, mediaInfo, Boolean(job.onlyAudio));
|
||||
const isAudioOnly = isYoutubeMusic(infoJson, mediaInfo) || Boolean(job.onlyAudio);
|
||||
const derivedType = determineMediaType({
|
||||
tracker: "youtube",
|
||||
movieMatch: null,
|
||||
|
||||
Reference in New Issue
Block a user