feat: Implement helper for selecting best m3u8 URL
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import { MissAVDownloader } from '../../../services/downloaders/MissAVDownloader';
|
||||
|
||||
describe('MissAVDownloader URL Selection', () => {
|
||||
describe('selectBestM3u8Url', () => {
|
||||
it('should prioritize surrit.com master playlist over other specific quality playlists', () => {
|
||||
const urls = [
|
||||
'https://surrit.com/9183fb8b-9d17-43c7-b429-4c28b7813e2c/playlist.m3u8',
|
||||
'https://surrit.com/9183fb8b-9d17-43c7-b429-4c28b7813e2c/480p/video.m3u8',
|
||||
'https://edge-hls.growcdnssedge.com/hls/121964773/master/121964773_240p.m3u8',
|
||||
'https://media-hls.growcdnssedge.com/b-hls-18/121964773/121964773_240p.m3u8'
|
||||
];
|
||||
|
||||
// Default behavior (no format sort)
|
||||
const selected = MissAVDownloader.selectBestM3u8Url(urls, false);
|
||||
expect(selected).toBe('https://surrit.com/9183fb8b-9d17-43c7-b429-4c28b7813e2c/playlist.m3u8');
|
||||
});
|
||||
|
||||
it('should prioritize higher resolution when multiple surrit URLs exist', () => {
|
||||
const urls = [
|
||||
'https://surrit.com/uuid/playlist.m3u8', // Master
|
||||
'https://surrit.com/uuid/720p/video.m3u8',
|
||||
'https://surrit.com/uuid/480p/video.m3u8'
|
||||
];
|
||||
|
||||
const selected = MissAVDownloader.selectBestM3u8Url(urls, false);
|
||||
// If we have specific qualities, we usually prefer the highest specific one if no format sort is used,
|
||||
// OR we might prefer the master if we trust yt-dlp to pick best.
|
||||
// Based on typical behavior without format sort: existing logic preferred specific resolutions.
|
||||
// But for MissAV, playlist.m3u8 is usually more reliable/complete.
|
||||
// Let's assume we want to stick with Master if available for surrit.
|
||||
expect(selected).toContain('playlist.m3u8');
|
||||
// OR if we keep logic "prefer specific quality", then 720p.
|
||||
// The requirement is "Prioritize surrit.com URLs... prefer playlist.m3u8 (generic master) over specific resolution masters if the specific resolution is low/suspicious"
|
||||
// In this case 720p is good.
|
||||
// However, usually playlist.m3u8 contains all variants.
|
||||
});
|
||||
|
||||
it('should fallback to resolution comparison if no surrit URLs', () => {
|
||||
const urls = [
|
||||
'https://other.com/video_240p.m3u8',
|
||||
'https://other.com/video_720p.m3u8',
|
||||
'https://other.com/video_480p.m3u8'
|
||||
];
|
||||
|
||||
const selected = MissAVDownloader.selectBestM3u8Url(urls, false);
|
||||
expect(selected).toBe('https://other.com/video_720p.m3u8');
|
||||
});
|
||||
|
||||
it('should handle real world scenario from logs', () => {
|
||||
// From user log
|
||||
const urls = [
|
||||
'https://surrit.com/9183fb8b-9d17-43c7-b429-4c28b7813e2c/playlist.m3u8',
|
||||
'https://surrit.com/9183fb8b-9d17-43c7-b429-4c28b7813e2c/480p/video.m3u8',
|
||||
'https://media-hls.growcdnssedge.com/b-hls-18/121964773/121964773_240p.m3u8',
|
||||
'https://edge-hls.growcdnssedge.com/hls/121964773/master/121964773_240p.m3u8'
|
||||
];
|
||||
|
||||
const selected = MissAVDownloader.selectBestM3u8Url(urls, false);
|
||||
// The bug was it picked the last one (edge-hls...240p.m3u8) or similar.
|
||||
// We want the surrit playlist.
|
||||
expect(selected).toBe('https://surrit.com/9183fb8b-9d17-43c7-b429-4c28b7813e2c/playlist.m3u8');
|
||||
});
|
||||
|
||||
it('should respect format sort when enabled', () => {
|
||||
const urls = [
|
||||
'https://surrit.com/uuid/playlist.m3u8',
|
||||
'https://surrit.com/uuid/480p/video.m3u8'
|
||||
];
|
||||
// With format sort, we DEFINITELY want the master playlist so yt-dlp can do the sorting
|
||||
const selected = MissAVDownloader.selectBestM3u8Url(urls, true);
|
||||
expect(selected).toBe('https://surrit.com/uuid/playlist.m3u8');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
import { SUBTITLES_DIR } from "../../config/paths";
|
||||
import { extractBilibiliVideoId } from "../../utils/helpers";
|
||||
import { Video } from "../storageService";
|
||||
import { BaseDownloader, DownloadOptions, VideoInfo } from "./BaseDownloader";
|
||||
@@ -22,7 +23,7 @@ export type {
|
||||
BilibiliVideoInfo,
|
||||
BilibiliVideosResult,
|
||||
CollectionDownloadResult,
|
||||
DownloadResult,
|
||||
DownloadResult
|
||||
};
|
||||
|
||||
export class BilibiliDownloader extends BaseDownloader {
|
||||
@@ -183,6 +184,11 @@ export class BilibiliDownloader extends BaseDownloader {
|
||||
videoUrl: string,
|
||||
baseFilename: string
|
||||
): Promise<Array<{ language: string; filename: string; path: string }>> {
|
||||
return bilibiliSubtitle.downloadSubtitles(videoUrl, baseFilename);
|
||||
return bilibiliSubtitle.downloadSubtitles(
|
||||
videoUrl,
|
||||
baseFilename,
|
||||
SUBTITLES_DIR,
|
||||
"/subtitles"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,48 +162,16 @@ export class MissAVDownloader extends BaseDownloader {
|
||||
const hasFormatSort = !!(userConfig.S || userConfig.formatSort);
|
||||
|
||||
// 4. Select the best m3u8 URL from collected URLs
|
||||
// If user specified format sort, prefer master playlists so yt-dlp can choose resolution
|
||||
// Otherwise, prefer specific quality playlists
|
||||
let m3u8Url: string | null = null;
|
||||
if (m3u8Urls.length > 0) {
|
||||
// Sort URLs based on whether user wants format sort
|
||||
const sortedUrls = m3u8Urls.sort((a, b) => {
|
||||
const aIsMaster =
|
||||
a.includes("/playlist.m3u8") || a.includes("/master/");
|
||||
const bIsMaster =
|
||||
b.includes("/playlist.m3u8") || b.includes("/master/");
|
||||
|
||||
if (hasFormatSort) {
|
||||
// When format sort is specified, prefer master playlists
|
||||
// so yt-dlp can apply format sort to choose the right resolution
|
||||
if (aIsMaster && !bIsMaster) return -1; // Master playlist first
|
||||
if (!aIsMaster && bIsMaster) return 1;
|
||||
// Among master playlists or non-master playlists, prefer higher quality
|
||||
const aQuality = a.match(/(\d+p)/)?.[1] || "0p";
|
||||
const bQuality = b.match(/(\d+p)/)?.[1] || "0p";
|
||||
const aQualityNum = parseInt(aQuality) || 0;
|
||||
const bQualityNum = parseInt(bQuality) || 0;
|
||||
return bQualityNum - aQualityNum; // Higher quality first
|
||||
} else {
|
||||
// Default behavior: prefer specific quality playlists over master playlists
|
||||
if (aIsMaster && !bIsMaster) return 1;
|
||||
if (!aIsMaster && bIsMaster) return -1;
|
||||
// Among non-master playlists, prefer higher quality (480p > 240p)
|
||||
const aQuality = a.match(/(\d+p)/)?.[1] || "0p";
|
||||
const bQuality = b.match(/(\d+p)/)?.[1] || "0p";
|
||||
const aQualityNum = parseInt(aQuality) || 0;
|
||||
const bQualityNum = parseInt(bQuality) || 0;
|
||||
return bQualityNum - aQualityNum; // Higher quality first
|
||||
}
|
||||
});
|
||||
|
||||
m3u8Url = sortedUrls[0];
|
||||
let m3u8Url = MissAVDownloader.selectBestM3u8Url(m3u8Urls, hasFormatSort);
|
||||
|
||||
if (m3u8Url) {
|
||||
logger.info(
|
||||
`Selected m3u8 URL from ${m3u8Urls.length} candidates (format sort: ${hasFormatSort}):`,
|
||||
m3u8Url
|
||||
);
|
||||
if (sortedUrls.length > 1) {
|
||||
logger.info("Alternative URLs:", sortedUrls.slice(1));
|
||||
const alternatives = m3u8Urls.filter(u => u !== m3u8Url);
|
||||
if (alternatives.length > 0) {
|
||||
logger.info("Alternative URLs:", alternatives);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,4 +507,84 @@ export class MissAVDownloader extends BaseDownloader {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to select best m3u8 URL
|
||||
static selectBestM3u8Url(urls: string[], hasFormatSort: boolean): string | null {
|
||||
if (urls.length === 0) return null;
|
||||
|
||||
const sortedUrls = [...urls].sort((a, b) => {
|
||||
// 1. Priority: surrit.com
|
||||
const aIsSurrit = a.includes("surrit.com");
|
||||
const bIsSurrit = b.includes("surrit.com");
|
||||
if (aIsSurrit && !bIsSurrit) return -1;
|
||||
if (!aIsSurrit && bIsSurrit) return 1;
|
||||
|
||||
// 2. Priority: Master playlist (playlist.m3u8 specifically for surrit, or general master)
|
||||
// We generally prefer master playlists because they contain all variants, allowing yt-dlp to pick the best.
|
||||
// The previous logic penalized master playlists without explicit resolution, which caused issues.
|
||||
const aIsMaster = a.includes("/playlist.m3u8") || a.includes("/master/");
|
||||
const bIsMaster = b.includes("/playlist.m3u8") || b.includes("/master/");
|
||||
|
||||
// If we are strictly comparing surrit URLs (both are surrit), we prefer the master playlist
|
||||
// because it's the "cleanest" source.
|
||||
if (aIsSurrit && bIsSurrit) {
|
||||
const aIsPlaylistM3u8 = a.includes("playlist.m3u8");
|
||||
const bIsPlaylistM3u8 = b.includes("playlist.m3u8");
|
||||
if (aIsPlaylistM3u8 && !bIsPlaylistM3u8) return -1;
|
||||
if (!aIsPlaylistM3u8 && bIsPlaylistM3u8) return 1;
|
||||
}
|
||||
|
||||
// If format sort is enabled, we almost always want the master playlist
|
||||
if (hasFormatSort) {
|
||||
if (aIsMaster && !bIsMaster) return -1;
|
||||
if (!aIsMaster && bIsMaster) return 1;
|
||||
} else {
|
||||
// If NO format sort, previously we preferred specific resolution.
|
||||
// BUT, given the bug report where a 240p stream was picked over a master,
|
||||
// we should probably trust the master playlist more particularly if the alternative is low quality.
|
||||
// However, if we have a high quality specific stream (e.g. 720p/1080p explicit), that might be fine.
|
||||
|
||||
// Let's refine: If one is surrit master, pick it. (Handled by step 1 & surrit sub-logic)
|
||||
// If neither is surrit, and one is master...
|
||||
|
||||
// If both are master or both are not master, compare resolution.
|
||||
}
|
||||
|
||||
// 3. Priority: Resolution (detected from URL)
|
||||
const aQuality = a.match(/(\d+p)/)?.[1] || "0p";
|
||||
const bQuality = b.match(/(\d+p)/)?.[1] || "0p";
|
||||
const aQualityNum = parseInt(aQuality) || 0;
|
||||
const bQualityNum = parseInt(bQuality) || 0;
|
||||
|
||||
// If we have a significant resolution difference, we might prefer the higher one
|
||||
// UNLESS one is a master playlist and the other is a low res specific one.
|
||||
// If one is master (0p detected) and other is 240p, 0p (master) should win if it's likely to contain better streams.
|
||||
|
||||
// Updated Strategy:
|
||||
// If both have resolution, compare them.
|
||||
if (aQualityNum > 0 && bQualityNum > 0) {
|
||||
return bQualityNum - aQualityNum; // Higher quality first
|
||||
}
|
||||
|
||||
// If one is master (assumed 0p from URL) and other is specific resolution:
|
||||
// If we are prioritizing master playlists (e.g. because of surrit or format sort), master wins.
|
||||
// If we are NOT specifically prioritizing master, we still might want to prefer it over very low res (e.g. < 480p).
|
||||
if (aIsMaster && bQualityNum > 0 && bQualityNum < 480) return -1; // Master wins over < 480p
|
||||
if (bIsMaster && aQualityNum > 0 && aQualityNum < 480) return 1; // Master wins over < 480p
|
||||
|
||||
// Fallback: Default to higher number (so 720p wins over 0p/master if we didn't catch it above)
|
||||
// This preserves 'best attempt' for specific high quality URLs if they exist not on surrit.
|
||||
if (aQualityNum !== bQualityNum) {
|
||||
return bQualityNum - aQualityNum;
|
||||
}
|
||||
|
||||
// Final tie-breaker: prefer master if all else equal
|
||||
if (aIsMaster && !bIsMaster) return -1;
|
||||
if (!aIsMaster && bIsMaster) return 1;
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
return sortedUrls[0];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user