Добавил поиск метаданных по каталогам

This commit is contained in:
2026-06-14 15:21:01 +03:00
parent 9c1b178e46
commit 5087f35861
21 changed files with 1435 additions and 72 deletions
+132
View File
@@ -0,0 +1,132 @@
package metadata
import (
"context"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
const tmdbDefaultBaseURL = "https://api.themoviedb.org/3"
// TMDBConfig — настройки клиента TMDB.
type TMDBConfig struct {
APIKey string
Proxy string
Timeout time.Duration
BaseURL string // пусто → api.themoviedb.org; задаётся в тестах
}
// TMDB — клиент The Movie Database (API v3, авторизация по api_key).
type TMDB struct {
apiKey string
baseURL string
hc *http.Client
}
// NewTMDB собирает клиент TMDB.
func NewTMDB(cfg TMDBConfig) (*TMDB, error) {
if cfg.APIKey == "" {
return nil, fmt.Errorf("metadata: tmdb api_key required")
}
hc, err := newHTTPClient(cfg.Proxy, cfg.Timeout)
if err != nil {
return nil, err
}
base := cfg.BaseURL
if base == "" {
base = tmdbDefaultBaseURL
}
return &TMDB{apiKey: cfg.APIKey, baseURL: strings.TrimRight(base, "/"), hc: hc}, nil
}
func (t *TMDB) Name() string { return "tmdb" }
type tmdbSearchResp struct {
Results []struct {
ID int `json:"id"`
Title string `json:"title"` // movie
OriginalTitle string `json:"original_title"` // movie
Name string `json:"name"` // tv
OriginalName string `json:"original_name"` // tv
ReleaseDate string `json:"release_date"` // movie
FirstAirDate string `json:"first_air_date"` // tv
} `json:"results"`
}
// Search ищет фильм/сериал по названию и году.
func (t *TMDB) Search(ctx context.Context, q Query) ([]Candidate, error) {
var path string
params := url.Values{"api_key": {t.apiKey}, "query": {q.Title}, "include_adult": {"false"}}
switch q.Type {
case Movie:
path = "/search/movie"
if q.Year > 0 {
params.Set("year", strconv.Itoa(q.Year))
}
case Series:
path = "/search/tv"
if q.Year > 0 {
params.Set("first_air_date_year", strconv.Itoa(q.Year))
}
default:
return nil, fmt.Errorf("metadata: tmdb: неизвестный тип %q", q.Type)
}
var resp tmdbSearchResp
if err := getJSON(ctx, t.hc, t.baseURL+path+"?"+params.Encode(), nil, &resp); err != nil {
return nil, fmt.Errorf("tmdb search: %w", err)
}
out := make([]Candidate, 0, len(resp.Results))
for _, r := range resp.Results {
title, orig, date := r.Title, r.OriginalTitle, r.ReleaseDate
if q.Type == Series {
title, orig, date = r.Name, r.OriginalName, r.FirstAirDate
}
out = append(out, Candidate{
Provider: "tmdb",
ID: strconv.Itoa(r.ID),
Title: title,
OriginalTitle: orig,
Year: yearOf(date),
})
}
return out, nil
}
type tmdbTVResp struct {
Seasons []struct {
SeasonNumber int `json:"season_number"`
EpisodeCount int `json:"episode_count"`
} `json:"seasons"`
}
// SeasonEpisodeCounts возвращает число серий по сезонам сериала.
func (t *TMDB) SeasonEpisodeCounts(ctx context.Context, id string) (map[int]int, error) {
params := url.Values{"api_key": {t.apiKey}}
var resp tmdbTVResp
if err := getJSON(ctx, t.hc, t.baseURL+"/tv/"+url.PathEscape(id)+"?"+params.Encode(), nil, &resp); err != nil {
return nil, fmt.Errorf("tmdb tv %s: %w", id, err)
}
out := make(map[int]int, len(resp.Seasons))
for _, s := range resp.Seasons {
out[s.SeasonNumber] = s.EpisodeCount
}
return out, nil
}
// yearOf достаёт год из даты вида "1999-03-31".
func yearOf(date string) int {
if len(date) < 4 {
return 0
}
y, err := strconv.Atoi(date[:4])
if err != nil {
return 0
}
return y
}