Quality sorting fix
This commit is contained in:
parent
7ed39d3926
commit
0fcc4edde1
2 changed files with 187 additions and 58 deletions
|
|
@ -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'];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue