[Feature] Add fields director and (studio) code to scenes (#3051)

* added schema migration and updated data models
* added code and director to UI
* new fields are exported and imported
* added filters
* Add changelog entry
This commit is contained in:
HappyAxolotl 2022-11-07 08:16:52 +01:00 committed by GitHub
parent 7540d3b477
commit eff86bf2f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 411 additions and 248 deletions

View file

@ -1,7 +1,9 @@
fragment SlimSceneData on Scene {
id
title
code
details
director
url
date
rating

View file

@ -1,7 +1,9 @@
fragment SceneData on Scene {
id
title
code
details
director
url
date
rating

View file

@ -105,7 +105,9 @@ fragment ScrapedSceneTagData on ScrapedTag {
fragment ScrapedSceneData on ScrapedScene {
title
code
details
director
url
date
image
@ -166,7 +168,9 @@ fragment ScrapedGalleryData on ScrapedGallery {
fragment ScrapedStashBoxSceneData on ScrapedScene {
title
code
details
director
url
date
image

View file

@ -52,7 +52,9 @@ query ParseSceneFilenames($filter: FindFilterType!, $config: SceneParserInput!)
...SlimSceneData
}
title
code
details
director
url
date
rating

View file

@ -122,7 +122,9 @@ input SceneFilterType {
NOT: SceneFilterType
title: StringCriterionInput
code: StringCriterionInput
details: StringCriterionInput
director: StringCriterionInput
"""Filter by file oshash"""
oshash: StringCriterionInput

View file

@ -36,7 +36,9 @@ type Scene {
checksum: String @deprecated(reason: "Use files.fingerprints")
oshash: String @deprecated(reason: "Use files.fingerprints")
title: String
code: String
details: String
director: String
url: String
date: String
rating: Int
@ -76,7 +78,9 @@ input SceneUpdateInput {
clientMutationId: String
id: ID!
title: String
code: String
details: String
director: String
url: String
date: String
rating: Int
@ -108,7 +112,9 @@ input BulkSceneUpdateInput {
clientMutationId: String
ids: [ID!]
title: String
code: String
details: String
director: String
url: String
date: String
rating: Int
@ -156,7 +162,9 @@ type SceneMovieID {
type SceneParserResult {
scene: Scene!
title: String
code: String
details: String
director: String
url: String
date: String
rating: Int

View file

@ -61,7 +61,9 @@ type ScrapedTag {
type ScrapedScene {
title: String
code: String
details: String
director: String
url: String
date: String
@ -82,7 +84,9 @@ type ScrapedScene {
input ScrapedSceneInput {
title: String
code: String
details: String
director: String
url: String
date: String

View file

@ -94,7 +94,9 @@ fragment FingerprintFragment on Fingerprint {
fragment SceneFragment on Scene {
id
title
code
details
director
duration
date
urls {

View file

@ -112,7 +112,9 @@ func (r *mutationResolver) sceneUpdate(ctx context.Context, input models.SceneUp
updatedScene := models.NewScenePartial()
updatedScene.Title = translator.optionalString(input.Title, "title")
updatedScene.Code = translator.optionalString(input.Code, "code")
updatedScene.Details = translator.optionalString(input.Details, "details")
updatedScene.Director = translator.optionalString(input.Director, "director")
updatedScene.URL = translator.optionalString(input.URL, "url")
updatedScene.Date = translator.optionalDate(input.Date, "date")
updatedScene.Rating = translator.optionalInt(input.Rating, "rating")
@ -246,7 +248,9 @@ func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input BulkSceneU
updatedScene := models.NewScenePartial()
updatedScene.Title = translator.optionalString(input.Title, "title")
updatedScene.Code = translator.optionalString(input.Code, "code")
updatedScene.Details = translator.optionalString(input.Details, "details")
updatedScene.Director = translator.optionalString(input.Director, "director")
updatedScene.URL = translator.optionalString(input.URL, "url")
updatedScene.Date = translator.optionalDate(input.Date, "date")
updatedScene.Rating = translator.optionalInt(input.Rating, "rating")

View file

@ -26,7 +26,9 @@ type SceneParserInput struct {
type SceneParserResult struct {
Scene *models.Scene `json:"scene"`
Title *string `json:"title"`
Code *string `json:"code"`
Details *string `json:"details"`
Director *string `json:"director"`
URL *string `json:"url"`
Date *string `json:"date"`
Rating *int `json:"rating"`

View file

@ -39,6 +39,7 @@ type SceneMovie struct {
type Scene struct {
Title string `json:"title,omitempty"`
Code string `json:"code,omitempty"`
Studio string `json:"studio,omitempty"`
URL string `json:"url,omitempty"`
Date string `json:"date,omitempty"`
@ -46,6 +47,7 @@ type Scene struct {
Organized bool `json:"organized,omitempty"`
OCounter int `json:"o_counter,omitempty"`
Details string `json:"details,omitempty"`
Director string `json:"director,omitempty"`
Galleries []GalleryRef `json:"galleries,omitempty"`
Performers []string `json:"performers,omitempty"`
Movies []SceneMovie `json:"movies,omitempty"`

View file

@ -14,7 +14,9 @@ import (
type Scene struct {
ID int `json:"id"`
Title string `json:"title"`
Code string `json:"code"`
Details string `json:"details"`
Director string `json:"director"`
URL string `json:"url"`
Date *Date `json:"date"`
Rating *int `json:"rating"`
@ -133,7 +135,9 @@ func (s *Scene) LoadRelationships(ctx context.Context, l SceneReader) error {
// the database entry.
type ScenePartial struct {
Title OptionalString
Code OptionalString
Details OptionalString
Director OptionalString
URL OptionalString
Date OptionalDate
Rating OptionalInt
@ -167,7 +171,9 @@ type SceneUpdateInput struct {
ClientMutationID *string `json:"clientMutationId"`
ID string `json:"id"`
Title *string `json:"title"`
Code *string `json:"code"`
Details *string `json:"details"`
Director *string `json:"director"`
URL *string `json:"url"`
Date *string `json:"date"`
Rating *int `json:"rating"`
@ -200,7 +206,9 @@ func (s ScenePartial) UpdateInput(id int) SceneUpdateInput {
return SceneUpdateInput{
ID: strconv.Itoa(id),
Title: s.Title.Ptr(),
Code: s.Code.Ptr(),
Details: s.Details.Ptr(),
Director: s.Director.Ptr(),
URL: s.URL.Ptr(),
Date: dateStr,
Rating: s.Rating.Ptr(),

View file

@ -13,7 +13,9 @@ func TestScenePartial_UpdateInput(t *testing.T) {
var (
title = "title"
code = "1337"
details = "details"
director = "director"
url = "url"
date = "2001-02-03"
rating = 4
@ -35,7 +37,9 @@ func TestScenePartial_UpdateInput(t *testing.T) {
id,
ScenePartial{
Title: NewOptionalString(title),
Code: NewOptionalString(code),
Details: NewOptionalString(details),
Director: NewOptionalString(director),
URL: NewOptionalString(url),
Date: NewOptionalDate(dateObj),
Rating: NewOptionalInt(rating),
@ -45,7 +49,9 @@ func TestScenePartial_UpdateInput(t *testing.T) {
SceneUpdateInput{
ID: idStr,
Title: &title,
Code: &code,
Details: &details,
Director: &director,
URL: &url,
Date: &date,
Rating: &rating,

View file

@ -79,7 +79,9 @@ func (ScrapedMovie) IsScrapedContent() {}
type ScrapedItem struct {
ID int `db:"id" json:"id"`
Title sql.NullString `db:"title" json:"title"`
Code sql.NullString `db:"code" json:"code"`
Description sql.NullString `db:"description" json:"description"`
Director sql.NullString `db:"director" json:"director"`
URL sql.NullString `db:"url" json:"url"`
Date SQLiteDate `db:"date" json:"date"`
Rating sql.NullString `db:"rating" json:"rating"`

View file

@ -13,11 +13,13 @@ type PHashDuplicationCriterionInput struct {
}
type SceneFilterType struct {
And *SceneFilterType `json:"AND"`
Or *SceneFilterType `json:"OR"`
Not *SceneFilterType `json:"NOT"`
Title *StringCriterionInput `json:"title"`
Details *StringCriterionInput `json:"details"`
And *SceneFilterType `json:"AND"`
Or *SceneFilterType `json:"OR"`
Not *SceneFilterType `json:"NOT"`
Title *StringCriterionInput `json:"title"`
Code *StringCriterionInput `json:"code"`
Details *StringCriterionInput `json:"details"`
Director *StringCriterionInput `json:"director"`
// Filter by file oshash
Oshash *StringCriterionInput `json:"oshash"`
// Filter by file checksum

View file

@ -39,8 +39,10 @@ type TagFinder interface {
func ToBasicJSON(ctx context.Context, reader CoverGetter, scene *models.Scene) (*jsonschema.Scene, error) {
newSceneJSON := jsonschema.Scene{
Title: scene.Title,
Code: scene.Code,
URL: scene.URL,
Details: scene.Details,
Director: scene.Director,
CreatedAt: json.JSONTime{Time: scene.CreatedAt},
UpdatedAt: json.JSONTime{Time: scene.UpdatedAt},
}

View file

@ -82,7 +82,9 @@ func (i *Importer) sceneJSONToScene(sceneJSON jsonschema.Scene) models.Scene {
newScene := models.Scene{
// Path: i.Path,
Title: sceneJSON.Title,
Code: sceneJSON.Code,
Details: sceneJSON.Details,
Director: sceneJSON.Director,
URL: sceneJSON.URL,
PerformerIDs: models.NewRelatedIDs([]int{}),
TagIDs: models.NewRelatedIDs([]int{}),

View file

@ -36,9 +36,11 @@ func queryURLParametersFromScrapedScene(scene ScrapedSceneInput) queryURLParamet
}
setField("title", scene.Title)
setField("code", scene.Code)
setField("url", scene.URL)
setField("date", scene.Date)
setField("details", scene.Details)
setField("director", scene.Director)
setField("remote_site_id", scene.RemoteSiteID)
return ret
}

View file

@ -5,10 +5,12 @@ import (
)
type ScrapedScene struct {
Title *string `json:"title"`
Details *string `json:"details"`
URL *string `json:"url"`
Date *string `json:"date"`
Title *string `json:"title"`
Code *string `json:"code"`
Details *string `json:"details"`
Director *string `json:"director"`
URL *string `json:"url"`
Date *string `json:"date"`
// This should be a base64 encoded data URL
Image *string `json:"image"`
File *models.SceneFileType `json:"file"`
@ -25,7 +27,9 @@ func (ScrapedScene) IsScrapedContent() {}
type ScrapedSceneInput struct {
Title *string `json:"title"`
Code *string `json:"code"`
Details *string `json:"details"`
Director *string `json:"director"`
URL *string `json:"url"`
Date *string `json:"date"`
RemoteSiteID *string `json:"remote_site_id"`

View file

@ -182,7 +182,9 @@ type FingerprintFragment struct {
type SceneFragment struct {
ID string "json:\"id\" graphql:\"id\""
Title *string "json:\"title\" graphql:\"title\""
Code *string "json:\"code\" graphql:\"code\""
Details *string "json:\"details\" graphql:\"details\""
Director *string "json:\"director\" graphql:\"director\""
Duration *int "json:\"duration\" graphql:\"duration\""
Date *string "json:\"date\" graphql:\"date\""
Urls []*URLFragment "json:\"urls\" graphql:\"urls\""
@ -237,6 +239,49 @@ const FindSceneByFingerprintDocument = `query FindSceneByFingerprint ($fingerpri
... SceneFragment
}
}
fragment MeasurementsFragment on Measurements {
band_size
cup_size
waist
hip
}
fragment SceneFragment on Scene {
id
title
code
details
director
duration
date
urls {
... URLFragment
}
images {
... ImageFragment
}
studio {
... StudioFragment
}
tags {
... TagFragment
}
performers {
... PerformerAppearanceFragment
}
fingerprints {
... FingerprintFragment
}
}
fragment StudioFragment on Studio {
name
id
urls {
... URLFragment
}
images {
... ImageFragment
}
}
fragment PerformerFragment on Performer {
id
name
@ -271,73 +316,32 @@ fragment PerformerFragment on Performer {
... BodyModificationFragment
}
}
fragment FuzzyDateFragment on FuzzyDate {
date
accuracy
}
fragment BodyModificationFragment on BodyModification {
location
description
}
fragment FingerprintFragment on Fingerprint {
algorithm
hash
duration
}
fragment SceneFragment on Scene {
id
title
details
duration
date
urls {
... URLFragment
}
images {
... ImageFragment
}
studio {
... StudioFragment
}
tags {
... TagFragment
}
performers {
... PerformerAppearanceFragment
}
fingerprints {
... FingerprintFragment
}
}
fragment URLFragment on URL {
url
type
}
fragment TagFragment on Tag {
name
id
}
fragment FuzzyDateFragment on FuzzyDate {
date
accuracy
}
fragment MeasurementsFragment on Measurements {
band_size
cup_size
waist
hip
}
fragment BodyModificationFragment on BodyModification {
location
description
}
fragment ImageFragment on Image {
id
url
width
height
}
fragment StudioFragment on Studio {
fragment TagFragment on Tag {
name
id
urls {
... URLFragment
}
images {
... ImageFragment
}
}
fragment PerformerAppearanceFragment on PerformerAppearance {
as
@ -369,6 +373,22 @@ fragment URLFragment on URL {
url
type
}
fragment ImageFragment on Image {
id
url
width
height
}
fragment StudioFragment on Studio {
name
id
urls {
... URLFragment
}
images {
... ImageFragment
}
}
fragment PerformerFragment on Performer {
id
name
@ -403,15 +423,16 @@ fragment PerformerFragment on Performer {
... BodyModificationFragment
}
}
fragment FingerprintFragment on Fingerprint {
algorithm
hash
duration
fragment BodyModificationFragment on BodyModification {
location
description
}
fragment SceneFragment on Scene {
id
title
code
details
director
duration
date
urls {
@ -433,20 +454,6 @@ fragment SceneFragment on Scene {
... FingerprintFragment
}
}
fragment StudioFragment on Studio {
name
id
urls {
... URLFragment
}
images {
... ImageFragment
}
}
fragment TagFragment on Tag {
name
id
}
fragment PerformerAppearanceFragment on PerformerAppearance {
as
performer {
@ -463,15 +470,14 @@ fragment MeasurementsFragment on Measurements {
waist
hip
}
fragment BodyModificationFragment on BodyModification {
location
description
fragment FingerprintFragment on Fingerprint {
algorithm
hash
duration
}
fragment ImageFragment on Image {
fragment TagFragment on Tag {
name
id
url
width
height
}
`
@ -493,31 +499,6 @@ const FindScenesBySceneFingerprintsDocument = `query FindScenesBySceneFingerprin
... SceneFragment
}
}
fragment SceneFragment on Scene {
id
title
details
duration
date
urls {
... URLFragment
}
images {
... ImageFragment
}
studio {
... StudioFragment
}
tags {
... TagFragment
}
performers {
... PerformerAppearanceFragment
}
fingerprints {
... FingerprintFragment
}
}
fragment ImageFragment on Image {
id
url
@ -534,6 +515,29 @@ fragment StudioFragment on Studio {
... ImageFragment
}
}
fragment PerformerAppearanceFragment on PerformerAppearance {
as
performer {
... PerformerFragment
}
}
fragment FuzzyDateFragment on FuzzyDate {
date
accuracy
}
fragment FingerprintFragment on Fingerprint {
algorithm
hash
duration
}
fragment URLFragment on URL {
url
type
}
fragment TagFragment on Tag {
name
id
}
fragment PerformerFragment on Performer {
id
name
@ -568,29 +572,6 @@ fragment PerformerFragment on Performer {
... BodyModificationFragment
}
}
fragment FingerprintFragment on Fingerprint {
algorithm
hash
duration
}
fragment URLFragment on URL {
url
type
}
fragment TagFragment on Tag {
name
id
}
fragment PerformerAppearanceFragment on PerformerAppearance {
as
performer {
... PerformerFragment
}
}
fragment FuzzyDateFragment on FuzzyDate {
date
accuracy
}
fragment MeasurementsFragment on Measurements {
band_size
cup_size
@ -601,6 +582,33 @@ fragment BodyModificationFragment on BodyModification {
location
description
}
fragment SceneFragment on Scene {
id
title
code
details
director
duration
date
urls {
... URLFragment
}
images {
... ImageFragment
}
studio {
... StudioFragment
}
tags {
... TagFragment
}
performers {
... PerformerAppearanceFragment
}
fingerprints {
... FingerprintFragment
}
}
`
func (c *Client) FindScenesBySceneFingerprints(ctx context.Context, fingerprints [][]*FingerprintQueryInput, httpRequestOptions ...client.HTTPRequestOption) (*FindScenesBySceneFingerprints, error) {
@ -621,14 +629,27 @@ const SearchSceneDocument = `query SearchScene ($term: String!) {
... SceneFragment
}
}
fragment FuzzyDateFragment on FuzzyDate {
date
accuracy
fragment MeasurementsFragment on Measurements {
band_size
cup_size
waist
hip
}
fragment BodyModificationFragment on BodyModification {
location
description
}
fragment FingerprintFragment on Fingerprint {
algorithm
hash
duration
}
fragment SceneFragment on Scene {
id
title
code
details
director
duration
date
urls {
@ -650,45 +671,16 @@ fragment SceneFragment on Scene {
... FingerprintFragment
}
}
fragment URLFragment on URL {
url
type
}
fragment ImageFragment on Image {
id
url
width
height
}
fragment TagFragment on Tag {
name
id
}
fragment PerformerAppearanceFragment on PerformerAppearance {
as
performer {
... PerformerFragment
}
}
fragment BodyModificationFragment on BodyModification {
location
description
}
fragment FingerprintFragment on Fingerprint {
algorithm
hash
duration
}
fragment URLFragment on URL {
url
type
}
fragment StudioFragment on Studio {
name
id
urls {
... URLFragment
}
images {
... ImageFragment
}
}
fragment PerformerFragment on Performer {
id
name
@ -723,11 +715,29 @@ fragment PerformerFragment on Performer {
... BodyModificationFragment
}
}
fragment MeasurementsFragment on Measurements {
band_size
cup_size
waist
hip
fragment FuzzyDateFragment on FuzzyDate {
date
accuracy
}
fragment StudioFragment on Studio {
name
id
urls {
... URLFragment
}
images {
... ImageFragment
}
}
fragment TagFragment on Tag {
name
id
}
fragment PerformerAppearanceFragment on PerformerAppearance {
as
performer {
... PerformerFragment
}
}
`
@ -749,6 +759,26 @@ const SearchPerformerDocument = `query SearchPerformer ($term: String!) {
... PerformerFragment
}
}
fragment ImageFragment on Image {
id
url
width
height
}
fragment FuzzyDateFragment on FuzzyDate {
date
accuracy
}
fragment MeasurementsFragment on Measurements {
band_size
cup_size
waist
hip
}
fragment BodyModificationFragment on BodyModification {
location
description
}
fragment PerformerFragment on Performer {
id
name
@ -787,26 +817,6 @@ fragment URLFragment on URL {
url
type
}
fragment ImageFragment on Image {
id
url
width
height
}
fragment FuzzyDateFragment on FuzzyDate {
date
accuracy
}
fragment MeasurementsFragment on Measurements {
band_size
cup_size
waist
hip
}
fragment BodyModificationFragment on BodyModification {
location
description
}
`
func (c *Client) SearchPerformer(ctx context.Context, term string, httpRequestOptions ...client.HTTPRequestOption) (*SearchPerformer, error) {
@ -827,16 +837,6 @@ const FindPerformerByIDDocument = `query FindPerformerByID ($id: ID!) {
... PerformerFragment
}
}
fragment MeasurementsFragment on Measurements {
band_size
cup_size
waist
hip
}
fragment BodyModificationFragment on BodyModification {
location
description
}
fragment PerformerFragment on Performer {
id
name
@ -885,6 +885,16 @@ fragment FuzzyDateFragment on FuzzyDate {
date
accuracy
}
fragment MeasurementsFragment on Measurements {
band_size
cup_size
waist
hip
}
fragment BodyModificationFragment on BodyModification {
location
description
}
`
func (c *Client) FindPerformerByID(ctx context.Context, id string, httpRequestOptions ...client.HTTPRequestOption) (*FindPerformerByID, error) {
@ -909,12 +919,49 @@ fragment BodyModificationFragment on BodyModification {
location
description
}
fragment SceneFragment on Scene {
id
title
code
details
director
duration
date
urls {
... URLFragment
}
images {
... ImageFragment
}
studio {
... StudioFragment
}
tags {
... TagFragment
}
performers {
... PerformerAppearanceFragment
}
fingerprints {
... FingerprintFragment
}
}
fragment ImageFragment on Image {
id
url
width
height
}
fragment StudioFragment on Studio {
name
id
urls {
... URLFragment
}
images {
... ImageFragment
}
}
fragment FuzzyDateFragment on FuzzyDate {
date
accuracy
@ -925,6 +972,10 @@ fragment MeasurementsFragment on Measurements {
waist
hip
}
fragment URLFragment on URL {
url
type
}
fragment TagFragment on Tag {
name
id
@ -974,45 +1025,6 @@ fragment FingerprintFragment on Fingerprint {
hash
duration
}
fragment SceneFragment on Scene {
id
title
details
duration
date
urls {
... URLFragment
}
images {
... ImageFragment
}
studio {
... StudioFragment
}
tags {
... TagFragment
}
performers {
... PerformerAppearanceFragment
}
fingerprints {
... FingerprintFragment
}
}
fragment URLFragment on URL {
url
type
}
fragment StudioFragment on Studio {
name
id
urls {
... URLFragment
}
images {
... ImageFragment
}
}
`
func (c *Client) FindSceneByID(ctx context.Context, id string, httpRequestOptions ...client.HTTPRequestOption) (*FindSceneByID, error) {

View file

@ -88,8 +88,8 @@ type DraftEntity struct {
ID *string `json:"id,omitempty"`
}
func (DraftEntity) IsSceneDraftStudio() {}
func (DraftEntity) IsSceneDraftTag() {}
func (DraftEntity) IsSceneDraftStudio() {}
func (DraftEntity) IsSceneDraftPerformer() {}
type DraftEntityInput struct {
@ -339,8 +339,8 @@ type Performer struct {
Updated time.Time `json:"updated"`
}
func (Performer) IsSceneDraftPerformer() {}
func (Performer) IsEditTarget() {}
func (Performer) IsSceneDraftPerformer() {}
type PerformerAppearance struct {
Performer *Performer `json:"performer,omitempty"`

View file

@ -667,8 +667,10 @@ func (c Client) sceneFragmentToScrapedScene(ctx context.Context, s *graphql.Scen
stashID := s.ID
ss := &scraper.ScrapedScene{
Title: s.Title,
Code: s.Code,
Date: s.Date,
Details: s.Details,
Director: s.Director,
URL: findURL(s.Urls, "STUDIO"),
Duration: s.Duration,
RemoteSiteID: &stashID,

View file

@ -22,7 +22,7 @@ import (
"github.com/stashapp/stash/pkg/logger"
)
var appSchemaVersion uint = 37
var appSchemaVersion uint = 38
//go:embed migrations/*.sql
var migrationsBox embed.FS

View file

@ -0,0 +1,2 @@
ALTER TABLE `scenes` ADD COLUMN `code` text;
ALTER TABLE `scenes` ADD COLUMN `director` text;

View file

@ -54,7 +54,9 @@ ORDER BY files.size DESC
type sceneRow struct {
ID int `db:"id" goqu:"skipinsert"`
Title zero.String `db:"title"`
Code zero.String `db:"code"`
Details zero.String `db:"details"`
Director zero.String `db:"director"`
URL zero.String `db:"url"`
Date models.SQLiteDate `db:"date"`
Rating null.Int `db:"rating"`
@ -68,7 +70,9 @@ type sceneRow struct {
func (r *sceneRow) fromScene(o models.Scene) {
r.ID = o.ID
r.Title = zero.StringFrom(o.Title)
r.Code = zero.StringFrom(o.Code)
r.Details = zero.StringFrom(o.Details)
r.Director = zero.StringFrom(o.Director)
r.URL = zero.StringFrom(o.URL)
if o.Date != nil {
_ = r.Date.Scan(o.Date.Time)
@ -94,7 +98,9 @@ func (r *sceneQueryRow) resolve() *models.Scene {
ret := &models.Scene{
ID: r.ID,
Title: r.Title.String,
Code: r.Code.String,
Details: r.Details.String,
Director: r.Director.String,
URL: r.URL.String,
Date: r.Date.DatePtr(),
Rating: nullIntPtr(r.Rating),
@ -123,7 +129,9 @@ type sceneRowRecord struct {
func (r *sceneRowRecord) fromPartial(o models.ScenePartial) {
r.setNullString("title", o.Title)
r.setNullString("code", o.Code)
r.setNullString("details", o.Details)
r.setNullString("director", o.Director)
r.setNullString("url", o.URL)
r.setSQLiteDate("date", o.Date)
r.setNullInt("rating", o.Rating)
@ -801,7 +809,9 @@ func (qb *SceneStore) makeFilter(ctx context.Context, sceneFilter *models.SceneF
query.handleCriterion(ctx, pathCriterionHandler(sceneFilter.Path, "folders.path", "files.basename", qb.addFoldersTable))
query.handleCriterion(ctx, sceneFileCountCriterionHandler(qb, sceneFilter.FileCount))
query.handleCriterion(ctx, stringCriterionHandler(sceneFilter.Title, "scenes.title"))
query.handleCriterion(ctx, stringCriterionHandler(sceneFilter.Code, "scenes.code"))
query.handleCriterion(ctx, stringCriterionHandler(sceneFilter.Details, "scenes.details"))
query.handleCriterion(ctx, stringCriterionHandler(sceneFilter.Director, "scenes.director"))
query.handleCriterion(ctx, criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) {
if sceneFilter.Oshash != nil {
qb.addSceneFilesTable(f)

View file

@ -73,7 +73,9 @@ func loadSceneRelationships(ctx context.Context, expected models.Scene, actual *
func Test_sceneQueryBuilder_Create(t *testing.T) {
var (
title = "title"
code = "1337"
details = "details"
director = "director"
url = "url"
rating = 3
ocounter = 5
@ -100,7 +102,9 @@ func Test_sceneQueryBuilder_Create(t *testing.T) {
"full",
models.Scene{
Title: title,
Code: code,
Details: details,
Director: director,
URL: url,
Date: &date,
Rating: &rating,
@ -139,7 +143,9 @@ func Test_sceneQueryBuilder_Create(t *testing.T) {
"with file",
models.Scene{
Title: title,
Code: code,
Details: details,
Director: director,
URL: url,
Date: &date,
Rating: &rating,
@ -294,7 +300,9 @@ func makeSceneFileWithID(i int) *file.VideoFile {
func Test_sceneQueryBuilder_Update(t *testing.T) {
var (
title = "title"
code = "1337"
details = "details"
director = "director"
url = "url"
rating = 3
ocounter = 5
@ -320,7 +328,9 @@ func Test_sceneQueryBuilder_Update(t *testing.T) {
&models.Scene{
ID: sceneIDs[sceneIdxWithGallery],
Title: title,
Code: code,
Details: details,
Director: director,
URL: url,
Date: &date,
Rating: &rating,
@ -481,7 +491,9 @@ func clearScenePartial() models.ScenePartial {
// leave mandatory fields
return models.ScenePartial{
Title: models.OptionalString{Set: true, Null: true},
Code: models.OptionalString{Set: true, Null: true},
Details: models.OptionalString{Set: true, Null: true},
Director: models.OptionalString{Set: true, Null: true},
URL: models.OptionalString{Set: true, Null: true},
Date: models.OptionalDate{Set: true, Null: true},
Rating: models.OptionalInt{Set: true, Null: true},
@ -496,7 +508,9 @@ func clearScenePartial() models.ScenePartial {
func Test_sceneQueryBuilder_UpdatePartial(t *testing.T) {
var (
title = "title"
code = "1337"
details = "details"
director = "director"
url = "url"
rating = 3
ocounter = 5
@ -524,7 +538,9 @@ func Test_sceneQueryBuilder_UpdatePartial(t *testing.T) {
sceneIDs[sceneIdxWithSpacedName],
models.ScenePartial{
Title: models.NewOptionalString(title),
Code: models.NewOptionalString(code),
Details: models.NewOptionalString(details),
Director: models.NewOptionalString(director),
URL: models.NewOptionalString(url),
Date: models.NewOptionalDate(date),
Rating: models.NewOptionalInt(rating),
@ -578,7 +594,9 @@ func Test_sceneQueryBuilder_UpdatePartial(t *testing.T) {
makeSceneFile(sceneIdxWithSpacedName),
}),
Title: title,
Code: code,
Details: details,
Director: director,
URL: url,
Date: &date,
Rating: &rating,

View file

@ -1403,7 +1403,7 @@ func getTagChildCount(id int) int {
return 0
}
//createTags creates n tags with plain Name and o tags with camel cased NaMe included
// createTags creates n tags with plain Name and o tags with camel cased NaMe included
func createTags(ctx context.Context, tqb models.TagReaderWriter, n int, o int) error {
const namePlain = "Name"
const nameNoCase = "NaMe"

View file

@ -27,7 +27,7 @@ export const SceneDetailPanel: React.FC<ISceneDetailProps> = (props) => {
return (
<>
<h6>
<FormattedMessage id="details" />
<FormattedMessage id="details" />:{" "}
</h6>
<p className="pre">{props.scene.details}</p>
</>
@ -121,6 +121,16 @@ export const SceneDetailPanel: React.FC<ISceneDetailProps> = (props) => {
<FormattedMessage id="updated_at" />:{" "}
{TextUtils.formatDateTime(intl, props.scene.updated_at)}{" "}
</h6>
{props.scene.code && (
<h6>
<FormattedMessage id="scene_code" />: {props.scene.code}{" "}
</h6>
)}
{props.scene.director && (
<h6>
<FormattedMessage id="director" />: {props.scene.director}{" "}
</h6>
)}
</div>
{props.scene.studio && (
<div className="col-3 d-xl-none">

View file

@ -105,7 +105,9 @@ export const SceneEditPanel: React.FC<IProps> = ({
const schema = yup.object({
title: yup.string().optional().nullable(),
code: yup.string().optional().nullable(),
details: yup.string().optional().nullable(),
director: yup.string().optional().nullable(),
url: yup.string().optional().nullable(),
date: yup.string().optional().nullable(),
rating: yup.number().optional().nullable(),
@ -127,7 +129,9 @@ export const SceneEditPanel: React.FC<IProps> = ({
const initialValues = useMemo(
() => ({
title: scene.title ?? "",
code: scene.code ?? "",
details: scene.details ?? "",
director: scene.director ?? "",
url: scene.url ?? "",
date: scene.date ?? "",
rating: scene.rating ?? null,
@ -337,7 +341,9 @@ export const SceneEditPanel: React.FC<IProps> = ({
try {
const input: GQL.ScrapedSceneInput = {
date: fragment.date,
code: fragment.code,
details: fragment.details,
director: fragment.director,
remote_site_id: fragment.remote_site_id,
title: fragment.title,
url: fragment.url,
@ -536,10 +542,18 @@ export const SceneEditPanel: React.FC<IProps> = ({
formik.setFieldValue("title", updatedScene.title);
}
if (updatedScene.code) {
formik.setFieldValue("code", updatedScene.code);
}
if (updatedScene.details) {
formik.setFieldValue("details", updatedScene.details);
}
if (updatedScene.director) {
formik.setFieldValue("director", updatedScene.director);
}
if (updatedScene.date) {
formik.setFieldValue("date", updatedScene.date);
}
@ -696,6 +710,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
<div className="form-container row px-3">
<div className="col-12 col-lg-7 col-xl-12">
{renderTextField("title", intl.formatMessage({ id: "title" }))}
{renderTextField("code", intl.formatMessage({ id: "scene_code" }))}
<Form.Group controlId="url" as={Row}>
<Col xs={3} className="pr-0 url-label">
<Form.Label className="col-form-label">
@ -716,6 +731,10 @@ export const SceneEditPanel: React.FC<IProps> = ({
intl.formatMessage({ id: "date" }),
"YYYY-MM-DD"
)}
{renderTextField(
"director",
intl.formatMessage({ id: "director" })
)}
<Form.Group controlId="rating" as={Row}>
{FormUtils.renderLabel({
title: intl.formatMessage({ id: "rating" }),

View file

@ -248,12 +248,18 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
const [title, setTitle] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(scene.title, scraped.title)
);
const [code, setCode] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(scene.code, scraped.code)
);
const [url, setURL] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(scene.url, scraped.url)
);
const [date, setDate] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(scene.date, scraped.date)
);
const [director, setDirector] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(scene.director, scraped.director)
);
const [studio, setStudio] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(scene.studio_id, scraped.studio?.stored_id)
);
@ -339,6 +345,7 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
const [details, setDetails] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(scene.details, scraped.details)
);
const [image, setImage] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(scene.cover_image, scraped.image)
);
@ -355,8 +362,10 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
if (
[
title,
code,
url,
date,
director,
studio,
performers,
movies,
@ -521,8 +530,10 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
return {
title: title.getNewValue(),
code: code.getNewValue(),
url: url.getNewValue(),
date: date.getNewValue(),
director: director.getNewValue(),
studio: newStudioValue
? {
stored_id: newStudioValue,
@ -561,6 +572,11 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
result={title}
onChange={(value) => setTitle(value)}
/>
<ScrapedInputGroupRow
title={intl.formatMessage({ id: "scene_code" })}
result={code}
onChange={(value) => setCode(value)}
/>
<ScrapedInputGroupRow
title={intl.formatMessage({ id: "url" })}
result={url}
@ -572,6 +588,11 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
result={date}
onChange={(value) => setDate(value)}
/>
<ScrapedInputGroupRow
title={intl.formatMessage({ id: "director" })}
result={director}
onChange={(value) => setDirector(value)}
/>
{renderScrapedStudioRow(
intl.formatMessage({ id: "studios" }),
studio,

View file

@ -1,4 +1,5 @@
### ✨ New Features
* Added Directory and Studio Code fields to scenes. ([#3051](https://github.com/stashapp/stash/pull/3051))
* Added selector for Country field. ([#1922](https://github.com/stashapp/stash/pull/1922))
* Added tag description filter criterion. ([#3011](https://github.com/stashapp/stash/pull/3011))

View file

@ -918,6 +918,7 @@
"scene": "Scene",
"sceneTagger": "Scene Tagger",
"sceneTags": "Scene Tags",
"scene_code": "Studio Code",
"scene_count": "Scene Count",
"scene_id": "Scene ID",
"scenes": "Scenes",

View file

@ -165,6 +165,8 @@ export function makeCriteria(type: CriterionType = "none") {
case "synopsis":
case "description":
return new StringCriterion(new StringCriterionOption(type, type));
case "scene_code":
return new StringCriterion(new StringCriterionOption(type, type, "code"));
case "interactive":
return new InteractiveCriterion();
case "captions":

View file

@ -51,8 +51,10 @@ const displayModeOptions = [
const criterionOptions = [
createStringCriterionOption("title"),
createStringCriterionOption("scene_code"),
createMandatoryStringCriterionOption("path"),
createStringCriterionOption("details"),
createStringCriterionOption("director"),
createMandatoryStringCriterionOption("oshash", "media_info.hash"),
createStringCriterionOption(
"sceneChecksum",

View file

@ -125,4 +125,5 @@ export type CriterionType =
| "duplicated"
| "ignore_auto_tag"
| "file_count"
| "description";
| "description"
| "scene_code";