fixed a new prescene layout

This commit is contained in:
Yevhen Odynets
2026-09-27 01:12:52 +03:00
parent 347f525801
commit 39aa8e2e81
7 changed files with 174 additions and 172 deletions
+65 -43
View File
@@ -5,8 +5,6 @@ import (
"strconv"
"strings"
"golang.org/x/exp/slices"
"git.kplus.net.ua/yevhen/resource-scraper/internal/releaseparser"
)
@@ -58,31 +56,31 @@ func Parse(raw string) releaseparser.ParsedRelease {
return result
}
// Where the dictionary-driven tail zone begins: after the resolved
// Artist/Title core (2 tokens by default, 3 with an artist dictionary hit).
// Left core boundary: after the resolved Artist (2 tokens by default,
// 3 with an artist dictionary hit, or fixed by an override).
start := 2
// Level 1: exact-lookup override table fixes Artist/Title only;
// the tail zone is parsed as usual below.
// the tail zone is parsed as usual below. The override is
// authoritative about the core: leftover tokens are plain unknowns,
// not part of the title.
if o, ok := Profile.Overrides[result.Raw]; ok {
result.Artist, result.Title = o.Artist, o.Title
start = overrideCoreLength(tokens, o)
}
// Level 2: artist dictionary — "[0]-[1]" whitelisted as a hyphenated
// artist shifts the core by one token.
switch {
case result.Artist != "":
// An override already fixed the core — leave it alone.
case len(tokens) >= 3 &&
Profile.ArtistTokens[strings.ToUpper(tokens[0]+"-"+tokens[1])]:
result.Artist = tokens[0] + "-" + tokens[1]
result.Title = tokens[2]
start = 3
default:
result.Artist = tokens[0]
if len(tokens) > 1 {
result.Title = tokens[1]
// Level 2: artist dictionary — a hyphenated artist may span ANY number
// of leading tokens ("Shadu-Nar-Mattaru" = 3). The LONGEST whitelisted
// prefix wins; the title is the token right after it. At least one
// title token and one tail token must remain.
artistDict := false
if result.Artist == "" {
for k := len(tokens) - 2; k >= 2; k-- {
if Profile.ArtistTokens[strings.ToUpper(strings.Join(tokens[:k], "-"))] {
artistDict = true
start = k + 1
break
}
}
}
@@ -93,8 +91,41 @@ func Parse(raw string) releaseparser.ParsedRelease {
end = len(tokens) - 2
}
// Right core boundary: the first dictionary-known token after the
// resolved artist. The boundary is contextual, but the title itself
// stays a single token ([1], or [2] with an artist dictionary hit).
// Unknown tokens inside the core region ([start .. tailStart)) are
// REPORTED in UnknownTokens — never eaten, never glued into the title.
tailStart := end
for i := start; i < end; i++ {
if isKnownToken(tokens[i]) {
tailStart = i
break
}
}
// Resolve Artist/Title from the core unless the override already did.
if result.Artist == "" {
if artistDict {
// The artist spans tokens [0 .. start-2]; re-join them.
result.Artist = strings.Join(tokens[:start-1], "-")
} else {
result.Artist = tokens[0]
}
if len(tokens) > start-1 {
result.Title = tokens[start-1]
}
}
// Core-region leftovers (e.g. the rest of a wildcards title after an
// override, or a hyphenated title fragment without an artist entry):
// visible in UnknownTokens for review.
for _, tok := range tokens[start:tailStart] {
result.UnknownTokens = append(result.UnknownTokens, tok)
}
// Level 3: dictionary-driven tail zone, starting after the resolved core.
parseTailZone(start, tokens[start:end], &result)
parseTailZone(tokens[tailStart:end], &result)
score(&result)
return result
@@ -160,12 +191,24 @@ func catalogNumber(token string) string {
return token[1 : len(token)-1]
}
// isKnownToken reports whether the token belongs to the tail zone: it
// matches one of the profile dictionaries or is a parenthesized catalogue
// number. The first such token after the core marks the right core
// boundary.
func isKnownToken(tok string) bool {
up := strings.ToUpper(tok)
return Profile.FormatTokens[up] || Profile.SourceTokens[up] ||
Profile.BitDepthTokens[up] || Profile.ResolutionTokens[up] ||
Profile.EditionTokens[up] || Profile.LanguageMarkers[up] ||
catalogNumber(tok) != ""
}
// parseTailZone resolves middle-zone tokens against the dictionaries.
// The first match wins for each field; catalogue numbers are recognized
// from parenthesized tokens, and anything unrecognized is appended to
// UnknownTokens so that it can be reviewed and curated into a dictionary.
func parseTailZone(start int, zone []string, result *releaseparser.ParsedRelease) {
for i, tok := range zone {
func parseTailZone(zone []string, result *releaseparser.ParsedRelease) {
for _, tok := range zone {
up := strings.ToUpper(tok)
switch {
case Profile.FormatTokens[up]:
@@ -197,13 +240,6 @@ func parseTailZone(start int, zone []string, result *releaseparser.ParsedRelease
default:
if cat := catalogNumber(tok); cat != "" && result.CatalogNo == "" {
result.CatalogNo = cat
} else if hasLaterKnownToken(zone, i) {
// suspected title continuation: the core is probably longer
// than 2 tokens. Not eaten, not unknown — review section.
result.TitleExtra = append(result.TitleExtra, tok)
if !slices.Contains(result.Warnings, "suspected title continuation") {
result.Warnings = append(result.Warnings, "suspected title continuation")
}
} else {
result.UnknownTokens = append(result.UnknownTokens, tok)
}
@@ -220,20 +256,6 @@ func applyType(tok string, result *releaseparser.ParsedRelease) {
}
}
// hasLaterKnownToken reports whether any token after index i in the zone
// matches one of the profile dictionaries.
func hasLaterKnownToken(zone []string, i int) bool {
for _, later := range zone[i+1:] {
up := strings.ToUpper(later)
if Profile.FormatTokens[up] || Profile.SourceTokens[up] ||
Profile.BitDepthTokens[up] || Profile.ResolutionTokens[up] ||
Profile.EditionTokens[up] || Profile.LanguageMarkers[up] {
return true
}
}
return false
}
// overrideCoreLength returns the number of leading tokens covered by the
// override's Artist and Title (each may span several hyphen-separated
// tokens), falling back to the default 2-token core when the raw tokens