release / govulncheck (push) Successful in 20s
release / web (push) Successful in 1m20s
release / go (push) Successful in 1m37s
release / integration (push) Successful in 5m15s
release / android (push) Successful in 5m26s
release / Build signed APK (releases and dev) (push) Successful in 5m45s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 16s
release / Verify release artifacts (tag releases only) (push) Skipped
Rewrites the internal/library comment blocks to rule 32. Task, milestone, decision and rule ids are gone; the incidents behind them are restated as the constraint they left (why a file is deleted before its row, why the missing mark is a mark, why duplicates block on duration). Counts and album names from one library are dropped from production comments; test comments keep the names their fixtures use. The tagReadVersion log keeps each version's before and after, without the measurements. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
70 lines
2.4 KiB
Go
70 lines
2.4 KiB
Go
package library
|
|
|
|
import (
|
|
"path"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// sourceMarker is one sign that a file was ripped from a video rather than
|
|
// taken from a release: words a video title carries and an album track does
|
|
// not.
|
|
//
|
|
// Each pattern is used twice, so it is written in the subset that Postgres's
|
|
// regex flavour and Go's RE2 read the same way: no \b (Postgres spells word
|
|
// boundaries \y), no lookaround, and brackets written as (\(|\[) rather than
|
|
// as a bracket expression. The SQL side matches it case-insensitively with ~*,
|
|
// the Go side with (?i); [^a-zA-Z] is spelled out so neither has to fold case
|
|
// inside a negated class.
|
|
type sourceMarker struct {
|
|
label string
|
|
pattern string
|
|
}
|
|
|
|
// suspectSourceMarkers was calibrated against a real library. Two candidates
|
|
// were left out on purpose:
|
|
// - "live in"/"live at": nearly all from real live albums.
|
|
// - a bare "reaction": it caught Beck's "Chain Reaction". The marker below
|
|
// wants the phrases a reaction video actually uses.
|
|
var suspectSourceMarkers = []sourceMarker{
|
|
{"music video", `official[^a-zA-Z]*(music[^a-zA-Z]*)?video|music[^a-zA-Z]*video`},
|
|
{"official audio", `official[^a-zA-Z]*audio`},
|
|
{"lyric video", `lyrics?[^a-zA-Z]*video`},
|
|
{"visualiser", `visuali[sz]er`},
|
|
{"reaction", `reaction[^a-zA-Z]*(video|mashup)|reacts?[^a-zA-Z]+to[^a-zA-Z]|first[^a-zA-Z]*time[^a-zA-Z]*(hearing|listening)`},
|
|
{"MV", `(^|[^a-zA-Z])(mv|m/v)([^a-zA-Z]|$)`},
|
|
{"[Audio]", `(\(|\[)audio(\)|\])`},
|
|
{"[HD]", `(\(|\[)(hd|hq|4k)(\)|\])`},
|
|
}
|
|
|
|
// SuspectSourcePattern is every marker as one alternation, for the SQL filter
|
|
// behind the admin suspect-sources report.
|
|
var SuspectSourcePattern = func() string {
|
|
parts := make([]string, len(suspectSourceMarkers))
|
|
for i, m := range suspectSourceMarkers {
|
|
parts[i] = "(" + m.pattern + ")"
|
|
}
|
|
return strings.Join(parts, "|")
|
|
}()
|
|
|
|
var suspectSourceRegexps = func() []*regexp.Regexp {
|
|
out := make([]*regexp.Regexp, len(suspectSourceMarkers))
|
|
for i, m := range suspectSourceMarkers {
|
|
out[i] = regexp.MustCompile("(?i)" + m.pattern)
|
|
}
|
|
return out
|
|
}()
|
|
|
|
// SourceMarkersFor returns the labels of every marker the file's basename
|
|
// carries, in list order. Empty for a file the SQL filter would not return.
|
|
func SourceMarkersFor(filePath string) []string {
|
|
base := path.Base(filePath)
|
|
labels := make([]string, 0, 2)
|
|
for i, re := range suspectSourceRegexps {
|
|
if re.MatchString(base) {
|
|
labels = append(labels, suspectSourceMarkers[i].label)
|
|
}
|
|
}
|
|
return labels
|
|
}
|