titles

package
v2.7.0 Latest Latest
Warning

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

Go to latest
Published: Nov 25, 2025 License: GPL-3.0 Imports: 14 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// Fuzzy matching thresholds
	MinSlugLengthForFuzzy   = 5
	FuzzyMatchMaxLengthDiff = 2
	FuzzyMatchMinSimilarity = 0.85

	// Confidence thresholds for result selection
	ConfidenceHigh       = 0.95 // Exact match with perfect/near-perfect tags - immediate return
	ConfidenceAcceptable = 0.70 // Good match with most tags matching - acceptable to launch
	ConfidenceMinimum    = 0.60 // Minimum confidence to launch - below this, error out

	// Match quality scores (base confidence for each strategy, before tag matching adjustment)
	MatchQualityExact           = 1.00 // Perfect slug match
	MatchQualitySecondaryTitle  = 0.92 // Exact secondary title match
	MatchQualityMainTitle       = 0.90 // Main title only match (partial match)
	MatchQualityProgressiveTrim = 0.85 // Progressive word trimming (last resort)

	// Strategy identifiers (order-independent naming)
	StrategyExactMatch            = "strategy_exact_match"
	StrategyPrefixMatch           = "strategy_prefix_match"
	StrategyMainTitleOnly         = "strategy_main_title_only"
	StrategySecondaryTitleExact   = "strategy_secondary_title_exact"
	StrategyTokenSignature        = "strategy_token_signature"
	StrategyJaroWinklerDamerau    = "strategy_jarowinkler_damerau"
	StrategyProgressiveTrim       = "strategy_progressive_trim"
	StrategyExactMatchNoAutoTags  = "strategy_exact_match_no_auto_tags"
	StrategyPrefixMatchNoAutoTags = "strategy_prefix_match_no_auto_tags"
)

Variables

This section is empty.

Functions

func CalculateTagMatchConfidence

func CalculateTagMatchConfidence(result *database.SearchResultWithCursor, tagFilters []database.TagFilter) float64

CalculateTagMatchConfidence calculates a confidence score based on how well a result's tags match the requested tag filters. Returns a value between 0.0 and 1.0, where: - 1.0 = perfect match (all tags match or no tags required) - 0.7-0.9 = good match (most tags match) - 0.5-0.7 = partial match (some tags match or no tags on result) - <0.5 = poor match (few tags match or conflicts exist)

func ExtractCanonicalTagsFromParens

func ExtractCanonicalTagsFromParens(input string) (tagFilters []database.TagFilter, remaining string)

ExtractCanonicalTagsFromParens extracts explicit canonical tag syntax from parentheses. Matches format: (operator?type:value) where operator is -, +, or ~ (optional, defaults to AND) Examples: (-unfinished:beta), (+region:us), (year:1994), (~lang:en)

This is used to support operator-based tag filtering in media titles, separate from filename metadata tags which don't support operators.

Returns the extracted tag filters and the input string with matched tags removed.

func FilterByFileTypePriority

func FilterByFileTypePriority(
	results []database.SearchResultWithCursor,
	launchers []platforms.Launcher,
) []database.SearchResultWithCursor

FilterByFileTypePriority scores results by launcher extension priority. For each result, finds the position of its file extension in the launcher's Extensions array. Lower position = higher priority (earlier in array = preferred file type). Returns only results with the best (lowest) score.

Example: Launcher.Extensions = [".mgl", ".vhd", ".img"]

  • result1.Path = "game.mgl" → score = 0 (best)
  • result2.Path = "game.vhd" → score = 1
  • Returns: [result1]

When multiple launchers exist for a system, each result is scored against ALL launchers and the best score across any launcher is used.

func FilterByPreferredLanguages

func FilterByPreferredLanguages(
	results []database.SearchResultWithCursor, preferredLangs []string,
) []database.SearchResultWithCursor

FilterByPreferredLanguages prefers configured languages over others

func FilterByPreferredRegions

func FilterByPreferredRegions(
	results []database.SearchResultWithCursor, preferredRegions []string,
) []database.SearchResultWithCursor

FilterByPreferredRegions prefers configured regions over others

func FilterByTags

func FilterByTags(
	results []database.SearchResultWithCursor, tagFilters []database.TagFilter,
) []database.SearchResultWithCursor

FilterByTags filters results that match all specified tags

func FilterOutRereleases

func FilterOutRereleases(results []database.SearchResultWithCursor) []database.SearchResultWithCursor

FilterOutRereleases removes re-releases and reboxed versions

func FilterOutVariants

func FilterOutVariants(results []database.SearchResultWithCursor) []database.SearchResultWithCursor

FilterOutVariants removes demos, betas, prototypes, hacks, and other variants

func HasAllTags

func HasAllTags(result *database.SearchResultWithCursor, tagFilters []database.TagFilter) bool

HasAllTags checks if a result matches the specified tag filters Respects operator logic: AND (must have), NOT (must not have), OR (at least one)

func IsRerelease

func IsRerelease(result *database.SearchResultWithCursor) bool

IsRerelease checks if a result is a re-release

func IsVariant

func IsVariant(result *database.SearchResultWithCursor) bool

IsVariant checks if a result is a variant (demo, beta, prototype, hack, etc.)

func MergeTagFilters

func MergeTagFilters(extracted, advArgs []database.TagFilter) []database.TagFilter

MergeTagFilters merges extracted tags with advanced args tags. Advanced args tags take precedence - if the same tag type exists in both, the advanced args value is used. Returns nil if the result would be empty.

func SelectBestResult

func SelectBestResult(
	results []database.SearchResultWithCursor,
	tagFilters []database.TagFilter,
	cfg *config.Instance,
	matchQuality float64,
	launchers []platforms.Launcher,
) (result database.SearchResultWithCursor, confidence float64)

SelectBestResult implements intelligent selection when multiple media match a slug. Returns the best result and a confidence score (0.0-1.0) based on match quality and tag match quality. matchQuality should be 1.0 for exact matches, or the similarity score (0.0-1.0) for fuzzy matches. launchers is used to prioritize file types based on launcher extension order (earlier = better).

func TryMainTitleOnly

func TryMainTitleOnly(
	ctx context.Context,
	gamesdb database.MediaDBI,
	systemID string,
	slug string,
	matchInfo GameMatchInfo,
	tagFilters []database.TagFilter,
	mediaType slugs.MediaType,
) ([]database.SearchResultWithCursor, string, error)

TryMainTitleOnly attempts main title-only search when query and DB have mismatched secondary titles. Handles two cases: 1. Query has secondary title, DB doesn't: "Some Game: The Next Gen" → "Some Game" (exact match on main) 2. Query lacks secondary title, DB has one: "Some Game" → "Some Game: The Next Gen" (partial match) Expects matchInfo to be pre-generated to avoid redundant computation.

func TryProgressiveTrim

func TryProgressiveTrim(
	ctx context.Context,
	gamesdb database.MediaDBI,
	systemID string,
	gameName string,
	slug string,
	tagFilters []database.TagFilter,
	mediaType slugs.MediaType,
) ([]database.SearchResultWithCursor, string, error)

TryProgressiveTrim attempts Strategy 5: Progressive trim candidates (last resort). Handles overly-verbose queries by progressively trimming words from the end. Uses a single IN query for all candidates (max depth: 3).

func TrySecondaryTitleExact

func TrySecondaryTitleExact(
	ctx context.Context,
	gamesdb database.MediaDBI,
	systemID string,
	slug string,
	matchInfo GameMatchInfo,
	tagFilters []database.TagFilter,
	mediaType slugs.MediaType,
) ([]database.SearchResultWithCursor, string, error)

TrySecondaryTitleExact attempts secondary title-only matching when query and DB have mismatched secondary titles. Handles two cases: 1. Input has secondary title, DB doesn't: "Legend of Zelda: Ocarina of Time" → "Ocarina of Time" (exact match) 2. Input lacks secondary title, DB has one: "Ocarina of Time" → "Legend of Zelda: Ocarina of Time" (partial match) Expects matchInfo to be pre-generated to avoid redundant computation.

func TryWithoutAutoTags

func TryWithoutAutoTags(
	ctx context.Context,
	gamesdb database.MediaDBI,
	systemID string,
	slug string,
	autoExtractedTags []database.TagFilter,
	advArgsTagFilters []database.TagFilter,
) ([]database.SearchResultWithCursor, string, error)

TryWithoutAutoTags attempts fallback strategy: retry without auto-extracted tags

Types

type FuzzyMatchResult

type FuzzyMatchResult struct {
	Strategy   string
	Results    []database.SearchResultWithCursor
	Similarity float64
}

FuzzyMatchResult contains the results of a fuzzy matching strategy.

func TryAdvancedFuzzyMatching

func TryAdvancedFuzzyMatching(
	ctx context.Context,
	gamesdb database.MediaDBI,
	systemID string,
	gameName string,
	slug string,
	tagFilters []database.TagFilter,
	mediaType slugs.MediaType,
) (FuzzyMatchResult, error)

TryAdvancedFuzzyMatching attempts Strategy 3: Advanced fuzzy matching with single prefilter. Uses a single prefilter query, then tries three algorithms in sequence: 1. Token signature (word-order independent) 2. Jaro-Winkler (typo tolerance, prefix matching) 3. Damerau-Levenshtein tie-breaking (transposition handling)

type GameMatchInfo

type GameMatchInfo struct {
	CanonicalSlug      string
	MainTitleSlug      string
	SecondaryTitleSlug string
	OriginalInput      string
	HasSecondaryTitle  bool
	HasLeadingArticle  bool
}

GameMatchInfo contains metadata extracted from a game title for intelligent matching. This structure supports multi-strategy resolution where the canonical slug may not match but fallback strategies (e.g., matching just the main title) can be attempted.

func GenerateMatchInfo

func GenerateMatchInfo(mediaType slugs.MediaType, title string) GameMatchInfo

GenerateMatchInfo analyzes a game title and extracts matching metadata. It detects secondary titles (using colon, " - ", or "'s " delimiters), leading articles, and generates slugs for both the full title and its components.

The mediaType parameter should match the MediaType of the system being queried, ensuring consistent slugification between indexing and resolution.

Example:

info := GenerateMatchInfo(slugs.MediaTypeGame, "The Legend of Zelda: Link's Awakening")
// info.CanonicalSlug = "legendofzeldalinksawakening"
// info.MainTitleSlug = "legendofzelda"
// info.SecondaryTitleSlug = "linksawakening"
// info.HasSecondaryTitle = true
// info.HasLeadingArticle = true

type ProgressiveTrimCandidate

type ProgressiveTrimCandidate struct {
	Slug          string
	WordCount     int
	IsExactMatch  bool
	IsPrefixMatch bool
}

ProgressiveTrimCandidate represents a progressively trimmed title variation for matching.

func GenerateProgressiveTrimCandidates

func GenerateProgressiveTrimCandidates(
	mediaType slugs.MediaType, title string, maxDepth int,
) []ProgressiveTrimCandidate

GenerateProgressiveTrimCandidates creates progressively trimmed variations of a title. This handles overly-verbose queries by removing words from the end one at a time. Useful for matching long descriptive titles against shorter canonical names.

The mediaType parameter should match the MediaType of the system being queried, ensuring consistent slugification between indexing and resolution.

Example:

GenerateProgressiveTrimCandidates(slugs.MediaTypeGame, "Super Mario World Special Edition", 3)
// Returns candidates for:
// - "Super Mario World Special Edition" (full)
// - "Super Mario World Special"
// - "Super Mario World"
// - "Super Mario" (stopped - only 3 iterations with maxDepth=3)

type TiebreakerScore

type TiebreakerScore struct {
	// NumericSuffix: Penalty for OS duplicate/copy markers like " (1)", " - Copy"
	// 0 = clean filename, 1 = has duplicate marker
	NumericSuffix int

	// PathDepth: Number of directory separators in path
	// Lower = more curated (files in root/organized folders)
	PathDepth int

	// CharDensity: Count of "messy" filename indicators (__, excessive dots, mixed separators)
	// Lower = cleaner filename
	CharDensity int

	// NameLength: Length of filename
	// Lower = simpler/cleaner name (after quality checks above)
	NameLength int
}

TiebreakerScore represents a multi-component score for tie-breaking when all other selection criteria are exhausted. Components are compared in order (first to last). Lower values are preferred (better quality).

func CalculateTiebreakerScore

func CalculateTiebreakerScore(result *database.SearchResultWithCursor) TiebreakerScore

CalculateTiebreakerScore computes a quality score for a search result based on filename and path characteristics. Used as final tie-breaker when all other selection criteria are exhausted.

func (TiebreakerScore) Compare

func (a TiebreakerScore) Compare(b TiebreakerScore) int

Compare compares two TiebreakerScores. Returns: -1 if a is better (lower) than b 0 if a equals b 1 if a is worse (higher) than b

Jump to

Keyboard shortcuts

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