Quality sorting fix

This commit is contained in:
tapframe 2025-09-18 23:29:38 +05:30
parent 7ed39d3926
commit 0fcc4edde1
2 changed files with 187 additions and 58 deletions

View file

@ -1265,7 +1265,7 @@ const PluginsScreen: React.FC = () => {
}; };
// Define available quality options // Define available quality options
const qualityOptions = ['2160p', '4K', '1080p', '720p', '360p', 'DV', 'HDR', 'REMUX', '480p', 'CAM', 'TS']; const qualityOptions = ['Auto', '2160p', '4K', '1080p', '720p', '360p', 'DV', 'HDR', 'REMUX', '480p', 'CAM', 'TS'];

View file

@ -676,14 +676,19 @@ export const StreamsScreen = () => {
return streams.filter(stream => { return streams.filter(stream => {
const streamTitle = stream.title || stream.name || ''; const streamTitle = stream.title || stream.name || '';
// Check if any excluded quality is found in the stream title // Check if any excluded quality is found in the stream title
const hasExcludedQuality = settings.excludedQualities.some(excludedQuality => { const hasExcludedQuality = settings.excludedQualities.some(excludedQuality => {
// Create a case-insensitive regex pattern for the quality if (excludedQuality === 'Auto') {
const pattern = new RegExp(excludedQuality.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'); // Special handling for Auto quality - check for Auto or Adaptive
return pattern.test(streamTitle); return /\b(auto|adaptive)\b/i.test(streamTitle);
} else {
// Create a case-insensitive regex pattern for other qualities
const pattern = new RegExp(excludedQuality.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
return pattern.test(streamTitle);
}
}); });
// Return true to keep the stream (if it doesn't have excluded quality) // Return true to keep the stream (if it doesn't have excluded quality)
return !hasExcludedQuality; return !hasExcludedQuality;
}); });
@ -1306,94 +1311,218 @@ export const StreamsScreen = () => {
// Check if we should group all streams under one section // Check if we should group all streams under one section
if (settings.streamDisplayMode === 'grouped') { if (settings.streamDisplayMode === 'grouped') {
// Separate streams by type: installed addons vs plugins // Separate addon and plugin streams - only apply quality filtering/sorting to plugins
const addonStreams: Stream[] = []; const addonStreams: Stream[] = [];
const pluginStreams: Stream[] = []; const pluginStreams: Stream[] = [];
const addonNames: string[] = []; const providerNames: string[] = [];
const pluginNames: string[] = []; let totalOriginalCount = 0;
let addonOriginalCount = 0;
let pluginOriginalCount = 0;
filteredEntries.forEach(([addonId, { addonName, streams: providerStreams }]) => { filteredEntries.forEach(([addonId, { addonName, streams: providerStreams }]) => {
const isInstalledAddon = installedAddons.some(addon => addon.id === addonId); const isInstalledAddon = installedAddons.some(addon => addon.id === addonId);
// Count original streams before filtering // Count original streams before filtering
if (isInstalledAddon) { totalOriginalCount += providerStreams.length;
addonOriginalCount += providerStreams.length;
} else {
pluginOriginalCount += providerStreams.length;
}
// Apply quality filtering only; keep original addon stream order
const filteredStreams = filterStreamsByQuality(providerStreams);
if (isInstalledAddon) { if (isInstalledAddon) {
addonStreams.push(...filteredStreams); // For ADDONS: Keep all streams in original order, NO filtering or sorting
if (!addonNames.includes(addonName)) { addonStreams.push(...providerStreams);
addonNames.push(addonName); if (!providerNames.includes(addonName)) {
providerNames.push(addonName);
} }
} else { } else {
pluginStreams.push(...filteredStreams); // For PLUGINS: Apply quality filtering and sorting
if (!pluginNames.includes(addonName)) { const filteredStreams = filterStreamsByQuality(providerStreams);
pluginNames.push(addonName);
if (filteredStreams.length > 0) {
pluginStreams.push(...filteredStreams);
if (!providerNames.includes(addonName)) {
providerNames.push(addonName);
}
} }
} }
}); });
const sections = []; const totalStreamsCount = addonStreams.length + pluginStreams.length;
if (addonStreams.length > 0) { const isEmptyDueToQualityFilter = totalOriginalCount > 0 && totalStreamsCount === 0;
sections.push({
title: addonNames.join(', '), if (isEmptyDueToQualityFilter) {
addonId: 'grouped-addons', return [{
data: addonStreams title: providerNames.join(', '),
}); addonId: 'grouped-all',
} else if (addonOriginalCount > 0 && addonStreams.length === 0) {
// Show empty section with message for addons that had streams but all were filtered
sections.push({
title: addonNames.join(', '),
addonId: 'grouped-addons',
data: [{ isEmptyPlaceholder: true } as any], data: [{ isEmptyPlaceholder: true } as any],
isEmptyDueToQualityFilter: true isEmptyDueToQualityFilter: true
}); }];
} }
if (pluginStreams.length > 0) { // Combine streams: Addons first (unsorted), then sorted plugins
sections.push({ let combinedStreams = [...addonStreams];
title: localScraperService.getRepositoryName(),
addonId: 'grouped-plugins', // Apply quality sorting to PLUGIN streams when enabled
data: pluginStreams if (settings.streamSortMode === 'quality-then-scraper' && pluginStreams.length > 0) {
}); const sortedPluginStreams = [...pluginStreams].sort((a, b) => {
} else if (pluginOriginalCount > 0 && pluginStreams.length === 0) { const titleA = (a.name || a.title || '').toLowerCase();
// Show empty section with message for plugins that had streams but all were filtered const titleB = (b.name || b.title || '').toLowerCase();
sections.push({
title: localScraperService.getRepositoryName(), // Check for "Auto" quality - always prioritize it
addonId: 'grouped-plugins', const isAutoA = /\b(auto|adaptive)\b/i.test(titleA);
data: [{ isEmptyPlaceholder: true } as any], const isAutoB = /\b(auto|adaptive)\b/i.test(titleB);
isEmptyDueToQualityFilter: true
if (isAutoA && !isAutoB) return -1; // Auto comes first
if (!isAutoA && isAutoB) return 1; // Auto comes first
// If both are Auto or both are not Auto, continue with normal sorting
// Helper function to extract quality as number
const getQualityNumeric = (title: string | undefined): number => {
if (!title) return 0;
// Check for 4K first (treat as 2160p)
if (/\b4k\b/i.test(title)) {
return 2160;
}
const matchWithP = title.match(/(\d+)p/i);
if (matchWithP) return parseInt(matchWithP[1], 10);
const qualityPatterns = [
/\b(240|360|480|720|1080|1440|2160|4320|8000)\b/i
];
for (const pattern of qualityPatterns) {
const match = title.match(pattern);
if (match) {
const quality = parseInt(match[1], 10);
if (quality >= 240 && quality <= 8000) return quality;
}
}
return 0;
};
const qualityA = getQualityNumeric(a.name || a.title);
const qualityB = getQualityNumeric(b.name || b.title);
// Sort by quality (highest first)
if (qualityA !== qualityB) {
return qualityB - qualityA;
}
// If quality is the same, sort by provider name, then stream name
const providerA = a.addonId || a.addonName || '';
const providerB = b.addonId || b.addonName || '';
if (providerA !== providerB) {
return providerA.localeCompare(providerB);
}
const nameA = (a.name || a.title || '').toLowerCase();
const nameB = (b.name || b.title || '').toLowerCase();
return nameA.localeCompare(nameB);
}); });
// Add sorted plugin streams to the combined streams
combinedStreams.push(...sortedPluginStreams);
} else {
// If quality sorting is disabled, just add plugin streams as-is
combinedStreams.push(...pluginStreams);
} }
return sections; return [{
title: providerNames.join(', '),
addonId: 'grouped-all',
data: combinedStreams,
isEmptyDueToQualityFilter: false
}];
} else { } else {
// Use separate sections for each provider (current behavior) // Use separate sections for each provider (current behavior)
return filteredEntries.map(([addonId, { addonName, streams: providerStreams }]) => { return filteredEntries.map(([addonId, { addonName, streams: providerStreams }]) => {
const isInstalledAddon = installedAddons.some(addon => addon.id === addonId);
// Count original streams before filtering // Count original streams before filtering
const originalCount = providerStreams.length; const originalCount = providerStreams.length;
// Apply quality filtering only; keep original addon stream order let filteredStreams = providerStreams;
const filteredStreams = filterStreamsByQuality(providerStreams); let isEmptyDueToQualityFilter = false;
const isEmptyDueToQualityFilter = originalCount > 0 && filteredStreams.length === 0; // Only apply quality filtering to plugins, NOT addons
if (!isInstalledAddon) {
filteredStreams = filterStreamsByQuality(providerStreams);
isEmptyDueToQualityFilter = originalCount > 0 && filteredStreams.length === 0;
}
if (isEmptyDueToQualityFilter) {
return {
title: addonName,
addonId,
data: [{ isEmptyPlaceholder: true } as any],
isEmptyDueToQualityFilter
};
}
let processedStreams = filteredStreams;
// Apply quality sorting for plugins when enabled, but NOT for addons
if (!isInstalledAddon && settings.streamSortMode === 'quality-then-scraper') {
processedStreams = [...filteredStreams].sort((a, b) => {
const titleA = (a.name || a.title || '').toLowerCase();
const titleB = (b.name || b.title || '').toLowerCase();
// Check for "Auto" quality - always prioritize it
const isAutoA = /\b(auto|adaptive)\b/i.test(titleA);
const isAutoB = /\b(auto|adaptive)\b/i.test(titleB);
if (isAutoA && !isAutoB) return -1; // Auto comes first
if (!isAutoA && isAutoB) return 1; // Auto comes first
// If both are Auto or both are not Auto, continue with normal sorting
// Helper function to extract quality as number
const getQualityNumeric = (title: string | undefined): number => {
if (!title) return 0;
// Check for 4K first (treat as 2160p)
if (/\b4k\b/i.test(title)) {
return 2160;
}
const matchWithP = title.match(/(\d+)p/i);
if (matchWithP) return parseInt(matchWithP[1], 10);
const qualityPatterns = [
/\b(240|360|480|720|1080|1440|2160|4320|8000)\b/i
];
for (const pattern of qualityPatterns) {
const match = title.match(pattern);
if (match) {
const quality = parseInt(match[1], 10);
if (quality >= 240 && quality <= 8000) return quality;
}
}
return 0;
};
const qualityA = getQualityNumeric(a.name || a.title);
const qualityB = getQualityNumeric(b.name || b.title);
// Sort by quality (highest first)
if (qualityA !== qualityB) {
return qualityB - qualityA;
}
// If quality is the same, sort by name/title
const nameA = (a.name || a.title || '').toLowerCase();
const nameB = (b.name || b.title || '').toLowerCase();
return nameA.localeCompare(nameB);
});
}
return { return {
title: addonName, title: addonName,
addonId, addonId,
data: isEmptyDueToQualityFilter ? [{ isEmptyPlaceholder: true } as any] : filteredStreams, data: processedStreams,
isEmptyDueToQualityFilter isEmptyDueToQualityFilter: false
}; };
}); });
} }
}, [selectedProvider, type, episodeStreams, groupedStreams, settings.streamDisplayMode, filterStreamsByQuality, addonResponseOrder]); }, [selectedProvider, type, episodeStreams, groupedStreams, settings.streamDisplayMode, filterStreamsByQuality, addonResponseOrder, settings.streamSortMode]);
const episodeImage = useMemo(() => { const episodeImage = useMemo(() => {
if (episodeThumbnail) { if (episodeThumbnail) {