diff --git a/internal/metadata/metadata.go b/internal/metadata/metadata.go new file mode 100644 index 0000000..421a88c --- /dev/null +++ b/internal/metadata/metadata.go @@ -0,0 +1,159 @@ +package metadata + +import ( + "encoding/json" + "fmt" + "net/http" + "regexp" + "strings" + "time" + + "videnc-vibe/pkg/types" +) + +const ( + omdbAPIURL = "https://www.omdbapi.com/" + tvmazeAPIURL = "https://api.tvmaze.com/" +) + +var ( + imdbRegex = regexp.MustCompile(`tt(\d+)`) + tvmRegex = regexp.MustCompile(`(?i)tvm(\d+)`) + seRegex = regexp.MustCompile(`(?i)s(\d+)e(\d+)`) +) + +type OMDbResponse struct { + Title string `json:"Title"` + Year string `json:"Released"` + IMDBID string `json:"imdbID"` + Response string `json:"Response"` + Error string `json:"Error"` +} + +type TVMazeShowResponse struct { + Name string `json:"name"` + Extern struct { + IMDB string `json:"imdb"` + } `json:"externals"` +} + +type TVMazeEpisode struct { + Name string `json:"name"` + Season int `json:"season"` + Number int `json:"number"` + Airdate string `json:"airdate"` + Show TVMazeShowResponse `json:"show"` +} + +type Client struct { + omdbAPIKey string + httpClient *http.Client +} + +func NewClient(apiKey string) *Client { + return &Client{ + omdbAPIKey: apiKey, + httpClient: &http.Client{Timeout: 10 * time.Second}, + } +} + +func ParseFilename(filename string) (isSeries bool, imdbID, tvmazeID, season, episode string) { + filename = strings.TrimSuffix(filename, ".mkv") + + seMatch := seRegex.FindStringSubmatch(filename) + tvmMatch := tvmRegex.FindStringSubmatch(filename) + imdbMatch := imdbRegex.FindStringSubmatch(filename) + + if seMatch != nil && tvmMatch != nil { + isSeries = true + season = fmt.Sprintf("%02d", parseInt(seMatch[1])) + episode = fmt.Sprintf("%02d", parseInt(seMatch[2])) + tvmazeID = tvmMatch[1] + return + } + + if imdbMatch != nil { + imdbID = imdbMatch[0] + } + + return +} + +func parseInt(s string) int { + var n int + fmt.Sscanf(s, "%d", &n) + return n +} + +func (c *Client) FetchMovieMetadata(imdbID string) (*types.Metadata, error) { + url := fmt.Sprintf("%s?i=%s&apikey=%s", omdbAPIURL, imdbID, c.omdbAPIKey) + + resp, err := c.httpClient.Get(url) + if err != nil { + return nil, fmt.Errorf("fetching OMDb: %w", err) + } + defer resp.Body.Close() + + var result OMDbResponse + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return nil, fmt.Errorf("decoding OMDb response: %w", err) + } + + if result.Response != "True" { + return nil, fmt.Errorf("OMDb error: %s", result.Error) + } + + dateReleased := formatDate(result.Year) + + return &types.Metadata{ + Title: result.Title, + DateReleased: dateReleased, + IMDBID: result.IMDBID, + OriginalMedia: types.MediaTypeBluRay, + }, nil +} + +func (c *Client) FetchSeriesMetadata(tvmazeID, season, episode string) (*types.Metadata, error) { + url := fmt.Sprintf("%s/shows/%s/episodebynumber?season=%s&number=%s", tvmazeAPIURL, tvmazeID, season, episode) + + resp, err := c.httpClient.Get(url) + if err != nil { + return nil, fmt.Errorf("fetching TVmaze: %w", err) + } + defer resp.Body.Close() + + var ep TVMazeEpisode + if err := json.NewDecoder(resp.Body).Decode(&ep); err != nil { + return nil, fmt.Errorf("decoding TVmaze response: %w", err) + } + + dateReleased := formatDate(ep.Airdate) + + var imdbID string + if ep.Show.Extern.IMDB != "" { + imdbID = ep.Show.Extern.IMDB + } + + return &types.Metadata{ + Title: ep.Name, + Collection: ep.Show.Name, + Season: season, + Episode: episode, + DateReleased: dateReleased, + IMDBID: imdbID, + TVMazeID: tvmazeID, + IsSeries: true, + OriginalMedia: types.MediaTypeBluRay, + }, nil +} + +func formatDate(date string) string { + if date == "" || date == "N/A" { + return "" + } + t, err := time.Parse("02 Jan 2006", date) + if err != nil { + return date + } + return t.Format("2006-01-02") +}