mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
parent
dcb86d9186
commit
94a978d063
4 changed files with 210 additions and 69 deletions
|
|
@ -53,6 +53,7 @@ func isCDPPathWS(c GlobalConfig) bool {
|
||||||
type SceneFinder interface {
|
type SceneFinder interface {
|
||||||
models.SceneGetter
|
models.SceneGetter
|
||||||
models.URLLoader
|
models.URLLoader
|
||||||
|
models.VideoFileLoader
|
||||||
}
|
}
|
||||||
|
|
||||||
type PerformerFinder interface {
|
type PerformerFinder interface {
|
||||||
|
|
@ -380,7 +381,15 @@ func (c Cache) getScene(ctx context.Context, sceneID int) (*models.Scene, error)
|
||||||
return fmt.Errorf("scene with id %d not found", sceneID)
|
return fmt.Errorf("scene with id %d not found", sceneID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret.LoadURLs(ctx, qb)
|
if err := ret.LoadURLs(ctx, qb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ret.LoadFiles(ctx, qb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -403,12 +412,15 @@ func (c Cache) getGallery(ctx context.Context, galleryID int) (*models.Gallery,
|
||||||
return fmt.Errorf("gallery with id %d not found", galleryID)
|
return fmt.Errorf("gallery with id %d not found", galleryID)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ret.LoadFiles(ctx, qb)
|
if err := ret.LoadURLs(ctx, qb); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret.LoadURLs(ctx, qb)
|
if err := ret.LoadFiles(ctx, qb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ func (s *jsonScraper) loadURL(ctx context.Context, url string) (string, error) {
|
||||||
return "", errors.New("not valid json")
|
return "", errors.New("not valid json")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil && s.config.DebugOptions != nil && s.config.DebugOptions.PrintHTML {
|
if s.config.DebugOptions != nil && s.config.DebugOptions.PrintHTML {
|
||||||
logger.Infof("loadURL (%s) response: \n%s", url, docStr)
|
logger.Infof("loadURL (%s) response: \n%s", url, docStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,203 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
stashExec "github.com/stashapp/stash/pkg/exec"
|
stashExec "github.com/stashapp/stash/pkg/exec"
|
||||||
"github.com/stashapp/stash/pkg/logger"
|
"github.com/stashapp/stash/pkg/logger"
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
stashJson "github.com/stashapp/stash/pkg/models/json"
|
||||||
"github.com/stashapp/stash/pkg/python"
|
"github.com/stashapp/stash/pkg/python"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// inputs for scrapers
|
||||||
|
|
||||||
|
type fingerprintInput struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Fingerprint string `json:"fingerprint,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileInput struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
ZipFile *fileInput `json:"zip_file,omitempty"`
|
||||||
|
ModTime stashJson.JSONTime `json:"mod_time"`
|
||||||
|
|
||||||
|
Path string `json:"path,omitempty"`
|
||||||
|
|
||||||
|
Fingerprints []fingerprintInput `json:"fingerprints,omitempty"`
|
||||||
|
Size int64 `json:"size,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type videoFileInput struct {
|
||||||
|
fileInput
|
||||||
|
Format string `json:"format,omitempty"`
|
||||||
|
Width int `json:"width,omitempty"`
|
||||||
|
Height int `json:"height,omitempty"`
|
||||||
|
Duration float64 `json:"duration,omitempty"`
|
||||||
|
VideoCodec string `json:"video_codec,omitempty"`
|
||||||
|
AudioCodec string `json:"audio_codec,omitempty"`
|
||||||
|
FrameRate float64 `json:"frame_rate,omitempty"`
|
||||||
|
BitRate int64 `json:"bitrate,omitempty"`
|
||||||
|
|
||||||
|
Interactive bool `json:"interactive,omitempty"`
|
||||||
|
InteractiveSpeed *int `json:"interactive_speed,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// sceneInput is the input passed to the scraper for an existing scene
|
||||||
|
type sceneInput struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
|
||||||
|
// deprecated - use urls instead
|
||||||
|
URL *string `json:"url"`
|
||||||
|
URLs []string `json:"urls"`
|
||||||
|
|
||||||
|
// don't use omitempty for these to maintain backwards compatibility
|
||||||
|
Date *string `json:"date"`
|
||||||
|
Details string `json:"details"`
|
||||||
|
|
||||||
|
Director string `json:"director,omitempty"`
|
||||||
|
|
||||||
|
Files []videoFileInput `json:"files,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileInputFromFile(f models.BaseFile) fileInput {
|
||||||
|
b := f.Base()
|
||||||
|
var z *fileInput
|
||||||
|
if b.ZipFile != nil {
|
||||||
|
zz := fileInputFromFile(*b.ZipFile.Base())
|
||||||
|
z = &zz
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := fileInput{
|
||||||
|
ID: f.ID.String(),
|
||||||
|
ZipFile: z,
|
||||||
|
ModTime: stashJson.JSONTime{Time: f.ModTime},
|
||||||
|
Path: f.Path,
|
||||||
|
Size: f.Size,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fp := range f.Fingerprints {
|
||||||
|
ret.Fingerprints = append(ret.Fingerprints, fingerprintInput{
|
||||||
|
Type: fp.Type,
|
||||||
|
Fingerprint: fp.Value(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func videoFileInputFromVideoFile(vf *models.VideoFile) videoFileInput {
|
||||||
|
return videoFileInput{
|
||||||
|
fileInput: fileInputFromFile(*vf.Base()),
|
||||||
|
Format: vf.Format,
|
||||||
|
Width: vf.Width,
|
||||||
|
Height: vf.Height,
|
||||||
|
Duration: vf.Duration,
|
||||||
|
VideoCodec: vf.VideoCodec,
|
||||||
|
AudioCodec: vf.AudioCodec,
|
||||||
|
FrameRate: vf.FrameRate,
|
||||||
|
BitRate: vf.BitRate,
|
||||||
|
Interactive: vf.Interactive,
|
||||||
|
InteractiveSpeed: vf.InteractiveSpeed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sceneInputFromScene(scene *models.Scene) sceneInput {
|
||||||
|
dateToStringPtr := func(s *models.Date) *string {
|
||||||
|
if s != nil {
|
||||||
|
v := s.String()
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback to file basename if title is empty
|
||||||
|
title := scene.GetTitle()
|
||||||
|
|
||||||
|
var url *string
|
||||||
|
urls := scene.URLs.List()
|
||||||
|
if len(urls) > 0 {
|
||||||
|
url = &urls[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := sceneInput{
|
||||||
|
ID: strconv.Itoa(scene.ID),
|
||||||
|
Title: title,
|
||||||
|
Details: scene.Details,
|
||||||
|
// include deprecated URL for now
|
||||||
|
URL: url,
|
||||||
|
URLs: urls,
|
||||||
|
Date: dateToStringPtr(scene.Date),
|
||||||
|
Code: scene.Code,
|
||||||
|
Director: scene.Director,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range scene.Files.List() {
|
||||||
|
vf := videoFileInputFromVideoFile(f)
|
||||||
|
ret.Files = append(ret.Files, vf)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
type galleryInput struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Urls []string `json:"urls"`
|
||||||
|
Date *string `json:"date"`
|
||||||
|
Details string `json:"details"`
|
||||||
|
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
Photographer string `json:"photographer,omitempty"`
|
||||||
|
|
||||||
|
Files []fileInput `json:"files,omitempty"`
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
URL *string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func galleryInputFromGallery(gallery *models.Gallery) galleryInput {
|
||||||
|
dateToStringPtr := func(s *models.Date) *string {
|
||||||
|
if s != nil {
|
||||||
|
v := s.String()
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback to file basename if title is empty
|
||||||
|
title := gallery.GetTitle()
|
||||||
|
|
||||||
|
var url *string
|
||||||
|
urls := gallery.URLs.List()
|
||||||
|
if len(urls) > 0 {
|
||||||
|
url = &urls[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := galleryInput{
|
||||||
|
ID: strconv.Itoa(gallery.ID),
|
||||||
|
Title: title,
|
||||||
|
Details: gallery.Details,
|
||||||
|
URL: url,
|
||||||
|
Urls: urls,
|
||||||
|
Date: dateToStringPtr(gallery.Date),
|
||||||
|
Code: gallery.Code,
|
||||||
|
Photographer: gallery.Photographer,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range gallery.Files.List() {
|
||||||
|
fi := fileInputFromFile(*f.Base())
|
||||||
|
ret.Files = append(ret.Files, fi)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
var ErrScraperScript = errors.New("scraper script error")
|
var ErrScraperScript = errors.New("scraper script error")
|
||||||
|
|
||||||
type scriptScraper struct {
|
type scriptScraper struct {
|
||||||
|
|
@ -43,7 +232,7 @@ func (s *scriptScraper) runScraperScript(ctx context.Context, inString string, o
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warnf("%s", err)
|
logger.Warnf("%s", err)
|
||||||
} else {
|
} else {
|
||||||
cmd = p.Command(context.TODO(), command[1:])
|
cmd = p.Command(ctx, command[1:])
|
||||||
envVariable, _ := filepath.Abs(filepath.Dir(filepath.Dir(s.config.path)))
|
envVariable, _ := filepath.Abs(filepath.Dir(filepath.Dir(s.config.path)))
|
||||||
python.AppendPythonPath(cmd, envVariable)
|
python.AppendPythonPath(cmd, envVariable)
|
||||||
}
|
}
|
||||||
|
|
@ -51,7 +240,7 @@ func (s *scriptScraper) runScraperScript(ctx context.Context, inString string, o
|
||||||
|
|
||||||
if cmd == nil {
|
if cmd == nil {
|
||||||
// if could not find python, just use the command args as-is
|
// if could not find python, just use the command args as-is
|
||||||
cmd = stashExec.Command(command[0], command[1:]...)
|
cmd = stashExec.CommandContext(ctx, command[0], command[1:]...)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Dir = filepath.Dir(s.config.path)
|
cmd.Dir = filepath.Dir(s.config.path)
|
||||||
|
|
@ -205,7 +394,7 @@ func (s *scriptScraper) scrape(ctx context.Context, input string, ty ScrapeConte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *scriptScraper) scrapeSceneByScene(ctx context.Context, scene *models.Scene) (*ScrapedScene, error) {
|
func (s *scriptScraper) scrapeSceneByScene(ctx context.Context, scene *models.Scene) (*ScrapedScene, error) {
|
||||||
inString, err := json.Marshal(sceneToUpdateInput(scene))
|
inString, err := json.Marshal(sceneInputFromScene(scene))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -219,7 +408,7 @@ func (s *scriptScraper) scrapeSceneByScene(ctx context.Context, scene *models.Sc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *scriptScraper) scrapeGalleryByGallery(ctx context.Context, gallery *models.Gallery) (*ScrapedGallery, error) {
|
func (s *scriptScraper) scrapeGalleryByGallery(ctx context.Context, gallery *models.Gallery) (*ScrapedGallery, error) {
|
||||||
inString, err := json.Marshal(galleryToUpdateInput(gallery))
|
inString, err := json.Marshal(galleryInputFromGallery(gallery))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
"github.com/shurcooL/graphql"
|
"github.com/shurcooL/graphql"
|
||||||
|
|
@ -310,62 +309,3 @@ func (s *stashScraper) scrapeGalleryByGallery(ctx context.Context, gallery *mode
|
||||||
func (s *stashScraper) scrapeByURL(_ context.Context, _ string, _ ScrapeContentType) (ScrapedContent, error) {
|
func (s *stashScraper) scrapeByURL(_ context.Context, _ string, _ ScrapeContentType) (ScrapedContent, error) {
|
||||||
return nil, ErrNotSupported
|
return nil, ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
func sceneToUpdateInput(scene *models.Scene) models.SceneUpdateInput {
|
|
||||||
dateToStringPtr := func(s *models.Date) *string {
|
|
||||||
if s != nil {
|
|
||||||
v := s.String()
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// fallback to file basename if title is empty
|
|
||||||
title := scene.GetTitle()
|
|
||||||
|
|
||||||
var url *string
|
|
||||||
urls := scene.URLs.List()
|
|
||||||
if len(urls) > 0 {
|
|
||||||
url = &urls[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return models.SceneUpdateInput{
|
|
||||||
ID: strconv.Itoa(scene.ID),
|
|
||||||
Title: &title,
|
|
||||||
Details: &scene.Details,
|
|
||||||
// include deprecated URL for now
|
|
||||||
URL: url,
|
|
||||||
Urls: urls,
|
|
||||||
Date: dateToStringPtr(scene.Date),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func galleryToUpdateInput(gallery *models.Gallery) models.GalleryUpdateInput {
|
|
||||||
dateToStringPtr := func(s *models.Date) *string {
|
|
||||||
if s != nil {
|
|
||||||
v := s.String()
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// fallback to file basename if title is empty
|
|
||||||
title := gallery.GetTitle()
|
|
||||||
|
|
||||||
var url *string
|
|
||||||
urls := gallery.URLs.List()
|
|
||||||
if len(urls) > 0 {
|
|
||||||
url = &urls[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return models.GalleryUpdateInput{
|
|
||||||
ID: strconv.Itoa(gallery.ID),
|
|
||||||
Title: &title,
|
|
||||||
Details: &gallery.Details,
|
|
||||||
URL: url,
|
|
||||||
Urls: urls,
|
|
||||||
Date: dateToStringPtr(gallery.Date),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue