167 lines
4.1 KiB
Go
167 lines
4.1 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))
|
|
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 ""
|
|
}
|