subsyt/internal/organize/organize.go
Viktor Varland 3e32707400
Some checks failed
build / build (push) Has been cancelled
fix: sanitize bug where / is converted to -
2025-10-03 09:18:18 +02:00

178 lines
4.2 KiB
Go

package organize
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"git.meatbag.se/varl/subsyt/internal/config"
"git.meatbag.se/varl/subsyt/internal/fsops"
"git.meatbag.se/varl/subsyt/internal/metadata"
"git.meatbag.se/varl/subsyt/internal/nfo"
)
type Organizer struct {
Paths config.Paths
FS fsops.Manager
}
func (o Organizer) Prepare() error {
if err := o.FS.EnsureDir(o.Paths.DownloadRoot); err != nil {
return err
}
if err := o.FS.EnsureDir(o.Paths.ChannelsDir); err != nil {
return err
}
if err := o.FS.EnsureDir(o.Paths.EpisodesDir); err != nil {
return err
}
if err := o.FS.EnsureDir(o.Paths.MediaDir); err != nil {
return err
}
return nil
}
func (o Organizer) ProcessShows(shows []metadata.ShowAsset) error {
for _, show := range shows {
showDir := filepath.Join(o.Paths.MediaDir, safeName(show.Show.Title))
if err := o.FS.EnsureDir(showDir); err != nil {
return err
}
if o.FS.DryRun {
log.Printf("[dry-run] write tvshow.nfo for %s\n", show.Show.Title)
} else {
nfo.WriteShowInfo(show.Show, filepath.Join(showDir, "tvshow.nfo"))
}
for _, img := range show.Images {
name := categorizeShowImage(img)
if name == "" {
continue
}
dest := filepath.Join(showDir, name)
if _, err := os.Stat(dest); err == nil {
log.Printf("skip image move, destination exists: %s\n", dest)
if err := o.FS.Remove(img); err != nil {
return err
}
continue
}
if err := o.FS.Move(img, dest); err != nil {
return err
}
}
metadata.EnsureBanner(show.Show, showDir, o.FS.DryRun)
metadata.EnsureFanart(show.Show, showDir, o.FS.DryRun)
if err := o.FS.Remove(show.InfoPath); err != nil {
return err
}
}
if err := o.FS.RemoveEmptyDirs(o.Paths.ChannelsDir); err != nil {
return err
}
return nil
}
func (o Organizer) ProcessEpisodes(episodes []metadata.EpisodeAsset) error {
for _, ep := range episodes {
showDir := filepath.Join(o.Paths.MediaDir, safeName(ep.Episode.ShowTitle))
seasonDir := filepath.Join(showDir, ep.Episode.Season)
if err := o.FS.EnsureDir(seasonDir); err != nil {
return err
}
var destMediaPath string
if ep.MediaPath != "" {
filename := filepath.Base(ep.MediaPath)
destMediaPath = filepath.Join(seasonDir, filename)
if err := o.FS.Move(ep.MediaPath, destMediaPath); err != nil {
return err
}
} else {
log.Printf("no media file for %s (%s)\n", ep.Episode.Title, ep.InfoPath)
}
for _, sidecar := range ep.Sidecars {
filename := filepath.Base(sidecar)
ext := strings.ToLower(filepath.Ext(filename))
if ext == ".jpg" || ext == ".jpeg" {
filename = metadata.NormalizeEpisodeThumbnail(filename)
}
dest := filepath.Join(seasonDir, filename)
if _, err := os.Stat(dest); err == nil {
log.Printf("skip sidecar move, destination exists: %s\n", dest)
if err := o.FS.Remove(sidecar); err != nil {
return err
}
continue
}
if err := o.FS.Move(sidecar, dest); err != nil {
return err
}
}
if destMediaPath != "" {
base := strings.TrimSuffix(filepath.Base(destMediaPath), filepath.Ext(destMediaPath))
nfoPath := filepath.Join(seasonDir, fmt.Sprintf("%s.nfo", base))
if o.FS.DryRun {
log.Printf("[dry-run] write episode nfo %s\n", nfoPath)
} else {
nfo.WriteEpisodeNFO(ep.Episode, nfoPath)
}
}
if err := o.FS.Remove(ep.InfoPath); err != nil {
return err
}
}
if err := o.FS.RemoveEmptyDirs(o.Paths.EpisodesDir); err != nil {
return err
}
return nil
}
func safeName(name string) string {
replacer := strings.NewReplacer(
"/", "",
"\\", "",
":", "",
"?", "",
"*", "",
"\"", "",
"<", "",
">", "",
"|", "",
)
sanitized := replacer.Replace(strings.TrimSpace(name))
sanitized = strings.Trim(sanitized, " .-_")
if sanitized == "" {
return "unknown"
}
return sanitized
}
func categorizeShowImage(path string) string {
lower := strings.ToLower(filepath.Base(path))
switch {
case strings.Contains(lower, "banner"):
return "banner.jpg"
case strings.Contains(lower, "fanart"):
return "fanart.jpg"
case strings.Contains(lower, "poster"):
return "poster.jpg"
default:
if strings.HasSuffix(lower, ".jpg") || strings.HasSuffix(lower, ".jpeg") {
return "poster.jpg"
}
}
return ""
}