nfo

package
v0.3.0-alpha Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 29, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApplyPreset

func ApplyPreset(preset string, scalarStrategy string, arrayStrategy string) (string, string, error)

ApplyPreset applies a preset configuration to scalar and array strategy strings. Presets:

  • "conservative": preserve-existing + merge (strictest preservation)
  • "gap-fill": fill-missing-only + merge (safe gap filling)
  • "aggressive": prefer-scraper + replace (trust scrapers completely)

Returns the resolved scalar and array strategy strings, or an error if preset is invalid. If preset is empty, returns the original strategy strings unchanged.

func FindNFOFile

func FindNFOFile(baseDir string, movie *models.Movie, nfoFilenameTemplate string, groupActress bool, perFile bool, isMultiPart bool, partSuffix string, videoFilePath string) string

FindNFOFile resolves the NFO path and searches for an existing file, trying the primary path first then legacy paths in order. Returns the found path (empty string if none found).

func FormatActressName

func FormatActressName(actress models.Actress, japaneseNames bool, firstNameOrder bool, unknownActress string, unknownActressMode ...string) string

func NFOToMovie

func NFOToMovie(nfo *Movie) (*models.Movie, []string)

NFOToMovie converts an NFO Movie struct to a models.Movie

func ParseArrayStrategy

func ParseArrayStrategy(strategy string) bool

ParseArrayStrategy converts array strategy string to boolean Valid values: "merge", "replace" (case-insensitive) Returns true (merge) as default

func ResolveNFOFilename

func ResolveNFOFilename(movie *models.Movie, nfoFilenameTemplate string, groupActress bool, perFile bool, isMultiPart bool, partSuffix string) string

ResolveNFOFilename computes the NFO filename for a movie using the same logic as Generate, without writing the file. This ensures that history/revert code tracks the exact path the generator will use.

func ResolveNFOPath

func ResolveNFOPath(baseDir string, movie *models.Movie, nfoFilenameTemplate string, groupActress bool, perFile bool, isMultiPart bool, partSuffix string, videoFilePath string) (nfoPath string, legacyPaths []string)

ResolveNFOPath builds the expected NFO file path and a list of legacy paths to check for backward compatibility.

Types

type Actor

type Actor struct {
	Name    string `xml:"name"`
	AltName string `xml:"altname,omitempty"` // Alternative/romanized name
	Role    string `xml:"role,omitempty"`
	Order   int    `xml:"order,omitempty"`
	Thumb   string `xml:"thumb,omitempty"`
}

Actor represents an actress/actor in the movie

type AudioStream

type AudioStream struct {
	Codec    string `xml:"codec,omitempty"`
	Language string `xml:"language,omitempty"`
	Channels int    `xml:"channels,omitempty"`
}

AudioStream represents audio stream information

type Config

type Config struct {
	// Actress name formatting
	ActorFirstNameOrder bool   // true = FirstName LastName, false = LastName FirstName
	ActorJapaneseNames  bool   // Use Japanese names if available
	UnknownActress      string // Placeholder for unknown actresses (default: "Unknown")
	UnknownActressMode  string // skip (default) or fallback

	// File naming
	NFOFilenameTemplate string // Template for NFO filename (default: "<ID>.nfo")
	PerFile             bool   // Create separate NFO for each multi-part file (default: false)

	// Optional fields
	ActressAsTag         bool // Copy actress names to <tag> elements
	IncludeOriginalPath  bool // Include source filename in NFO
	IncludeStreamDetails bool // Include video/audio stream information
	IncludeFanart        bool // Include fanart section
	IncludeTrailer       bool // Include trailer URLs

	// Actress role options
	AddGenericRole bool // Add generic "Actress" role to all actresses (default: false)
	AltNameRole    bool // Use alternate name (Japanese) in role field instead of name (default: false)

	// Rating source
	DefaultRatingSource string // Which rating to mark as default (default: "themoviedb")

	// Static NFO fields
	StaticTags    []string // Static tags to add to all NFOs
	StaticTagline string   // Static tagline for all NFOs
	StaticCredits []string // Static credits for all NFOs

	// Database integration
	TagDatabase *database.MovieTagRepository // Optional tag database for per-movie tags

	// Output configuration
	GroupActress bool // Replace multiple actresses with "@Group" (default: false)
}

Config holds NFO generation settings

func ConfigFromAppConfig

func ConfigFromAppConfig(appCfg *config.NFOConfig, outputCfg *config.OutputConfig, metadataCfg *config.MetadataConfig, db *database.DB) *Config

ConfigFromAppConfig converts application config to NFO generator config Optional db parameter enables tag database lookups if metadata config has tag_database.enabled

Example

ExampleConfigFromAppConfig demonstrates config conversion

package main

import (
	"fmt"
)

func main() {
	// Application config would typically come from config.yaml
	appCfg := &struct {
		FilenameTemplate     string
		FirstNameOrder       bool
		ActressLanguageJA    bool
		UnknownActressText   string
		IncludeFanart        bool
		IncludeTrailer       bool
		RatingSource         string
		IncludeStreamDetails bool
	}{
		FilenameTemplate:     "<ID>.nfo",
		FirstNameOrder:       true,
		ActressLanguageJA:    false,
		UnknownActressText:   "Unknown",
		IncludeFanart:        true,
		IncludeTrailer:       true,
		RatingSource:         "themoviedb",
		IncludeStreamDetails: false,
	}

	fmt.Printf("Filename template: %s\n", appCfg.FilenameTemplate)
	fmt.Printf("Use Japanese names: %v\n", appCfg.ActressLanguageJA)
	fmt.Printf("Include fanart: %v\n", appCfg.IncludeFanart)

}
Output:
Filename template: <ID>.nfo
Use Japanese names: false
Include fanart: true

func DefaultConfig

func DefaultConfig() *Config

DefaultConfig returns default NFO generation settings

type DataSource

type DataSource struct {
	Source      string     // "scraper:r18dev", "nfo", "merged", "empty"
	Confidence  float64    // 0.0-1.0 (for future use)
	LastUpdated *time.Time // When this data was last updated
}

DataSource indicates where a field's data came from

type Fanart

type Fanart struct {
	Thumbs []Thumb `xml:"thumb,omitempty"`
}

Fanart contains fanart/background images

type FileInfo

type FileInfo struct {
	StreamDetails *StreamDetails `xml:"streamdetails,omitempty"`
}

FileInfo contains media file technical information

type Generator

type Generator struct {
	// contains filtered or unexported fields
}

Generator creates NFO files from movie metadata

func NewGenerator

func NewGenerator(fs afero.Fs, cfg *Config) *Generator

NewGenerator creates a new NFO generator

func (*Generator) Generate

func (g *Generator) Generate(movie *models.Movie, outputPath string, partSuffix string, videoFilePath string) error

Generate creates an NFO file from a Movie model partSuffix: optional suffix for multi-part files (e.g., "-pt1", "-A") videoFilePath: optional path to video file for extracting stream details (empty string to skip)

Example

ExampleGenerator_Generate demonstrates how to generate an NFO file

package main

import (
	"fmt"
	"os"
	"time"

	"github.com/spf13/afero"

	"github.com/javinizer/javinizer-go/internal/models"
	"github.com/javinizer/javinizer-go/internal/nfo"
)

func main() {
	// Create a movie with metadata
	releaseDate := time.Date(2020, 9, 13, 0, 0, 0, 0, time.UTC)
	movie := &models.Movie{
		ID:          "IPX-535",
		ContentID:   "ipx00535",
		Title:       "Beautiful Day",
		ReleaseDate: &releaseDate,
		Runtime:     120,
		Director:    "Yamada Taro",
		Maker:       "IdeaPocket",
		Label:       "IP Premium",
		Series:      "Beautiful Days",
		RatingScore: 8.5,
		RatingVotes: 100,
		Actresses: []models.Actress{
			{
				FirstName: "Momo",
				LastName:  "Sakura",
			},
		},
		Genres: []models.Genre{
			{Name: "Beautiful Girl"},
			{Name: "Featured Actress"},
		},
	}

	// Create generator with default config
	gen := nfo.NewGenerator(afero.NewOsFs(), nfo.DefaultConfig())

	// Generate NFO file (no part suffix for single file)
	tmpDir := os.TempDir()
	err := gen.Generate(movie, tmpDir, "", "")
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Println("NFO generated successfully")
}
Output:
NFO generated successfully

func (*Generator) GenerateFromScraperResult

func (g *Generator) GenerateFromScraperResult(result *models.ScraperResult, outputPath string) error

GenerateFromScraperResult creates an NFO from a ScraperResult

func (*Generator) MovieToNFO

func (g *Generator) MovieToNFO(movie *models.Movie, videoFilePath string) *Movie

MovieToNFO converts a Movie model to NFO format videoFilePath: optional path to video file for extracting stream details (empty string to skip)

Example

ExampleGenerator_MovieToNFO demonstrates converting a Movie to NFO structure

package main

import (
	"fmt"
	"time"

	"github.com/spf13/afero"

	"github.com/javinizer/javinizer-go/internal/models"
	"github.com/javinizer/javinizer-go/internal/nfo"
)

func main() {
	releaseDate := time.Date(2020, 9, 13, 0, 0, 0, 0, time.UTC)
	movie := &models.Movie{
		ID:          "IPX-535",
		Title:       "Beautiful Day",
		ReleaseDate: &releaseDate,
		Runtime:     120,
		Maker:       "IdeaPocket",
	}

	gen := nfo.NewGenerator(afero.NewOsFs(), nfo.DefaultConfig())
	nfoMovie := gen.MovieToNFO(movie, "")

	fmt.Printf("ID: %s\n", nfoMovie.ID)
	fmt.Printf("Title: %s\n", nfoMovie.Title)
	fmt.Printf("Year: %d\n", nfoMovie.Year)
	fmt.Printf("Runtime: %d\n", nfoMovie.Runtime)
	fmt.Printf("Studio: %s\n", nfoMovie.Studio)

}
Output:
ID: IPX-535
Title: Beautiful Day
Year: 2020
Runtime: 120
Studio: IdeaPocket

func (*Generator) ScraperResultToNFO

func (g *Generator) ScraperResultToNFO(result *models.ScraperResult) *Movie

ScraperResultToNFO converts a ScraperResult to NFO format

func (*Generator) WriteNFO

func (g *Generator) WriteNFO(nfo *Movie, path string) error

WriteNFO writes an NFO structure to a file

type MergeResult

type MergeResult struct {
	Merged     *models.Movie
	Provenance map[string]DataSource
	Stats      MergeStats
}

MergeResult contains the merged movie and metadata about the merge

func MergeMovieMetadataWithOptions

func MergeMovieMetadataWithOptions(scraped, nfo *models.Movie, scalarStrategy MergeStrategy, mergeArrays bool) (*MergeResult, error)

MergeMovieMetadataWithOptions merges scraped and NFO data with granular control scraped: Movie from scraper results nfo: Movie from existing NFO file scalarStrategy: How to handle scalar fields (PreferNFO or PreferScraper) mergeArrays: If true, combine arrays from both sources; if false, use scalarStrategy for arrays too

This provides independent control over: - Scalar fields (title, studio, etc): prefer NFO or prefer scraped - Array fields (actresses, genres): merge both sources or replace

type MergeStats

type MergeStats struct {
	TotalFields       int
	FromScraper       int
	FromNFO           int
	MergedArrays      int
	ConflictsResolved int // Both had data, chose one
	EmptyFields       int
}

MergeStats tracks what happened during the merge

type MergeStrategy

type MergeStrategy int

MergeStrategy defines how to merge metadata from different sources

const (
	// PreferScraper uses scraper data when available, falls back to NFO (default)
	PreferScraper MergeStrategy = iota
	// PreferNFO uses NFO data when available, falls back to scraper (conservative)
	PreferNFO
	// MergeArrays combines arrays from both sources and deduplicates
	MergeArrays
	// PreserveExisting never overwrites non-empty fields (strictest preservation)
	PreserveExisting
	// FillMissingOnly only populates completely empty fields (safe gap filling)
	FillMissingOnly
)

func ParseScalarStrategy

func ParseScalarStrategy(strategy string) MergeStrategy

ParseScalarStrategy converts scalar strategy string to MergeStrategy Valid values: "prefer-scraper", "prefer-nfo", "preserve-existing", "fill-missing-only" (case-insensitive) Returns PreferNFO as default

type Movie

type Movie struct {
	XMLName xml.Name `xml:"movie"`

	// Basic identification
	Title         string `xml:"title,omitempty"`
	OriginalTitle string `xml:"originaltitle,omitempty"`
	SortTitle     string `xml:"sorttitle,omitempty"`

	// IDs
	ID       string     `xml:"id,omitempty"`
	UniqueID []UniqueID `xml:"uniqueid,omitempty"`

	// Plot/Description
	Plot    string `xml:"plot,omitempty"`
	Outline string `xml:"outline,omitempty"` // Short description
	Tagline string `xml:"tagline,omitempty"`

	// Time information
	Runtime     int    `xml:"runtime,omitempty"`     // in minutes
	Year        int    `xml:"year,omitempty"`        // Release year
	ReleaseDate string `xml:"releasedate,omitempty"` // YYYY-MM-DD format
	Premiered   string `xml:"premiered,omitempty"`   // YYYY-MM-DD format

	// Rating
	Ratings Ratings `xml:"ratings,omitempty"`

	// People
	Director string  `xml:"director,omitempty"`
	Actors   []Actor `xml:"actor,omitempty"`
	Credits  string  `xml:"credits,omitempty"` // Writer/credits

	// Production info
	Studio string `xml:"studio,omitempty"` // Production studio
	Maker  string `xml:"maker,omitempty"`  // Custom field for JAV maker
	Label  string `xml:"label,omitempty"`  // Custom field for JAV label
	Set    string `xml:"set,omitempty"`    // Series name

	// Categories
	Genres []string `xml:"genre,omitempty"`
	Tags   []string `xml:"tag,omitempty"`

	// Media
	Thumb   []Thumb `xml:"thumb,omitempty"`
	Fanart  *Fanart `xml:"fanart,omitempty"`
	Trailer string  `xml:"trailer,omitempty"`

	// File info (optional)
	FileInfo     *FileInfo `xml:"fileinfo,omitempty"`
	OriginalPath string    `xml:"originalpath,omitempty"` // Original source filename
}

Movie represents a Kodi-compatible NFO movie structure

type ParseResult

type ParseResult struct {
	Movie    *models.Movie
	Warnings []string // Non-fatal parsing issues
	Source   string   // File path for debugging
	NFOTitle string   // Raw <title> from NFO, used for display title preservation
}

ParseResult contains the parsed NFO data and any warnings

func ParseNFO

func ParseNFO(fs afero.Fs, filePath string) (*ParseResult, error)

ParseNFO parses a Kodi-compatible NFO file into a models.Movie struct Uses streaming XML parsing with a size limit to prevent memory exhaustion.

type Rating

type Rating struct {
	Name    string  `xml:"name,attr,omitempty"`
	Max     int     `xml:"max,attr,omitempty"`
	Default bool    `xml:"default,attr,omitempty"`
	Value   float64 `xml:"value"`
	Votes   int     `xml:"votes,omitempty"`
}

Rating represents a single rating source

type Ratings

type Ratings struct {
	Rating []Rating `xml:"rating,omitempty"`
}

Ratings contains rating information

type StreamDetails

type StreamDetails struct {
	Video    []VideoStream    `xml:"video,omitempty"`
	Audio    []AudioStream    `xml:"audio,omitempty"`
	Subtitle []SubtitleStream `xml:"subtitle,omitempty"`
}

StreamDetails contains video/audio/subtitle stream information

type SubtitleStream

type SubtitleStream struct {
	Language string `xml:"language,omitempty"`
}

SubtitleStream represents subtitle stream information

type Thumb

type Thumb struct {
	Aspect  string `xml:"aspect,attr,omitempty"`  // poster, banner, clearart, etc.
	Preview string `xml:"preview,attr,omitempty"` // Preview URL
	Value   string `xml:",chardata"`              // Main URL
}

Thumb represents a thumbnail/poster image

type UniqueID

type UniqueID struct {
	Type    string `xml:"type,attr"`
	Default bool   `xml:"default,attr,omitempty"`
	Value   string `xml:",chardata"`
}

UniqueID represents a unique identifier with a type

type VideoStream

type VideoStream struct {
	Codec             string  `xml:"codec,omitempty"`
	Aspect            float64 `xml:"aspect,omitempty"`
	Width             int     `xml:"width,omitempty"`
	Height            int     `xml:"height,omitempty"`
	DurationInSeconds int     `xml:"durationinseconds,omitempty"`
	StereoMode        string  `xml:"stereomode,omitempty"`
}

VideoStream represents video stream information

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL