package httpapi import ( "sort" "strconv" "strings" "git.vakhrushev.me/av/jellybit/internal/layout" "git.vakhrushev.me/av/jellybit/internal/recognize" ) // fileRow — строка виджета «файл источника → раскладка», общего для экранов // ревью и просмотра загрузки. Целевой путь берётся из превью (единая логика // internal/layout), а не вычисляется в шаблоне. type fileRow struct { Src string Dst string // целевой путь; пусто — файл не раскладывается RoleLabel string Linked bool Ignored bool } // buildFileRows сшивает файлы плана с целевыми путями из превью раскладки. func buildFileRows(plan recognize.Plan, preview []layout.Link) []fileRow { dstBySrc := make(map[string]string, len(preview)) for _, l := range preview { dstBySrc[l.Src] = l.Dst } rows := make([]fileRow, 0, len(plan.Files)) for _, f := range plan.Files { dst := dstBySrc[f.Src] rows = append(rows, fileRow{ Src: f.Src, Dst: dst, RoleLabel: roleLabel(string(f.Role)), Linked: dst != "", Ignored: f.Role == "ignore", }) } return rows } // seasonSummary собирает верхнеуровневую сводку сезонов сериальной раздачи по // эпизодным файлам плана. Сезон задан на файле (мультисезонные паки), поэтому // сводим множество различных сезонов; Season nil/0 — спецвыпуски. Примеры: // «Сезон 2», «Сезоны 1–3», «Сезоны 1, 3–4», «Спецвыпуски», «Сезоны 1–2, спецвыпуски». func seasonSummary(plan recognize.Plan) string { seen := map[int]bool{} specials := false for _, f := range plan.Files { if f.Role != recognize.RoleEpisode { continue } n := 0 if f.Season != nil { n = *f.Season } if n <= 0 { specials = true continue } seen[n] = true } nums := make([]int, 0, len(seen)) for n := range seen { nums = append(nums, n) } sort.Ints(nums) var parts []string switch { case len(nums) == 1: parts = append(parts, "Сезон "+strconv.Itoa(nums[0])) case len(nums) > 1: parts = append(parts, "Сезоны "+seasonRanges(nums)) } if specials { if len(parts) == 0 { parts = append(parts, "Спецвыпуски") } else { parts = append(parts, "спецвыпуски") } } return strings.Join(parts, ", ") } // seasonRanges схлопывает возрастающие номера сезонов в диапазоны: // [1,2,3] → «1–3», [1,3,4] → «1, 3–4». func seasonRanges(nums []int) string { var out []string for i := 0; i < len(nums); { j := i for j+1 < len(nums) && nums[j+1] == nums[j]+1 { j++ } if j == i { out = append(out, strconv.Itoa(nums[i])) } else { out = append(out, strconv.Itoa(nums[i])+"–"+strconv.Itoa(nums[j])) } i = j + 1 } return strings.Join(out, ", ") } // roleLabel — человекочитаемая роль файла раскладки. func roleLabel(role string) string { switch role { case "episode": return "эпизод" case "main", "movie": return "фильм" case "subtitle": return "субтитры" case "extra": return "допматериал" case "sample": return "семпл" case "ignore": return "игнор" default: return role } }