fix(makhno): handle voices with different season counts and missing season data

The previous implementation assumed all voices had the same number of seasons and used index-based access, which caused issues when voices had different season counts. The new implementation:

- Extracts season numbers from season titles using regex
- Creates a unified list of all available season numbers across all voices
- Handles cases where voices have no seasons or missing season data
- Selects appropriate season numbers when specific seasons are requested
- Maintains backward compatibility with existing URL parameters

This fixes issues with season selection when different voice tracks have varying season counts or when some voices lack season information entirely.
This commit is contained in:
baliasnyifeliks 2026-02-04 19:28:32 +02:00
parent c6cc802403
commit e3aa03089c

View File

@ -196,18 +196,35 @@ namespace Makhno
return OnError(); return OnError();
} }
int maxSeasons = playerData.Voices.Max(v => v.Seasons?.Count ?? 0); var voiceSeasons = playerData.Voices
if (maxSeasons <= 0) .Select((voice, index) => new
{
Voice = voice,
Index = index,
Seasons = GetSeasonsWithNumbers(voice)
})
.Where(v => v.Seasons.Count > 0)
.ToList();
var seasonNumbers = voiceSeasons
.SelectMany(v => v.Seasons.Select(s => s.Number))
.Distinct()
.OrderBy(n => n)
.ToList();
if (seasonNumbers.Count == 0)
return OnError(); return OnError();
if (season == -1) if (season == -1)
{ {
var season_tpl = new SeasonTpl(); var season_tpl = new SeasonTpl();
for (int i = 0; i < maxSeasons; i++) foreach (var seasonNumber in seasonNumbers)
{ {
var seasonItem = playerData.Voices.Select(v => v.Seasons.ElementAtOrDefault(i)).FirstOrDefault(s => s != null); var seasonItem = voiceSeasons
string seasonName = seasonItem.Title ?? $"Сезон {i + 1}"; .SelectMany(v => v.Seasons)
int seasonNumber = i + 1; .FirstOrDefault(s => s.Number == seasonNumber);
string seasonName = seasonItem.Season?.Title ?? $"Сезон {seasonNumber}";
string link = $"{host}/makhno?imdb_id={imdb_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&season={seasonNumber}"; string link = $"{host}/makhno?imdb_id={imdb_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&season={seasonNumber}";
season_tpl.Append(seasonName, link, seasonNumber.ToString()); season_tpl.Append(seasonName, link, seasonNumber.ToString());
} }
@ -215,28 +232,28 @@ namespace Makhno
return rjson ? Content(season_tpl.ToJson(), "application/json; charset=utf-8") : Content(season_tpl.ToHtml(), "text/html; charset=utf-8"); return rjson ? Content(season_tpl.ToJson(), "application/json; charset=utf-8") : Content(season_tpl.ToHtml(), "text/html; charset=utf-8");
} }
int seasonIndex = season > 0 ? season - 1 : season;
if (seasonIndex < 0 || seasonIndex >= maxSeasons)
return OnError();
var voice_tpl = new VoiceTpl(); var voice_tpl = new VoiceTpl();
var episode_tpl = new EpisodeTpl(); var episode_tpl = new EpisodeTpl();
string selectedVoice = t; string selectedVoice = t;
if (string.IsNullOrEmpty(selectedVoice) && playerData.Voices.Any()) if (string.IsNullOrEmpty(selectedVoice) || !int.TryParse(selectedVoice, out _))
{ {
selectedVoice = "0"; var voiceWithSeason = voiceSeasons.FirstOrDefault(v => v.Seasons.Any(s => s.Number == season));
selectedVoice = voiceWithSeason != null ? voiceWithSeason.Index.ToString() : voiceSeasons.First().Index.ToString();
} }
for (int i = 0; i < playerData.Voices.Count; i++) for (int i = 0; i < playerData.Voices.Count; i++)
{ {
var voice = playerData.Voices[i]; var voice = playerData.Voices[i];
string voiceName = voice.Name ?? $"Озвучка {i + 1}"; string voiceName = voice.Name ?? $"Озвучка {i + 1}";
int voiceSeasonIndex = GetSeasonIndexForVoice(voice, seasonIndex); var seasonsForVoice = GetSeasonsWithNumbers(voice);
if (voiceSeasonIndex < 0) if (seasonsForVoice.Count == 0)
continue; continue;
int seasonNumber = voiceSeasonIndex + 1; int seasonNumber = seasonsForVoice.Any(s => s.Number == season)
? season
: seasonsForVoice.Min(s => s.Number);
string voiceLink = $"{host}/makhno?imdb_id={imdb_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&season={seasonNumber}&t={i}"; string voiceLink = $"{host}/makhno?imdb_id={imdb_id}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}&year={year}&serial=1&season={seasonNumber}&t={i}";
bool isActive = selectedVoice == i.ToString(); bool isActive = selectedVoice == i.ToString();
voice_tpl.Append(voiceName, isActive, voiceLink); voice_tpl.Append(voiceName, isActive, voiceLink);
@ -245,10 +262,14 @@ namespace Makhno
if (!string.IsNullOrEmpty(selectedVoice) && int.TryParse(selectedVoice, out int voiceIndex) && voiceIndex < playerData.Voices.Count) if (!string.IsNullOrEmpty(selectedVoice) && int.TryParse(selectedVoice, out int voiceIndex) && voiceIndex < playerData.Voices.Count)
{ {
var selectedVoiceData = playerData.Voices[voiceIndex]; var selectedVoiceData = playerData.Voices[voiceIndex];
int effectiveSeasonIndex = GetSeasonIndexForVoice(selectedVoiceData, seasonIndex); var seasonsForVoice = GetSeasonsWithNumbers(selectedVoiceData);
if (effectiveSeasonIndex >= 0) if (seasonsForVoice.Count > 0)
{ {
var selectedSeason = selectedVoiceData.Seasons[effectiveSeasonIndex]; int effectiveSeasonNumber = seasonsForVoice.Any(s => s.Number == season)
? season
: seasonsForVoice.Min(s => s.Number);
var selectedSeason = seasonsForVoice.First(s => s.Number == effectiveSeasonNumber).Season;
var sortedEpisodes = selectedSeason.Episodes.OrderBy(e => ExtractEpisodeNumber(e.Title)).ToList(); var sortedEpisodes = selectedSeason.Episodes.OrderBy(e => ExtractEpisodeNumber(e.Title)).ToList();
for (int i = 0; i < sortedEpisodes.Count; i++) for (int i = 0; i < sortedEpisodes.Count; i++)
@ -257,11 +278,10 @@ namespace Makhno
if (!string.IsNullOrEmpty(episode.File)) if (!string.IsNullOrEmpty(episode.File))
{ {
string streamUrl = BuildStreamUrl(init, episode.File); string streamUrl = BuildStreamUrl(init, episode.File);
int seasonNumber = effectiveSeasonIndex + 1;
episode_tpl.Append( episode_tpl.Append(
episode.Title, episode.Title,
title ?? original_title, title ?? original_title,
seasonNumber.ToString(), effectiveSeasonNumber.ToString(),
(i + 1).ToString("D2"), (i + 1).ToString("D2"),
streamUrl streamUrl
); );
@ -286,15 +306,29 @@ namespace Makhno
return match.Success ? int.Parse(match.Groups[1].Value) : 0; return match.Success ? int.Parse(match.Groups[1].Value) : 0;
} }
private int GetSeasonIndexForVoice(Voice voice, int requestedSeasonIndex) private int? ExtractSeasonNumber(string title)
{ {
if (string.IsNullOrEmpty(title))
return null;
var match = System.Text.RegularExpressions.Regex.Match(title, @"(\d+)");
return match.Success ? int.Parse(match.Groups[1].Value) : (int?)null;
}
private List<(Season Season, int Number)> GetSeasonsWithNumbers(Voice voice)
{
var result = new List<(Season Season, int Number)>();
if (voice?.Seasons == null || voice.Seasons.Count == 0) if (voice?.Seasons == null || voice.Seasons.Count == 0)
return -1; return result;
if (requestedSeasonIndex >= 0 && requestedSeasonIndex < voice.Seasons.Count) for (int i = 0; i < voice.Seasons.Count; i++)
return requestedSeasonIndex; {
var season = voice.Seasons[i];
int number = ExtractSeasonNumber(season?.Title) ?? (i + 1);
result.Add((season, number));
}
return 0; return result;
} }
private async Task<ResolveResult> ResolvePlaySource(string imdbId, string title, string originalTitle, int year, int serial, MakhnoInvoke invoke) private async Task<ResolveResult> ResolvePlaySource(string imdbId, string title, string originalTitle, int year, int serial, MakhnoInvoke invoke)