mirror of
https://github.com/stashapp/stash.git
synced 2026-05-09 05:05:29 +02:00
Cleaning up comments
This commit is contained in:
parent
01cf70aa8f
commit
c3922f66cf
21 changed files with 6 additions and 794 deletions
|
|
@ -1,5 +1,3 @@
|
|||
# TODO(audio): add audioCreate, audioUpdate, audioDestroy, audiosDestroy
|
||||
|
||||
"The query root for this schema"
|
||||
type Query {
|
||||
# Filters
|
||||
|
|
|
|||
|
|
@ -47,8 +47,6 @@ type Audio {
|
|||
|
||||
files: [AudioFile!]!
|
||||
paths: AudioPathsType! # Resolver
|
||||
# TODO(future|audio_markers): add in audio markers
|
||||
# audio_markers: [AudioMarker!]!
|
||||
studio: Studio
|
||||
groups: [AudioGroup!]!
|
||||
tags: [Tag!]!
|
||||
|
|
|
|||
|
|
@ -125,7 +125,6 @@ type ImageFile implements BaseFile {
|
|||
union VisualFile = VideoFile | ImageFile
|
||||
|
||||
type AudioFile implements BaseFile {
|
||||
# TODO(audio): edit this
|
||||
id: ID!
|
||||
path: String!
|
||||
basename: String!
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
// TODO(audio): update this file
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
// TODO(audio): update this file
|
||||
// Package identify provides the scene identification functionality for the application.
|
||||
// The identify functionality uses scene scrapers to identify a given scene and
|
||||
// set its metadata based on the scraped data.
|
||||
|
|
|
|||
|
|
@ -712,7 +712,7 @@ func getScanHandlers(options ScanMetadataInput, taskQueue *job.TaskQueue, progre
|
|||
CreatorUpdater: r.Audio,
|
||||
CaptionUpdater: r.File,
|
||||
PluginCache: pluginCache,
|
||||
FileNamingAlgorithm: c.GetVideoFileNamingAlgorithm(),
|
||||
FileNamingAlgorithm: c.GetAudioFileNamingAlgorithm(),
|
||||
Paths: mgr.Paths,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
"github.com/stashapp/stash/pkg/file"
|
||||
file_audio "github.com/stashapp/stash/pkg/file/audio"
|
||||
"github.com/stashapp/stash/pkg/fsutil"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
|
|
@ -96,17 +95,6 @@ func (s *Service) deleteFiles(ctx context.Context, audio *models.Audio, fileDele
|
|||
if err := file.Destroy(ctx, s.File, f, fileDeleter.Deleter, deleteFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// don't delete files in zip archives
|
||||
if f.ZipFileID == nil {
|
||||
funscriptPath := file_audio.GetFunscriptPath(f.Path)
|
||||
funscriptExists, _ := fsutil.FileExists(funscriptPath)
|
||||
if funscriptExists {
|
||||
if err := fileDeleter.Files([]string{funscriptPath}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/models/json"
|
||||
|
|
@ -171,18 +170,6 @@ func GetDependentGroupIDs(ctx context.Context, audio *models.Audio) ([]int, erro
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
func getDecimalString(num float64) string {
|
||||
if num == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
precision := getPrecision(num)
|
||||
if precision == 0 {
|
||||
precision = 1
|
||||
}
|
||||
return fmt.Sprintf("%."+strconv.Itoa(precision)+"f", num)
|
||||
}
|
||||
|
||||
func getPrecision(num float64) int {
|
||||
if num == 0 {
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -32,11 +32,7 @@ const (
|
|||
noGroupsID = 13
|
||||
errFindGroupID = 15
|
||||
|
||||
noMarkersID = 16
|
||||
errMarkersID = 17
|
||||
errFindPrimaryTagID = 18
|
||||
errFindByMarkerID = 19
|
||||
errCustomFieldsID = 20
|
||||
errCustomFieldsID = 20
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -1,722 +0,0 @@
|
|||
// TODO(audio): update this file
|
||||
|
||||
package audio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/studio"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/tag"
|
||||
)
|
||||
|
||||
type parserField struct {
|
||||
field string
|
||||
fieldRegex *regexp.Regexp
|
||||
regex string
|
||||
isFullDateField bool
|
||||
isCaptured bool
|
||||
}
|
||||
|
||||
func newParserField(field string, regex string, captured bool) parserField {
|
||||
ret := parserField{
|
||||
field: field,
|
||||
isFullDateField: false,
|
||||
isCaptured: captured,
|
||||
}
|
||||
|
||||
ret.fieldRegex, _ = regexp.Compile(`\{` + ret.field + `\}`)
|
||||
|
||||
regexStr := regex
|
||||
|
||||
if captured {
|
||||
regexStr = "(" + regexStr + ")"
|
||||
}
|
||||
ret.regex = regexStr
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func newFullDateParserField(field string, regex string) parserField {
|
||||
ret := newParserField(field, regex, true)
|
||||
ret.isFullDateField = true
|
||||
return ret
|
||||
}
|
||||
|
||||
func (f parserField) replaceInPattern(pattern string) string {
|
||||
return string(f.fieldRegex.ReplaceAllString(pattern, f.regex))
|
||||
}
|
||||
|
||||
var validFields map[string]parserField
|
||||
var escapeCharRE *regexp.Regexp
|
||||
var capitalizeTitleRE *regexp.Regexp
|
||||
var multiWSRE *regexp.Regexp
|
||||
var delimiterRE *regexp.Regexp
|
||||
|
||||
func compileREs() {
|
||||
const escapeCharPattern = `([\-\.\(\)\[\]])`
|
||||
escapeCharRE = regexp.MustCompile(escapeCharPattern)
|
||||
|
||||
const capitaliseTitlePattern = `(?:^| )\w`
|
||||
capitalizeTitleRE = regexp.MustCompile(capitaliseTitlePattern)
|
||||
|
||||
const multiWSPattern = ` {2,}`
|
||||
multiWSRE = regexp.MustCompile(multiWSPattern)
|
||||
|
||||
const delimiterPattern = `(?:\.|-|_)`
|
||||
delimiterRE = regexp.MustCompile(delimiterPattern)
|
||||
}
|
||||
|
||||
func initParserFields() {
|
||||
if validFields != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ret := make(map[string]parserField)
|
||||
|
||||
ret["title"] = newParserField("title", ".*", true)
|
||||
ret["ext"] = newParserField("ext", ".*$", false)
|
||||
|
||||
ret["d"] = newParserField("d", `(?:\.|-|_)`, false)
|
||||
ret["rating"] = newParserField("rating", `\d`, true)
|
||||
ret["rating100"] = newParserField("rating100", `\d`, true)
|
||||
ret["performer"] = newParserField("performer", ".*", true)
|
||||
ret["studio"] = newParserField("studio", ".*", true)
|
||||
ret["movie"] = newParserField("movie", ".*", true)
|
||||
ret["tag"] = newParserField("tag", ".*", true)
|
||||
|
||||
// date fields
|
||||
ret["date"] = newParserField("date", `\d{4}-\d{2}-\d{2}`, true)
|
||||
ret["yyyy"] = newParserField("yyyy", `\d{4}`, true)
|
||||
ret["yy"] = newParserField("yy", `\d{2}`, true)
|
||||
ret["mm"] = newParserField("mm", `\d{2}`, true)
|
||||
ret["mmm"] = newParserField("mmm", `\w{3}`, true)
|
||||
ret["dd"] = newParserField("dd", `\d{2}`, true)
|
||||
ret["yyyymmdd"] = newFullDateParserField("yyyymmdd", `\d{8}`)
|
||||
ret["yymmdd"] = newFullDateParserField("yymmdd", `\d{6}`)
|
||||
ret["ddmmyyyy"] = newFullDateParserField("ddmmyyyy", `\d{8}`)
|
||||
ret["ddmmyy"] = newFullDateParserField("ddmmyy", `\d{6}`)
|
||||
ret["mmddyyyy"] = newFullDateParserField("mmddyyyy", `\d{8}`)
|
||||
ret["mmddyy"] = newFullDateParserField("mmddyy", `\d{6}`)
|
||||
|
||||
validFields = ret
|
||||
}
|
||||
|
||||
func replacePatternWithRegex(pattern string, ignoreWords []string) string {
|
||||
initParserFields()
|
||||
|
||||
for _, field := range validFields {
|
||||
pattern = field.replaceInPattern(pattern)
|
||||
}
|
||||
|
||||
ignoreClause := getIgnoreClause(ignoreWords)
|
||||
ignoreField := newParserField("i", ignoreClause, false)
|
||||
pattern = ignoreField.replaceInPattern(pattern)
|
||||
|
||||
return pattern
|
||||
}
|
||||
|
||||
type parseMapper struct {
|
||||
fields []string
|
||||
regexString string
|
||||
regex *regexp.Regexp
|
||||
}
|
||||
|
||||
func getIgnoreClause(ignoreFields []string) string {
|
||||
if len(ignoreFields) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var ignoreClauses []string
|
||||
|
||||
for _, v := range ignoreFields {
|
||||
newVal := string(escapeCharRE.ReplaceAllString(v, `\$1`))
|
||||
newVal = strings.TrimSpace(newVal)
|
||||
newVal = "(?:" + newVal + ")"
|
||||
ignoreClauses = append(ignoreClauses, newVal)
|
||||
}
|
||||
|
||||
return "(?:" + strings.Join(ignoreClauses, "|") + ")"
|
||||
}
|
||||
|
||||
func newParseMapper(pattern string, ignoreFields []string) (*parseMapper, error) {
|
||||
ret := &parseMapper{}
|
||||
|
||||
// escape control characters
|
||||
regex := escapeCharRE.ReplaceAllString(pattern, `\$1`)
|
||||
|
||||
// replace {} with wildcard
|
||||
braceRE := regexp.MustCompile(`\{\}`)
|
||||
regex = braceRE.ReplaceAllString(regex, ".*")
|
||||
|
||||
// replace all known fields with applicable regexes
|
||||
regex = replacePatternWithRegex(regex, ignoreFields)
|
||||
|
||||
ret.regexString = regex
|
||||
|
||||
// make case insensitive
|
||||
regex = "(?i)" + regex
|
||||
|
||||
var err error
|
||||
|
||||
ret.regex, err = regexp.Compile(regex)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// find invalid fields
|
||||
invalidRE := regexp.MustCompile(`\{[A-Za-z]+\}`)
|
||||
foundInvalid := invalidRE.FindAllString(regex, -1)
|
||||
if len(foundInvalid) > 0 {
|
||||
return nil, errors.New("Invalid fields: " + strings.Join(foundInvalid, ", "))
|
||||
}
|
||||
|
||||
fieldExtractor := regexp.MustCompile(`\{([A-Za-z]+)\}`)
|
||||
|
||||
result := fieldExtractor.FindAllStringSubmatch(pattern, -1)
|
||||
|
||||
var fields []string
|
||||
for _, v := range result {
|
||||
field := v[1]
|
||||
|
||||
// only add to fields if it is captured
|
||||
parserField, found := validFields[field]
|
||||
if found && parserField.isCaptured {
|
||||
fields = append(fields, field)
|
||||
}
|
||||
}
|
||||
|
||||
ret.fields = fields
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
type audioHolder struct {
|
||||
audio *models.Audio
|
||||
result *models.Audio
|
||||
yyyy string
|
||||
mm string
|
||||
dd string
|
||||
performers []string
|
||||
groups []string
|
||||
studio string
|
||||
tags []string
|
||||
}
|
||||
|
||||
func newAudioHolder(audio *models.Audio) *audioHolder {
|
||||
audioCopy := models.Audio{
|
||||
ID: audio.ID,
|
||||
Files: audio.Files,
|
||||
// Checksum: audio.Checksum,
|
||||
// Path: audio.Path,
|
||||
}
|
||||
ret := audioHolder{
|
||||
audio: audio,
|
||||
result: &audioCopy,
|
||||
}
|
||||
|
||||
return &ret
|
||||
}
|
||||
|
||||
func validateRating(rating int) bool {
|
||||
return rating >= 1 && rating <= 5
|
||||
}
|
||||
|
||||
func validateRating100(rating100 int) bool {
|
||||
return rating100 >= 1 && rating100 <= 100
|
||||
}
|
||||
|
||||
// returns nil if invalid
|
||||
func parseDate(dateStr string) *models.Date {
|
||||
splits := strings.Split(dateStr, "-")
|
||||
if len(splits) != 3 {
|
||||
return nil
|
||||
}
|
||||
|
||||
year, _ := strconv.Atoi(splits[0])
|
||||
month, _ := strconv.Atoi(splits[1])
|
||||
d, _ := strconv.Atoi(splits[2])
|
||||
|
||||
// assume year must be between 1900 and 2100
|
||||
if year < 1900 || year > 2100 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if month < 1 || month > 12 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// not checking individual months to ensure date is in the correct range
|
||||
if d < 1 || d > 31 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ret, err := models.ParseDate(dateStr)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &ret
|
||||
}
|
||||
|
||||
func (h *audioHolder) setDate(field *parserField, value string) {
|
||||
yearIndex := 0
|
||||
yearLength := len(strings.Split(field.field, "y")) - 1
|
||||
dateIndex := 0
|
||||
monthIndex := 0
|
||||
|
||||
switch field.field {
|
||||
case "yyyymmdd", "yymmdd":
|
||||
monthIndex = yearLength
|
||||
dateIndex = monthIndex + 2
|
||||
case "ddmmyyyy", "ddmmyy":
|
||||
monthIndex = 2
|
||||
yearIndex = monthIndex + 2
|
||||
case "mmddyyyy", "mmddyy":
|
||||
dateIndex = monthIndex + 2
|
||||
yearIndex = dateIndex + 2
|
||||
}
|
||||
|
||||
yearValue := value[yearIndex : yearIndex+yearLength]
|
||||
monthValue := value[monthIndex : monthIndex+2]
|
||||
dateValue := value[dateIndex : dateIndex+2]
|
||||
|
||||
fullDate := yearValue + "-" + monthValue + "-" + dateValue
|
||||
|
||||
// ensure the date is valid
|
||||
// only set if new value is different from the old
|
||||
newDate := parseDate(fullDate)
|
||||
if newDate != nil && h.audio.Date != nil && *h.audio.Date != *newDate {
|
||||
h.result.Date = newDate
|
||||
}
|
||||
}
|
||||
|
||||
func mmmToMonth(mmm string) string {
|
||||
format := "02-Jan-2006"
|
||||
dateStr := "01-" + mmm + "-2000"
|
||||
t, err := time.Parse(format, dateStr)
|
||||
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// expect month in two-digit format
|
||||
format = "01-02-2006"
|
||||
return t.Format(format)[0:2]
|
||||
}
|
||||
|
||||
func (h *audioHolder) setField(field parserField, value interface{}) {
|
||||
if field.isFullDateField {
|
||||
h.setDate(&field, value.(string))
|
||||
return
|
||||
}
|
||||
|
||||
switch field.field {
|
||||
case "title":
|
||||
v := value.(string)
|
||||
h.result.Title = v
|
||||
case "date":
|
||||
h.result.Date = parseDate(value.(string))
|
||||
case "rating":
|
||||
rating, _ := strconv.Atoi(value.(string))
|
||||
if validateRating(rating) {
|
||||
// convert to 1-100 scale
|
||||
rating = models.Rating5To100(rating)
|
||||
h.result.Rating = &rating
|
||||
}
|
||||
case "rating100":
|
||||
rating, _ := strconv.Atoi(value.(string))
|
||||
if validateRating100(rating) {
|
||||
h.result.Rating = &rating
|
||||
}
|
||||
case "performer":
|
||||
// add performer to list
|
||||
h.performers = append(h.performers, value.(string))
|
||||
case "studio":
|
||||
h.studio = value.(string)
|
||||
case "movie":
|
||||
h.groups = append(h.groups, value.(string))
|
||||
case "tag":
|
||||
h.tags = append(h.tags, value.(string))
|
||||
case "yyyy":
|
||||
h.yyyy = value.(string)
|
||||
case "yy":
|
||||
v := value.(string)
|
||||
v = "20" + v
|
||||
h.yyyy = v
|
||||
case "mmm":
|
||||
h.mm = mmmToMonth(value.(string))
|
||||
case "mm":
|
||||
h.mm = value.(string)
|
||||
case "dd":
|
||||
h.dd = value.(string)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *audioHolder) postParse() {
|
||||
// set the date if the components are set
|
||||
if h.yyyy != "" && h.mm != "" && h.dd != "" {
|
||||
fullDate := h.yyyy + "-" + h.mm + "-" + h.dd
|
||||
h.setField(validFields["date"], fullDate)
|
||||
}
|
||||
}
|
||||
|
||||
func (m parseMapper) parse(audio *models.Audio) *audioHolder {
|
||||
|
||||
// #302 - if the pattern includes a path separator, then include the entire
|
||||
// audio path in the match. Otherwise, use the default behaviour of just
|
||||
// the file's basename
|
||||
// must be double \ because of the regex escaping
|
||||
filename := filepath.Base(audio.Path)
|
||||
if strings.Contains(m.regexString, `\\`) || strings.Contains(m.regexString, "/") {
|
||||
filename = audio.Path
|
||||
}
|
||||
|
||||
result := m.regex.FindStringSubmatch(filename)
|
||||
|
||||
if len(result) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
initParserFields()
|
||||
|
||||
audioHolder := newAudioHolder(audio)
|
||||
|
||||
for index, match := range result {
|
||||
if index == 0 {
|
||||
// skip entire match
|
||||
continue
|
||||
}
|
||||
|
||||
field := m.fields[index-1]
|
||||
parserField, found := validFields[field]
|
||||
if found {
|
||||
audioHolder.setField(parserField, match)
|
||||
}
|
||||
}
|
||||
|
||||
audioHolder.postParse()
|
||||
|
||||
return audioHolder
|
||||
}
|
||||
|
||||
type FilenameParser struct {
|
||||
Pattern string
|
||||
ParserInput models.AudioParserInput
|
||||
Filter *models.FindFilterType
|
||||
whitespaceRE *regexp.Regexp
|
||||
repository FilenameParserRepository
|
||||
performerCache map[string]*models.Performer
|
||||
studioCache map[string]*models.Studio
|
||||
groupCache map[string]*models.Group
|
||||
tagCache map[string]*models.Tag
|
||||
}
|
||||
|
||||
func NewFilenameParser(filter *models.FindFilterType, config models.AudioParserInput, repo FilenameParserRepository) *FilenameParser {
|
||||
p := &FilenameParser{
|
||||
Pattern: *filter.Q,
|
||||
ParserInput: config,
|
||||
Filter: filter,
|
||||
repository: repo,
|
||||
}
|
||||
|
||||
p.performerCache = make(map[string]*models.Performer)
|
||||
p.studioCache = make(map[string]*models.Studio)
|
||||
p.groupCache = make(map[string]*models.Group)
|
||||
p.tagCache = make(map[string]*models.Tag)
|
||||
|
||||
p.initWhiteSpaceRegex()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *FilenameParser) initWhiteSpaceRegex() {
|
||||
compileREs()
|
||||
|
||||
wsChars := ""
|
||||
if p.ParserInput.WhitespaceCharacters != nil {
|
||||
wsChars = *p.ParserInput.WhitespaceCharacters
|
||||
wsChars = strings.TrimSpace(wsChars)
|
||||
}
|
||||
|
||||
if len(wsChars) > 0 {
|
||||
wsRegExp := escapeCharRE.ReplaceAllString(wsChars, `\$1`)
|
||||
wsRegExp = "[" + wsRegExp + "]"
|
||||
p.whitespaceRE = regexp.MustCompile(wsRegExp)
|
||||
}
|
||||
}
|
||||
|
||||
type FilenameParserRepository struct {
|
||||
Audio models.AudioQueryer
|
||||
Performer PerformerNamesFinder
|
||||
Studio models.StudioQueryer
|
||||
Group GroupNameFinder
|
||||
Tag models.TagNameFinder
|
||||
}
|
||||
|
||||
func NewFilenameParserRepository(repo models.Repository) FilenameParserRepository {
|
||||
return FilenameParserRepository{
|
||||
Audio: repo.Audio,
|
||||
Performer: repo.Performer,
|
||||
Studio: repo.Studio,
|
||||
Group: repo.Group,
|
||||
Tag: repo.Tag,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *FilenameParser) Parse(ctx context.Context) ([]*models.AudioParserResult, int, error) {
|
||||
// perform the query to find the audios
|
||||
mapper, err := newParseMapper(p.Pattern, p.ParserInput.IgnoreWords)
|
||||
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
audioFilter := &models.AudioFilterType{
|
||||
Path: &models.StringCriterionInput{
|
||||
Modifier: models.CriterionModifierMatchesRegex,
|
||||
Value: "(?i)" + mapper.regexString,
|
||||
},
|
||||
}
|
||||
|
||||
if p.ParserInput.IgnoreOrganized != nil && *p.ParserInput.IgnoreOrganized {
|
||||
organized := false
|
||||
audioFilter.Organized = &organized
|
||||
}
|
||||
|
||||
p.Filter.Q = nil
|
||||
|
||||
audios, total, err := QueryWithCount(ctx, p.repository.Audio, audioFilter, p.Filter)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
ret := p.parseAudios(ctx, audios, mapper)
|
||||
|
||||
return ret, total, nil
|
||||
}
|
||||
|
||||
func (p *FilenameParser) parseAudios(ctx context.Context, audios []*models.Audio, mapper *parseMapper) []*models.AudioParserResult {
|
||||
var ret []*models.AudioParserResult
|
||||
for _, audio := range audios {
|
||||
audioHolder := mapper.parse(audio)
|
||||
|
||||
if audioHolder != nil {
|
||||
r := &models.AudioParserResult{
|
||||
Audio: audio,
|
||||
}
|
||||
p.setParserResult(ctx, *audioHolder, r)
|
||||
|
||||
ret = append(ret, r)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p FilenameParser) replaceWhitespaceCharacters(value string) string {
|
||||
if p.whitespaceRE != nil {
|
||||
value = p.whitespaceRE.ReplaceAllString(value, " ")
|
||||
// remove consecutive spaces
|
||||
value = multiWSRE.ReplaceAllString(value, " ")
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
type PerformerNamesFinder interface {
|
||||
FindByNames(ctx context.Context, names []string, nocase bool) ([]*models.Performer, error)
|
||||
}
|
||||
|
||||
func (p *FilenameParser) queryPerformer(ctx context.Context, qb PerformerNamesFinder, performerName string) *models.Performer {
|
||||
// massage the performer name
|
||||
performerName = delimiterRE.ReplaceAllString(performerName, " ")
|
||||
|
||||
// check cache first
|
||||
if ret, found := p.performerCache[performerName]; found {
|
||||
return ret
|
||||
}
|
||||
|
||||
// perform an exact match and grab the first
|
||||
performers, _ := qb.FindByNames(ctx, []string{performerName}, true)
|
||||
|
||||
var ret *models.Performer
|
||||
if len(performers) > 0 {
|
||||
ret = performers[0]
|
||||
}
|
||||
|
||||
// add result to cache
|
||||
p.performerCache[performerName] = ret
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p *FilenameParser) queryStudio(ctx context.Context, qb models.StudioQueryer, studioName string) *models.Studio {
|
||||
// massage the performer name
|
||||
studioName = delimiterRE.ReplaceAllString(studioName, " ")
|
||||
|
||||
// check cache first
|
||||
if ret, found := p.studioCache[studioName]; found {
|
||||
return ret
|
||||
}
|
||||
|
||||
ret, _ := studio.ByName(ctx, qb, studioName)
|
||||
|
||||
// try to match on alias
|
||||
if ret == nil {
|
||||
ret, _ = studio.ByAlias(ctx, qb, studioName)
|
||||
}
|
||||
|
||||
// add result to cache
|
||||
p.studioCache[studioName] = ret
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
type GroupNameFinder interface {
|
||||
FindByName(ctx context.Context, name string, nocase bool) (*models.Group, error)
|
||||
}
|
||||
|
||||
func (p *FilenameParser) queryGroup(ctx context.Context, qb GroupNameFinder, groupName string) *models.Group {
|
||||
// massage the group name
|
||||
groupName = delimiterRE.ReplaceAllString(groupName, " ")
|
||||
|
||||
// check cache first
|
||||
if ret, found := p.groupCache[groupName]; found {
|
||||
return ret
|
||||
}
|
||||
|
||||
ret, _ := qb.FindByName(ctx, groupName, true)
|
||||
|
||||
// add result to cache
|
||||
p.groupCache[groupName] = ret
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p *FilenameParser) queryTag(ctx context.Context, qb models.TagNameFinder, tagName string) *models.Tag {
|
||||
// massage the tag name
|
||||
tagName = delimiterRE.ReplaceAllString(tagName, " ")
|
||||
|
||||
// check cache first
|
||||
if ret, found := p.tagCache[tagName]; found {
|
||||
return ret
|
||||
}
|
||||
|
||||
// match tag name exactly
|
||||
ret, _ := tag.ByName(ctx, qb, tagName)
|
||||
|
||||
// try to match on alias
|
||||
if ret == nil {
|
||||
ret, _ = tag.ByAlias(ctx, qb, tagName)
|
||||
}
|
||||
|
||||
// add result to cache
|
||||
p.tagCache[tagName] = ret
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p *FilenameParser) setPerformers(ctx context.Context, qb PerformerNamesFinder, h audioHolder, result *models.AudioParserResult) {
|
||||
// query for each performer
|
||||
performersSet := make(map[int]bool)
|
||||
for _, performerName := range h.performers {
|
||||
if performerName != "" {
|
||||
performer := p.queryPerformer(ctx, qb, performerName)
|
||||
if performer != nil {
|
||||
if _, found := performersSet[performer.ID]; !found {
|
||||
result.PerformerIds = append(result.PerformerIds, strconv.Itoa(performer.ID))
|
||||
performersSet[performer.ID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *FilenameParser) setTags(ctx context.Context, qb models.TagNameFinder, h audioHolder, result *models.AudioParserResult) {
|
||||
// query for each performer
|
||||
tagsSet := make(map[int]bool)
|
||||
for _, tagName := range h.tags {
|
||||
if tagName != "" {
|
||||
tag := p.queryTag(ctx, qb, tagName)
|
||||
if tag != nil {
|
||||
if _, found := tagsSet[tag.ID]; !found {
|
||||
result.TagIds = append(result.TagIds, strconv.Itoa(tag.ID))
|
||||
tagsSet[tag.ID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *FilenameParser) setStudio(ctx context.Context, qb models.StudioQueryer, h audioHolder, result *models.AudioParserResult) {
|
||||
// query for each performer
|
||||
if h.studio != "" {
|
||||
studio := p.queryStudio(ctx, qb, h.studio)
|
||||
if studio != nil {
|
||||
studioID := strconv.Itoa(studio.ID)
|
||||
result.StudioID = &studioID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *FilenameParser) setGroups(ctx context.Context, qb GroupNameFinder, h audioHolder, result *models.AudioParserResult) {
|
||||
// query for each group
|
||||
groupsSet := make(map[int]bool)
|
||||
for _, groupName := range h.groups {
|
||||
if groupName != "" {
|
||||
group := p.queryGroup(ctx, qb, groupName)
|
||||
if group != nil {
|
||||
if _, found := groupsSet[group.ID]; !found {
|
||||
result.Groups = append(result.Groups, &models.AudioGroupID{
|
||||
GroupID: strconv.Itoa(group.ID),
|
||||
})
|
||||
groupsSet[group.ID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *FilenameParser) setParserResult(ctx context.Context, h audioHolder, result *models.AudioParserResult) {
|
||||
if h.result.Title != "" {
|
||||
title := h.result.Title
|
||||
title = p.replaceWhitespaceCharacters(title)
|
||||
|
||||
if p.ParserInput.CapitalizeTitle != nil && *p.ParserInput.CapitalizeTitle {
|
||||
title = capitalizeTitleRE.ReplaceAllStringFunc(title, strings.ToUpper)
|
||||
}
|
||||
|
||||
result.Title = &title
|
||||
}
|
||||
|
||||
if h.result.Date != nil {
|
||||
dateStr := h.result.Date.String()
|
||||
result.Date = &dateStr
|
||||
}
|
||||
|
||||
if h.result.Rating != nil {
|
||||
result.Rating = h.result.Rating
|
||||
}
|
||||
|
||||
r := p.repository
|
||||
|
||||
if len(h.performers) > 0 {
|
||||
p.setPerformers(ctx, r.Performer, h, result)
|
||||
}
|
||||
if len(h.tags) > 0 {
|
||||
p.setTags(ctx, r.Tag, h, result)
|
||||
}
|
||||
p.setStudio(ctx, r.Studio, h, result)
|
||||
|
||||
if len(h.groups) > 0 {
|
||||
p.setGroups(ctx, r.Group, h, result)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
// TODO(audio): update this file
|
||||
|
||||
package audio
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -79,7 +79,6 @@ func (i *Importer) audioJSONToAudio(audioJSON jsonschema.Audio) models.Audio {
|
|||
Details: audioJSON.Details,
|
||||
PerformerIDs: models.NewRelatedIDs([]int{}),
|
||||
TagIDs: models.NewRelatedIDs([]int{}),
|
||||
GalleryIDs: models.NewRelatedIDs([]int{}),
|
||||
Groups: models.NewRelatedGroupsAudio([]models.GroupsAudios{}),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -92,7 +92,6 @@ func TestImporterPreImport(t *testing.T) {
|
|||
PlayDuration: playDuration,
|
||||
|
||||
Files: models.NewRelatedAudioFiles([]*models.AudioFile{}),
|
||||
GalleryIDs: models.NewRelatedIDs([]int{}),
|
||||
TagIDs: models.NewRelatedIDs([]int{}),
|
||||
PerformerIDs: models.NewRelatedIDs([]int{}),
|
||||
Groups: models.NewRelatedGroupsAudio([]models.GroupsAudios{}),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
// TODO(audio): update this file
|
||||
|
||||
// Package audio provides the application logic for audio functionality.
|
||||
// Most functionality is provided by [Service].
|
||||
package audio
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
// TODO(audio): update this file
|
||||
package audio
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetFunscriptPath returns the path of a file
|
||||
// with the extension changed to .funscript
|
||||
func GetFunscriptPath(path string) string {
|
||||
ext := filepath.Ext(path)
|
||||
fn := strings.TrimSuffix(path, ext)
|
||||
return fn + ".funscript"
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
// TODO(audio): update this file
|
||||
package audio
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
// TODO(audio): update this file
|
||||
|
||||
package models
|
||||
|
||||
import "context"
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ type Audio struct {
|
|||
Title string `json:"title"`
|
||||
Code string `json:"code"`
|
||||
Details string `json:"details"`
|
||||
Artists string `json:"artists"`
|
||||
Date *Date `json:"date"`
|
||||
// Rating expressed in 1-100 scale
|
||||
Rating *int `json:"rating"`
|
||||
|
|
@ -40,7 +39,6 @@ type Audio struct {
|
|||
PlayDuration float64 `json:"play_duration"`
|
||||
|
||||
URLs RelatedStrings `json:"urls"`
|
||||
GalleryIDs RelatedIDs `json:"gallery_ids"`
|
||||
TagIDs RelatedIDs `json:"tag_ids"`
|
||||
PerformerIDs RelatedIDs `json:"performer_ids"`
|
||||
Groups RelatedGroupsAudio `json:"groups"`
|
||||
|
|
@ -134,12 +132,6 @@ func (s *Audio) LoadPrimaryFile(ctx context.Context, l FileGetter) error {
|
|||
})
|
||||
}
|
||||
|
||||
func (s *Audio) LoadGalleryIDs(ctx context.Context, l GalleryIDLoader) error {
|
||||
return s.GalleryIDs.load(func() ([]int, error) {
|
||||
return l.GetGalleryIDs(ctx, s.ID)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Audio) LoadPerformerIDs(ctx context.Context, l PerformerIDLoader) error {
|
||||
return s.PerformerIDs.load(func() ([]int, error) {
|
||||
return l.GetPerformerIDs(ctx, s.ID)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
// TODO(audio): update this file
|
||||
package paths
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -744,6 +744,8 @@ var selectPerformerLatestSceneSQL = utils.StrFormat(
|
|||
},
|
||||
)
|
||||
|
||||
// TODO(audio): duplicate above for Audio
|
||||
|
||||
func (qb *PerformerStore) sortByLatestScene(direction string) string {
|
||||
// need to get the latest date from scenes
|
||||
return " ORDER BY (" + selectPerformerLatestSceneSQL + ") " + direction
|
||||
|
|
|
|||
|
|
@ -653,6 +653,8 @@ var selectStudioLatestSceneSQL = utils.StrFormat(
|
|||
},
|
||||
)
|
||||
|
||||
// TODO(audio): duplicate above for Audio
|
||||
|
||||
func (qb *StudioStore) sortByLatestScene(direction string) string {
|
||||
// need to get the latest date from scenes
|
||||
return " ORDER BY (" + selectStudioLatestSceneSQL + ") " + direction
|
||||
|
|
|
|||
Loading…
Reference in a new issue