mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
Add PHash distance matching to stash-box integration (#1858)
* Add PHash distance matching to stash-box integration
This commit is contained in:
@@ -31,6 +31,7 @@ type ScrapedStudio {
|
|||||||
stored_id: ID
|
stored_id: ID
|
||||||
name: String!
|
name: String!
|
||||||
url: String
|
url: String
|
||||||
|
image: String
|
||||||
|
|
||||||
remote_site_id: String
|
remote_site_id: String
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ fragment PerformerFragment on Performer {
|
|||||||
disambiguation
|
disambiguation
|
||||||
aliases
|
aliases
|
||||||
gender
|
gender
|
||||||
|
merged_ids
|
||||||
urls {
|
urls {
|
||||||
...URLFragment
|
...URLFragment
|
||||||
}
|
}
|
||||||
@@ -75,11 +76,6 @@ fragment PerformerFragment on Performer {
|
|||||||
piercings {
|
piercings {
|
||||||
...BodyModificationFragment
|
...BodyModificationFragment
|
||||||
}
|
}
|
||||||
details
|
|
||||||
death_date {
|
|
||||||
...FuzzyDateFragment
|
|
||||||
}
|
|
||||||
weight
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment PerformerAppearanceFragment on PerformerAppearance {
|
fragment PerformerAppearanceFragment on PerformerAppearance {
|
||||||
@@ -127,8 +123,8 @@ query FindSceneByFingerprint($fingerprint: FingerprintQueryInput!) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query FindScenesByFingerprints($fingerprints: [String!]!) {
|
query FindScenesByFullFingerprints($fingerprints: [FingerprintQueryInput!]!) {
|
||||||
findScenesByFingerprints(fingerprints: $fingerprints) {
|
findScenesByFullFingerprints(fingerprints: $fingerprints) {
|
||||||
...SceneFragment
|
...SceneFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,6 +147,12 @@ query FindPerformerByID($id: ID!) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query FindSceneByID($id: ID!) {
|
||||||
|
findScene(id: $id) {
|
||||||
|
...SceneFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mutation SubmitFingerprint($input: FingerprintSubmission!) {
|
mutation SubmitFingerprint($input: FingerprintSubmission!) {
|
||||||
submitFingerprint(input: $input)
|
submitFingerprint(input: $input)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,26 +18,27 @@ func NewClient(cli *http.Client, baseURL string, options ...client.HTTPRequestOp
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
FindPerformer *Performer "json:\"findPerformer\" graphql:\"findPerformer\""
|
FindPerformer *Performer "json:\"findPerformer\" graphql:\"findPerformer\""
|
||||||
QueryPerformers QueryPerformersResultType "json:\"queryPerformers\" graphql:\"queryPerformers\""
|
QueryPerformers QueryPerformersResultType "json:\"queryPerformers\" graphql:\"queryPerformers\""
|
||||||
FindStudio *Studio "json:\"findStudio\" graphql:\"findStudio\""
|
FindStudio *Studio "json:\"findStudio\" graphql:\"findStudio\""
|
||||||
QueryStudios QueryStudiosResultType "json:\"queryStudios\" graphql:\"queryStudios\""
|
QueryStudios QueryStudiosResultType "json:\"queryStudios\" graphql:\"queryStudios\""
|
||||||
FindTag *Tag "json:\"findTag\" graphql:\"findTag\""
|
FindTag *Tag "json:\"findTag\" graphql:\"findTag\""
|
||||||
QueryTags QueryTagsResultType "json:\"queryTags\" graphql:\"queryTags\""
|
QueryTags QueryTagsResultType "json:\"queryTags\" graphql:\"queryTags\""
|
||||||
FindTagCategory *TagCategory "json:\"findTagCategory\" graphql:\"findTagCategory\""
|
FindTagCategory *TagCategory "json:\"findTagCategory\" graphql:\"findTagCategory\""
|
||||||
QueryTagCategories QueryTagCategoriesResultType "json:\"queryTagCategories\" graphql:\"queryTagCategories\""
|
QueryTagCategories QueryTagCategoriesResultType "json:\"queryTagCategories\" graphql:\"queryTagCategories\""
|
||||||
FindScene *Scene "json:\"findScene\" graphql:\"findScene\""
|
FindScene *Scene "json:\"findScene\" graphql:\"findScene\""
|
||||||
FindSceneByFingerprint []*Scene "json:\"findSceneByFingerprint\" graphql:\"findSceneByFingerprint\""
|
FindSceneByFingerprint []*Scene "json:\"findSceneByFingerprint\" graphql:\"findSceneByFingerprint\""
|
||||||
FindScenesByFingerprints []*Scene "json:\"findScenesByFingerprints\" graphql:\"findScenesByFingerprints\""
|
FindScenesByFingerprints []*Scene "json:\"findScenesByFingerprints\" graphql:\"findScenesByFingerprints\""
|
||||||
QueryScenes QueryScenesResultType "json:\"queryScenes\" graphql:\"queryScenes\""
|
FindScenesByFullFingerprints []*Scene "json:\"findScenesByFullFingerprints\" graphql:\"findScenesByFullFingerprints\""
|
||||||
FindEdit *Edit "json:\"findEdit\" graphql:\"findEdit\""
|
QueryScenes QueryScenesResultType "json:\"queryScenes\" graphql:\"queryScenes\""
|
||||||
QueryEdits QueryEditsResultType "json:\"queryEdits\" graphql:\"queryEdits\""
|
FindEdit *Edit "json:\"findEdit\" graphql:\"findEdit\""
|
||||||
FindUser *User "json:\"findUser\" graphql:\"findUser\""
|
QueryEdits QueryEditsResultType "json:\"queryEdits\" graphql:\"queryEdits\""
|
||||||
QueryUsers QueryUsersResultType "json:\"queryUsers\" graphql:\"queryUsers\""
|
FindUser *User "json:\"findUser\" graphql:\"findUser\""
|
||||||
Me *User "json:\"me\" graphql:\"me\""
|
QueryUsers QueryUsersResultType "json:\"queryUsers\" graphql:\"queryUsers\""
|
||||||
SearchPerformer []*Performer "json:\"searchPerformer\" graphql:\"searchPerformer\""
|
Me *User "json:\"me\" graphql:\"me\""
|
||||||
SearchScene []*Scene "json:\"searchScene\" graphql:\"searchScene\""
|
SearchPerformer []*Performer "json:\"searchPerformer\" graphql:\"searchPerformer\""
|
||||||
Version Version "json:\"version\" graphql:\"version\""
|
SearchScene []*Scene "json:\"searchScene\" graphql:\"searchScene\""
|
||||||
|
Version Version "json:\"version\" graphql:\"version\""
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation struct {
|
type Mutation struct {
|
||||||
@@ -120,6 +121,7 @@ type PerformerFragment struct {
|
|||||||
Disambiguation *string "json:\"disambiguation\" graphql:\"disambiguation\""
|
Disambiguation *string "json:\"disambiguation\" graphql:\"disambiguation\""
|
||||||
Aliases []string "json:\"aliases\" graphql:\"aliases\""
|
Aliases []string "json:\"aliases\" graphql:\"aliases\""
|
||||||
Gender *GenderEnum "json:\"gender\" graphql:\"gender\""
|
Gender *GenderEnum "json:\"gender\" graphql:\"gender\""
|
||||||
|
MergedIds []string "json:\"merged_ids\" graphql:\"merged_ids\""
|
||||||
Urls []*URLFragment "json:\"urls\" graphql:\"urls\""
|
Urls []*URLFragment "json:\"urls\" graphql:\"urls\""
|
||||||
Images []*ImageFragment "json:\"images\" graphql:\"images\""
|
Images []*ImageFragment "json:\"images\" graphql:\"images\""
|
||||||
Birthdate *FuzzyDateFragment "json:\"birthdate\" graphql:\"birthdate\""
|
Birthdate *FuzzyDateFragment "json:\"birthdate\" graphql:\"birthdate\""
|
||||||
@@ -160,8 +162,8 @@ type SceneFragment struct {
|
|||||||
type FindSceneByFingerprint struct {
|
type FindSceneByFingerprint struct {
|
||||||
FindSceneByFingerprint []*SceneFragment "json:\"findSceneByFingerprint\" graphql:\"findSceneByFingerprint\""
|
FindSceneByFingerprint []*SceneFragment "json:\"findSceneByFingerprint\" graphql:\"findSceneByFingerprint\""
|
||||||
}
|
}
|
||||||
type FindScenesByFingerprints struct {
|
type FindScenesByFullFingerprints struct {
|
||||||
FindScenesByFingerprints []*SceneFragment "json:\"findScenesByFingerprints\" graphql:\"findScenesByFingerprints\""
|
FindScenesByFullFingerprints []*SceneFragment "json:\"findScenesByFullFingerprints\" graphql:\"findScenesByFullFingerprints\""
|
||||||
}
|
}
|
||||||
type SearchScene struct {
|
type SearchScene struct {
|
||||||
SearchScene []*SceneFragment "json:\"searchScene\" graphql:\"searchScene\""
|
SearchScene []*SceneFragment "json:\"searchScene\" graphql:\"searchScene\""
|
||||||
@@ -172,6 +174,9 @@ type SearchPerformer struct {
|
|||||||
type FindPerformerByID struct {
|
type FindPerformerByID struct {
|
||||||
FindPerformer *PerformerFragment "json:\"findPerformer\" graphql:\"findPerformer\""
|
FindPerformer *PerformerFragment "json:\"findPerformer\" graphql:\"findPerformer\""
|
||||||
}
|
}
|
||||||
|
type FindSceneByID struct {
|
||||||
|
FindScene *SceneFragment "json:\"findScene\" graphql:\"findScene\""
|
||||||
|
}
|
||||||
type SubmitFingerprintPayload struct {
|
type SubmitFingerprintPayload struct {
|
||||||
SubmitFingerprint bool "json:\"submitFingerprint\" graphql:\"submitFingerprint\""
|
SubmitFingerprint bool "json:\"submitFingerprint\" graphql:\"submitFingerprint\""
|
||||||
}
|
}
|
||||||
@@ -181,56 +186,10 @@ const FindSceneByFingerprintQuery = `query FindSceneByFingerprint ($fingerprint:
|
|||||||
... SceneFragment
|
... SceneFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fragment TagFragment on Tag {
|
|
||||||
name
|
|
||||||
id
|
|
||||||
}
|
|
||||||
fragment PerformerFragment on Performer {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
disambiguation
|
|
||||||
aliases
|
|
||||||
gender
|
|
||||||
urls {
|
|
||||||
... URLFragment
|
|
||||||
}
|
|
||||||
images {
|
|
||||||
... ImageFragment
|
|
||||||
}
|
|
||||||
birthdate {
|
|
||||||
... FuzzyDateFragment
|
|
||||||
}
|
|
||||||
ethnicity
|
|
||||||
country
|
|
||||||
eye_color
|
|
||||||
hair_color
|
|
||||||
height
|
|
||||||
measurements {
|
|
||||||
... MeasurementsFragment
|
|
||||||
}
|
|
||||||
breast_type
|
|
||||||
career_start_year
|
|
||||||
career_end_year
|
|
||||||
tattoos {
|
|
||||||
... BodyModificationFragment
|
|
||||||
}
|
|
||||||
piercings {
|
|
||||||
... BodyModificationFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fragment FuzzyDateFragment on FuzzyDate {
|
fragment FuzzyDateFragment on FuzzyDate {
|
||||||
date
|
date
|
||||||
accuracy
|
accuracy
|
||||||
}
|
}
|
||||||
fragment BodyModificationFragment on BodyModification {
|
|
||||||
location
|
|
||||||
description
|
|
||||||
}
|
|
||||||
fragment FingerprintFragment on Fingerprint {
|
|
||||||
algorithm
|
|
||||||
hash
|
|
||||||
duration
|
|
||||||
}
|
|
||||||
fragment SceneFragment on Scene {
|
fragment SceneFragment on Scene {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
@@ -270,24 +229,71 @@ fragment StudioFragment on Studio {
|
|||||||
... ImageFragment
|
... ImageFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fragment ImageFragment on Image {
|
|
||||||
id
|
|
||||||
url
|
|
||||||
width
|
|
||||||
height
|
|
||||||
}
|
|
||||||
fragment PerformerAppearanceFragment on PerformerAppearance {
|
fragment PerformerAppearanceFragment on PerformerAppearance {
|
||||||
as
|
as
|
||||||
performer {
|
performer {
|
||||||
... PerformerFragment
|
... PerformerFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fragment PerformerFragment on Performer {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
disambiguation
|
||||||
|
aliases
|
||||||
|
gender
|
||||||
|
merged_ids
|
||||||
|
urls {
|
||||||
|
... URLFragment
|
||||||
|
}
|
||||||
|
images {
|
||||||
|
... ImageFragment
|
||||||
|
}
|
||||||
|
birthdate {
|
||||||
|
... FuzzyDateFragment
|
||||||
|
}
|
||||||
|
ethnicity
|
||||||
|
country
|
||||||
|
eye_color
|
||||||
|
hair_color
|
||||||
|
height
|
||||||
|
measurements {
|
||||||
|
... MeasurementsFragment
|
||||||
|
}
|
||||||
|
breast_type
|
||||||
|
career_start_year
|
||||||
|
career_end_year
|
||||||
|
tattoos {
|
||||||
|
... BodyModificationFragment
|
||||||
|
}
|
||||||
|
piercings {
|
||||||
|
... BodyModificationFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fragment ImageFragment on Image {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
width
|
||||||
|
height
|
||||||
|
}
|
||||||
|
fragment TagFragment on Tag {
|
||||||
|
name
|
||||||
|
id
|
||||||
|
}
|
||||||
fragment MeasurementsFragment on Measurements {
|
fragment MeasurementsFragment on Measurements {
|
||||||
band_size
|
band_size
|
||||||
cup_size
|
cup_size
|
||||||
waist
|
waist
|
||||||
hip
|
hip
|
||||||
}
|
}
|
||||||
|
fragment BodyModificationFragment on BodyModification {
|
||||||
|
location
|
||||||
|
description
|
||||||
|
}
|
||||||
|
fragment FingerprintFragment on Fingerprint {
|
||||||
|
algorithm
|
||||||
|
hash
|
||||||
|
duration
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
func (c *Client) FindSceneByFingerprint(ctx context.Context, fingerprint FingerprintQueryInput, httpRequestOptions ...client.HTTPRequestOption) (*FindSceneByFingerprint, error) {
|
func (c *Client) FindSceneByFingerprint(ctx context.Context, fingerprint FingerprintQueryInput, httpRequestOptions ...client.HTTPRequestOption) (*FindSceneByFingerprint, error) {
|
||||||
@@ -303,31 +309,38 @@ func (c *Client) FindSceneByFingerprint(ctx context.Context, fingerprint Fingerp
|
|||||||
return &res, nil
|
return &res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const FindScenesByFingerprintsQuery = `query FindScenesByFingerprints ($fingerprints: [String!]!) {
|
const FindScenesByFullFingerprintsQuery = `query FindScenesByFullFingerprints ($fingerprints: [FingerprintQueryInput!]!) {
|
||||||
findScenesByFingerprints(fingerprints: $fingerprints) {
|
findScenesByFullFingerprints(fingerprints: $fingerprints) {
|
||||||
... SceneFragment
|
... SceneFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fragment ImageFragment on Image {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
width
|
||||||
|
height
|
||||||
|
}
|
||||||
|
fragment StudioFragment on Studio {
|
||||||
|
name
|
||||||
|
id
|
||||||
|
urls {
|
||||||
|
... URLFragment
|
||||||
|
}
|
||||||
|
images {
|
||||||
|
... ImageFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
fragment TagFragment on Tag {
|
fragment TagFragment on Tag {
|
||||||
name
|
name
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
fragment FuzzyDateFragment on FuzzyDate {
|
|
||||||
date
|
|
||||||
accuracy
|
|
||||||
}
|
|
||||||
fragment PerformerAppearanceFragment on PerformerAppearance {
|
|
||||||
as
|
|
||||||
performer {
|
|
||||||
... PerformerFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fragment PerformerFragment on Performer {
|
fragment PerformerFragment on Performer {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
disambiguation
|
disambiguation
|
||||||
aliases
|
aliases
|
||||||
gender
|
gender
|
||||||
|
merged_ids
|
||||||
urls {
|
urls {
|
||||||
... URLFragment
|
... URLFragment
|
||||||
}
|
}
|
||||||
@@ -355,16 +368,16 @@ fragment PerformerFragment on Performer {
|
|||||||
... BodyModificationFragment
|
... BodyModificationFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fragment FuzzyDateFragment on FuzzyDate {
|
||||||
|
date
|
||||||
|
accuracy
|
||||||
|
}
|
||||||
fragment MeasurementsFragment on Measurements {
|
fragment MeasurementsFragment on Measurements {
|
||||||
band_size
|
band_size
|
||||||
cup_size
|
cup_size
|
||||||
waist
|
waist
|
||||||
hip
|
hip
|
||||||
}
|
}
|
||||||
fragment BodyModificationFragment on BodyModification {
|
|
||||||
location
|
|
||||||
description
|
|
||||||
}
|
|
||||||
fragment SceneFragment on Scene {
|
fragment SceneFragment on Scene {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
@@ -390,40 +403,34 @@ fragment SceneFragment on Scene {
|
|||||||
... FingerprintFragment
|
... FingerprintFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fragment URLFragment on URL {
|
fragment PerformerAppearanceFragment on PerformerAppearance {
|
||||||
url
|
as
|
||||||
type
|
performer {
|
||||||
}
|
... PerformerFragment
|
||||||
fragment ImageFragment on Image {
|
|
||||||
id
|
|
||||||
url
|
|
||||||
width
|
|
||||||
height
|
|
||||||
}
|
|
||||||
fragment StudioFragment on Studio {
|
|
||||||
name
|
|
||||||
id
|
|
||||||
urls {
|
|
||||||
... URLFragment
|
|
||||||
}
|
|
||||||
images {
|
|
||||||
... ImageFragment
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fragment BodyModificationFragment on BodyModification {
|
||||||
|
location
|
||||||
|
description
|
||||||
|
}
|
||||||
fragment FingerprintFragment on Fingerprint {
|
fragment FingerprintFragment on Fingerprint {
|
||||||
algorithm
|
algorithm
|
||||||
hash
|
hash
|
||||||
duration
|
duration
|
||||||
}
|
}
|
||||||
|
fragment URLFragment on URL {
|
||||||
|
url
|
||||||
|
type
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
func (c *Client) FindScenesByFingerprints(ctx context.Context, fingerprints []string, httpRequestOptions ...client.HTTPRequestOption) (*FindScenesByFingerprints, error) {
|
func (c *Client) FindScenesByFullFingerprints(ctx context.Context, fingerprints []*FingerprintQueryInput, httpRequestOptions ...client.HTTPRequestOption) (*FindScenesByFullFingerprints, error) {
|
||||||
vars := map[string]interface{}{
|
vars := map[string]interface{}{
|
||||||
"fingerprints": fingerprints,
|
"fingerprints": fingerprints,
|
||||||
}
|
}
|
||||||
|
|
||||||
var res FindScenesByFingerprints
|
var res FindScenesByFullFingerprints
|
||||||
if err := c.Client.Post(ctx, FindScenesByFingerprintsQuery, &res, vars, httpRequestOptions...); err != nil {
|
if err := c.Client.Post(ctx, FindScenesByFullFingerprintsQuery, &res, vars, httpRequestOptions...); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,21 +442,6 @@ const SearchSceneQuery = `query SearchScene ($term: String!) {
|
|||||||
... SceneFragment
|
... SceneFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fragment MeasurementsFragment on Measurements {
|
|
||||||
band_size
|
|
||||||
cup_size
|
|
||||||
waist
|
|
||||||
hip
|
|
||||||
}
|
|
||||||
fragment BodyModificationFragment on BodyModification {
|
|
||||||
location
|
|
||||||
description
|
|
||||||
}
|
|
||||||
fragment FingerprintFragment on Fingerprint {
|
|
||||||
algorithm
|
|
||||||
hash
|
|
||||||
duration
|
|
||||||
}
|
|
||||||
fragment URLFragment on URL {
|
fragment URLFragment on URL {
|
||||||
url
|
url
|
||||||
type
|
type
|
||||||
@@ -468,6 +460,11 @@ fragment FuzzyDateFragment on FuzzyDate {
|
|||||||
date
|
date
|
||||||
accuracy
|
accuracy
|
||||||
}
|
}
|
||||||
|
fragment FingerprintFragment on Fingerprint {
|
||||||
|
algorithm
|
||||||
|
hash
|
||||||
|
duration
|
||||||
|
}
|
||||||
fragment SceneFragment on Scene {
|
fragment SceneFragment on Scene {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
@@ -515,6 +512,7 @@ fragment PerformerFragment on Performer {
|
|||||||
disambiguation
|
disambiguation
|
||||||
aliases
|
aliases
|
||||||
gender
|
gender
|
||||||
|
merged_ids
|
||||||
urls {
|
urls {
|
||||||
... URLFragment
|
... URLFragment
|
||||||
}
|
}
|
||||||
@@ -542,6 +540,16 @@ fragment PerformerFragment on Performer {
|
|||||||
... BodyModificationFragment
|
... BodyModificationFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fragment MeasurementsFragment on Measurements {
|
||||||
|
band_size
|
||||||
|
cup_size
|
||||||
|
waist
|
||||||
|
hip
|
||||||
|
}
|
||||||
|
fragment BodyModificationFragment on BodyModification {
|
||||||
|
location
|
||||||
|
description
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
func (c *Client) SearchScene(ctx context.Context, term string, httpRequestOptions ...client.HTTPRequestOption) (*SearchScene, error) {
|
func (c *Client) SearchScene(ctx context.Context, term string, httpRequestOptions ...client.HTTPRequestOption) (*SearchScene, error) {
|
||||||
@@ -562,6 +570,16 @@ const SearchPerformerQuery = `query SearchPerformer ($term: String!) {
|
|||||||
... PerformerFragment
|
... PerformerFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fragment URLFragment on URL {
|
||||||
|
url
|
||||||
|
type
|
||||||
|
}
|
||||||
|
fragment ImageFragment on Image {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
width
|
||||||
|
height
|
||||||
|
}
|
||||||
fragment FuzzyDateFragment on FuzzyDate {
|
fragment FuzzyDateFragment on FuzzyDate {
|
||||||
date
|
date
|
||||||
accuracy
|
accuracy
|
||||||
@@ -582,6 +600,7 @@ fragment PerformerFragment on Performer {
|
|||||||
disambiguation
|
disambiguation
|
||||||
aliases
|
aliases
|
||||||
gender
|
gender
|
||||||
|
merged_ids
|
||||||
urls {
|
urls {
|
||||||
... URLFragment
|
... URLFragment
|
||||||
}
|
}
|
||||||
@@ -609,16 +628,6 @@ fragment PerformerFragment on Performer {
|
|||||||
... BodyModificationFragment
|
... BodyModificationFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fragment URLFragment on URL {
|
|
||||||
url
|
|
||||||
type
|
|
||||||
}
|
|
||||||
fragment ImageFragment on Image {
|
|
||||||
id
|
|
||||||
url
|
|
||||||
width
|
|
||||||
height
|
|
||||||
}
|
|
||||||
`
|
`
|
||||||
|
|
||||||
func (c *Client) SearchPerformer(ctx context.Context, term string, httpRequestOptions ...client.HTTPRequestOption) (*SearchPerformer, error) {
|
func (c *Client) SearchPerformer(ctx context.Context, term string, httpRequestOptions ...client.HTTPRequestOption) (*SearchPerformer, error) {
|
||||||
@@ -639,26 +648,13 @@ const FindPerformerByIDQuery = `query FindPerformerByID ($id: ID!) {
|
|||||||
... PerformerFragment
|
... PerformerFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 {
|
fragment PerformerFragment on Performer {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
disambiguation
|
disambiguation
|
||||||
aliases
|
aliases
|
||||||
gender
|
gender
|
||||||
|
merged_ids
|
||||||
urls {
|
urls {
|
||||||
... URLFragment
|
... URLFragment
|
||||||
}
|
}
|
||||||
@@ -696,6 +692,20 @@ fragment ImageFragment on Image {
|
|||||||
width
|
width
|
||||||
height
|
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) FindPerformerByID(ctx context.Context, id string, httpRequestOptions ...client.HTTPRequestOption) (*FindPerformerByID, error) {
|
func (c *Client) FindPerformerByID(ctx context.Context, id string, httpRequestOptions ...client.HTTPRequestOption) (*FindPerformerByID, error) {
|
||||||
@@ -711,6 +721,134 @@ func (c *Client) FindPerformerByID(ctx context.Context, id string, httpRequestOp
|
|||||||
return &res, nil
|
return &res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FindSceneByIDQuery = `query FindSceneByID ($id: ID!) {
|
||||||
|
findScene(id: $id) {
|
||||||
|
... SceneFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fragment ImageFragment on Image {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
width
|
||||||
|
height
|
||||||
|
}
|
||||||
|
fragment StudioFragment on Studio {
|
||||||
|
name
|
||||||
|
id
|
||||||
|
urls {
|
||||||
|
... URLFragment
|
||||||
|
}
|
||||||
|
images {
|
||||||
|
... ImageFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fragment TagFragment on Tag {
|
||||||
|
name
|
||||||
|
id
|
||||||
|
}
|
||||||
|
fragment PerformerFragment on Performer {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
disambiguation
|
||||||
|
aliases
|
||||||
|
gender
|
||||||
|
merged_ids
|
||||||
|
urls {
|
||||||
|
... URLFragment
|
||||||
|
}
|
||||||
|
images {
|
||||||
|
... ImageFragment
|
||||||
|
}
|
||||||
|
birthdate {
|
||||||
|
... FuzzyDateFragment
|
||||||
|
}
|
||||||
|
ethnicity
|
||||||
|
country
|
||||||
|
eye_color
|
||||||
|
hair_color
|
||||||
|
height
|
||||||
|
measurements {
|
||||||
|
... MeasurementsFragment
|
||||||
|
}
|
||||||
|
breast_type
|
||||||
|
career_start_year
|
||||||
|
career_end_year
|
||||||
|
tattoos {
|
||||||
|
... BodyModificationFragment
|
||||||
|
}
|
||||||
|
piercings {
|
||||||
|
... BodyModificationFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fragment MeasurementsFragment on Measurements {
|
||||||
|
band_size
|
||||||
|
cup_size
|
||||||
|
waist
|
||||||
|
hip
|
||||||
|
}
|
||||||
|
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 BodyModificationFragment on BodyModification {
|
||||||
|
location
|
||||||
|
description
|
||||||
|
}
|
||||||
|
fragment PerformerAppearanceFragment on PerformerAppearance {
|
||||||
|
as
|
||||||
|
performer {
|
||||||
|
... PerformerFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fragment FuzzyDateFragment on FuzzyDate {
|
||||||
|
date
|
||||||
|
accuracy
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
func (c *Client) FindSceneByID(ctx context.Context, id string, httpRequestOptions ...client.HTTPRequestOption) (*FindSceneByID, error) {
|
||||||
|
vars := map[string]interface{}{
|
||||||
|
"id": id,
|
||||||
|
}
|
||||||
|
|
||||||
|
var res FindSceneByID
|
||||||
|
if err := c.Client.Post(ctx, FindSceneByIDQuery, &res, vars, httpRequestOptions...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &res, nil
|
||||||
|
}
|
||||||
|
|
||||||
const SubmitFingerprintQuery = `mutation SubmitFingerprint ($input: FingerprintSubmission!) {
|
const SubmitFingerprintQuery = `mutation SubmitFingerprint ($input: FingerprintSubmission!) {
|
||||||
submitFingerprint(input: $input)
|
submitFingerprint(input: $input)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ type Edit struct {
|
|||||||
Status VoteStatusEnum `json:"status"`
|
Status VoteStatusEnum `json:"status"`
|
||||||
Applied bool `json:"applied"`
|
Applied bool `json:"applied"`
|
||||||
Created time.Time `json:"created"`
|
Created time.Time `json:"created"`
|
||||||
|
Updated time.Time `json:"updated"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type EditComment struct {
|
type EditComment struct {
|
||||||
@@ -134,9 +135,21 @@ type EyeColorCriterionInput struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Fingerprint struct {
|
type Fingerprint struct {
|
||||||
Hash string `json:"hash"`
|
Hash string `json:"hash"`
|
||||||
Algorithm FingerprintAlgorithm `json:"algorithm"`
|
Algorithm FingerprintAlgorithm `json:"algorithm"`
|
||||||
Duration int `json:"duration"`
|
Duration int `json:"duration"`
|
||||||
|
Submissions int `json:"submissions"`
|
||||||
|
Created time.Time `json:"created"`
|
||||||
|
Updated time.Time `json:"updated"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FingerprintEditInput struct {
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
Algorithm FingerprintAlgorithm `json:"algorithm"`
|
||||||
|
Duration int `json:"duration"`
|
||||||
|
Submissions int `json:"submissions"`
|
||||||
|
Created time.Time `json:"created"`
|
||||||
|
Updated time.Time `json:"updated"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FingerprintInput struct {
|
type FingerprintInput struct {
|
||||||
@@ -255,6 +268,8 @@ type Performer struct {
|
|||||||
Deleted bool `json:"deleted"`
|
Deleted bool `json:"deleted"`
|
||||||
Edits []*Edit `json:"edits"`
|
Edits []*Edit `json:"edits"`
|
||||||
SceneCount int `json:"scene_count"`
|
SceneCount int `json:"scene_count"`
|
||||||
|
MergedIds []string `json:"merged_ids"`
|
||||||
|
Studios []*PerformerStudio `json:"studios"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Performer) IsEditTarget() {}
|
func (Performer) IsEditTarget() {}
|
||||||
@@ -359,16 +374,16 @@ type PerformerEditInput struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PerformerEditOptions struct {
|
type PerformerEditOptions struct {
|
||||||
// Set performer alias on scenes without alias to old name if name is changed
|
// Set performer alias on scenes without alias to old name if name is changed
|
||||||
SetModifyAliases bool `json:"set_modify_aliases"`
|
SetModifyAliases bool `json:"set_modify_aliases"`
|
||||||
// Set performer alias on scenes attached to merge sources to old name
|
// Set performer alias on scenes attached to merge sources to old name
|
||||||
SetMergeAliases bool `json:"set_merge_aliases"`
|
SetMergeAliases bool `json:"set_merge_aliases"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PerformerEditOptionsInput struct {
|
type PerformerEditOptionsInput struct {
|
||||||
// Set performer alias on scenes without alias to old name if name is changed
|
// Set performer alias on scenes without alias to old name if name is changed
|
||||||
SetModifyAliases *bool `json:"set_modify_aliases"`
|
SetModifyAliases *bool `json:"set_modify_aliases"`
|
||||||
// Set performer alias on scenes attached to merge sources to old name
|
// Set performer alias on scenes attached to merge sources to old name
|
||||||
SetMergeAliases *bool `json:"set_merge_aliases"`
|
SetMergeAliases *bool `json:"set_merge_aliases"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,6 +417,11 @@ type PerformerFilterType struct {
|
|||||||
Piercings *BodyModificationCriterionInput `json:"piercings"`
|
Piercings *BodyModificationCriterionInput `json:"piercings"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PerformerStudio struct {
|
||||||
|
Studio *Studio `json:"studio"`
|
||||||
|
SceneCount int `json:"scene_count"`
|
||||||
|
}
|
||||||
|
|
||||||
type PerformerUpdateInput struct {
|
type PerformerUpdateInput struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name *string `json:"name"`
|
Name *string `json:"name"`
|
||||||
@@ -507,7 +527,7 @@ type SceneCreateInput struct {
|
|||||||
Performers []*PerformerAppearanceInput `json:"performers"`
|
Performers []*PerformerAppearanceInput `json:"performers"`
|
||||||
TagIds []string `json:"tag_ids"`
|
TagIds []string `json:"tag_ids"`
|
||||||
ImageIds []string `json:"image_ids"`
|
ImageIds []string `json:"image_ids"`
|
||||||
Fingerprints []*FingerprintInput `json:"fingerprints"`
|
Fingerprints []*FingerprintEditInput `json:"fingerprints"`
|
||||||
Duration *int `json:"duration"`
|
Duration *int `json:"duration"`
|
||||||
Director *string `json:"director"`
|
Director *string `json:"director"`
|
||||||
}
|
}
|
||||||
@@ -547,7 +567,7 @@ type SceneEditDetailsInput struct {
|
|||||||
Performers []*PerformerAppearanceInput `json:"performers"`
|
Performers []*PerformerAppearanceInput `json:"performers"`
|
||||||
TagIds []string `json:"tag_ids"`
|
TagIds []string `json:"tag_ids"`
|
||||||
ImageIds []string `json:"image_ids"`
|
ImageIds []string `json:"image_ids"`
|
||||||
Fingerprints []*FingerprintInput `json:"fingerprints"`
|
Fingerprints []*FingerprintEditInput `json:"fingerprints"`
|
||||||
Duration *int `json:"duration"`
|
Duration *int `json:"duration"`
|
||||||
Director *string `json:"director"`
|
Director *string `json:"director"`
|
||||||
}
|
}
|
||||||
@@ -578,6 +598,8 @@ type SceneFilterType struct {
|
|||||||
Performers *MultiIDCriterionInput `json:"performers"`
|
Performers *MultiIDCriterionInput `json:"performers"`
|
||||||
// Filter to include scenes with performer appearing as alias
|
// Filter to include scenes with performer appearing as alias
|
||||||
Alias *StringCriterionInput `json:"alias"`
|
Alias *StringCriterionInput `json:"alias"`
|
||||||
|
// Filter to only include scenes with these fingerprints
|
||||||
|
Fingerprints *MultiIDCriterionInput `json:"fingerprints"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SceneUpdateInput struct {
|
type SceneUpdateInput struct {
|
||||||
@@ -590,7 +612,7 @@ type SceneUpdateInput struct {
|
|||||||
Performers []*PerformerAppearanceInput `json:"performers"`
|
Performers []*PerformerAppearanceInput `json:"performers"`
|
||||||
TagIds []string `json:"tag_ids"`
|
TagIds []string `json:"tag_ids"`
|
||||||
ImageIds []string `json:"image_ids"`
|
ImageIds []string `json:"image_ids"`
|
||||||
Fingerprints []*FingerprintInput `json:"fingerprints"`
|
Fingerprints []*FingerprintEditInput `json:"fingerprints"`
|
||||||
Duration *int `json:"duration"`
|
Duration *int `json:"duration"`
|
||||||
Director *string `json:"director"`
|
Director *string `json:"director"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ func (c Client) FindStashBoxScenesByFingerprints(sceneIDs []string) ([][]*models
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var fingerprints []string
|
var fingerprints []*graphql.FingerprintQueryInput
|
||||||
// map fingerprints to their scene index
|
// map fingerprints to their scene index
|
||||||
fpToScene := make(map[string][]int)
|
fpToScene := make(map[string][]int)
|
||||||
|
|
||||||
@@ -93,18 +93,27 @@ func (c Client) FindStashBoxScenesByFingerprints(sceneIDs []string) ([][]*models
|
|||||||
}
|
}
|
||||||
|
|
||||||
if scene.Checksum.Valid {
|
if scene.Checksum.Valid {
|
||||||
fingerprints = append(fingerprints, scene.Checksum.String)
|
fingerprints = append(fingerprints, &graphql.FingerprintQueryInput{
|
||||||
|
Hash: scene.Checksum.String,
|
||||||
|
Algorithm: graphql.FingerprintAlgorithmMd5,
|
||||||
|
})
|
||||||
fpToScene[scene.Checksum.String] = append(fpToScene[scene.Checksum.String], index)
|
fpToScene[scene.Checksum.String] = append(fpToScene[scene.Checksum.String], index)
|
||||||
}
|
}
|
||||||
|
|
||||||
if scene.OSHash.Valid {
|
if scene.OSHash.Valid {
|
||||||
fingerprints = append(fingerprints, scene.OSHash.String)
|
fingerprints = append(fingerprints, &graphql.FingerprintQueryInput{
|
||||||
|
Hash: scene.OSHash.String,
|
||||||
|
Algorithm: graphql.FingerprintAlgorithmOshash,
|
||||||
|
})
|
||||||
fpToScene[scene.OSHash.String] = append(fpToScene[scene.OSHash.String], index)
|
fpToScene[scene.OSHash.String] = append(fpToScene[scene.OSHash.String], index)
|
||||||
}
|
}
|
||||||
|
|
||||||
if scene.Phash.Valid {
|
if scene.Phash.Valid {
|
||||||
phashStr := utils.PhashToString(scene.Phash.Int64)
|
phashStr := utils.PhashToString(scene.Phash.Int64)
|
||||||
fingerprints = append(fingerprints, phashStr)
|
fingerprints = append(fingerprints, &graphql.FingerprintQueryInput{
|
||||||
|
Hash: phashStr,
|
||||||
|
Algorithm: graphql.FingerprintAlgorithmPhash,
|
||||||
|
})
|
||||||
fpToScene[phashStr] = append(fpToScene[phashStr], index)
|
fpToScene[phashStr] = append(fpToScene[phashStr], index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,7 +156,7 @@ func (c Client) FindStashBoxScenesByFingerprintsFlat(sceneIDs []string) ([]*mode
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var fingerprints []string
|
var fingerprints []*graphql.FingerprintQueryInput
|
||||||
|
|
||||||
if err := c.txnManager.WithReadTxn(ctx, func(r models.ReaderRepository) error {
|
if err := c.txnManager.WithReadTxn(ctx, func(r models.ReaderRepository) error {
|
||||||
qb := r.Scene()
|
qb := r.Scene()
|
||||||
@@ -163,16 +172,24 @@ func (c Client) FindStashBoxScenesByFingerprintsFlat(sceneIDs []string) ([]*mode
|
|||||||
}
|
}
|
||||||
|
|
||||||
if scene.Checksum.Valid {
|
if scene.Checksum.Valid {
|
||||||
fingerprints = append(fingerprints, scene.Checksum.String)
|
fingerprints = append(fingerprints, &graphql.FingerprintQueryInput{
|
||||||
|
Hash: scene.Checksum.String,
|
||||||
|
Algorithm: graphql.FingerprintAlgorithmMd5,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if scene.OSHash.Valid {
|
if scene.OSHash.Valid {
|
||||||
fingerprints = append(fingerprints, scene.OSHash.String)
|
fingerprints = append(fingerprints, &graphql.FingerprintQueryInput{
|
||||||
|
Hash: scene.OSHash.String,
|
||||||
|
Algorithm: graphql.FingerprintAlgorithmOshash,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if scene.Phash.Valid {
|
if scene.Phash.Valid {
|
||||||
phashStr := utils.PhashToString(scene.Phash.Int64)
|
fingerprints = append(fingerprints, &graphql.FingerprintQueryInput{
|
||||||
fingerprints = append(fingerprints, phashStr)
|
Hash: utils.PhashToString(scene.Phash.Int64),
|
||||||
|
Algorithm: graphql.FingerprintAlgorithmPhash,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,20 +201,20 @@ func (c Client) FindStashBoxScenesByFingerprintsFlat(sceneIDs []string) ([]*mode
|
|||||||
return c.findStashBoxScenesByFingerprints(ctx, fingerprints)
|
return c.findStashBoxScenesByFingerprints(ctx, fingerprints)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Client) findStashBoxScenesByFingerprints(ctx context.Context, fingerprints []string) ([]*models.ScrapedScene, error) {
|
func (c Client) findStashBoxScenesByFingerprints(ctx context.Context, fingerprints []*graphql.FingerprintQueryInput) ([]*models.ScrapedScene, error) {
|
||||||
var ret []*models.ScrapedScene
|
var ret []*models.ScrapedScene
|
||||||
for i := 0; i < len(fingerprints); i += 100 {
|
for i := 0; i < len(fingerprints); i += 100 {
|
||||||
end := i + 100
|
end := i + 100
|
||||||
if end > len(fingerprints) {
|
if end > len(fingerprints) {
|
||||||
end = len(fingerprints)
|
end = len(fingerprints)
|
||||||
}
|
}
|
||||||
scenes, err := c.client.FindScenesByFingerprints(ctx, fingerprints[i:end])
|
scenes, err := c.client.FindScenesByFullFingerprints(ctx, fingerprints[i:end])
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sceneFragments := scenes.FindScenesByFingerprints
|
sceneFragments := scenes.FindScenesByFullFingerprints
|
||||||
|
|
||||||
for _, s := range sceneFragments {
|
for _, s := range sceneFragments {
|
||||||
ss, err := sceneFragmentToScrapedScene(ctx, c.getHTTPClient(), c.txnManager, s)
|
ss, err := sceneFragmentToScrapedScene(ctx, c.getHTTPClient(), c.txnManager, s)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
"formik": "^2.2.6",
|
"formik": "^2.2.6",
|
||||||
"graphql": "^15.4.0",
|
"graphql": "^15.4.0",
|
||||||
"graphql-tag": "^2.11.0",
|
"graphql-tag": "^2.11.0",
|
||||||
|
"hamming-distance": "^1.0.0",
|
||||||
"i18n-iso-countries": "^6.4.0",
|
"i18n-iso-countries": "^6.4.0",
|
||||||
"intersection-observer": "^0.12.0",
|
"intersection-observer": "^0.12.0",
|
||||||
"jimp": "^0.16.1",
|
"jimp": "^0.16.1",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
### ✨ New Features
|
### ✨ New Features
|
||||||
|
* Added support for matching scenes using perceptual hashes when querying stash-box. ([#1858](https://github.com/stashapp/stash/pull/1858))
|
||||||
* Generalised Tagger view to support tagging using supported scene scrapers. ([#1812](https://github.com/stashapp/stash/pull/1812))
|
* Generalised Tagger view to support tagging using supported scene scrapers. ([#1812](https://github.com/stashapp/stash/pull/1812))
|
||||||
* Added built-in `Auto Tag` scene scraper to match performers, studio and tags from filename - using AutoTag logic. ([#1817](https://github.com/stashapp/stash/pull/1817))
|
* Added built-in `Auto Tag` scene scraper to match performers, studio and tags from filename - using AutoTag logic. ([#1817](https://github.com/stashapp/stash/pull/1817))
|
||||||
* Added interface options to disable creating performers/studios/tags from dropdown selectors. ([#1814](https://github.com/stashapp/stash/pull/1814))
|
* Added interface options to disable creating performers/studios/tags from dropdown selectors. ([#1814](https://github.com/stashapp/stash/pull/1814))
|
||||||
|
|||||||
@@ -17,3 +17,4 @@ export { GridCard } from "./GridCard";
|
|||||||
export { RatingStars } from "./RatingStars";
|
export { RatingStars } from "./RatingStars";
|
||||||
export { ExportDialog } from "./ExportDialog";
|
export { ExportDialog } from "./ExportDialog";
|
||||||
export { default as DeleteEntityDialog } from "./DeleteEntityDialog";
|
export { default as DeleteEntityDialog } from "./DeleteEntityDialog";
|
||||||
|
export { OperationButton } from "./OperationButton";
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export { Tagger as default } from "./Tagger";
|
export { Tagger as default } from "./scenes/SceneTagger";
|
||||||
export { PerformerTagger } from "./performers/PerformerTagger";
|
export { PerformerTagger } from "./performers/PerformerTagger";
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ import {
|
|||||||
InputGroup,
|
InputGroup,
|
||||||
} from "react-bootstrap";
|
} from "react-bootstrap";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { Icon } from "src/components/Shared";
|
import { Icon } from "src/components/Shared";
|
||||||
import { ParseMode, TagOperation } from "./constants";
|
import { ParseMode, TagOperation } from "../constants";
|
||||||
import { TaggerStateContext } from "./context";
|
import { TaggerStateContext } from "../context";
|
||||||
|
|
||||||
interface IConfigProps {
|
interface IConfigProps {
|
||||||
show: boolean;
|
show: boolean;
|
||||||
@@ -3,12 +3,14 @@ import { Button, ButtonGroup } from "react-bootstrap";
|
|||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
|
|
||||||
import { Icon, PerformerSelect } from "src/components/Shared";
|
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { ValidTypes } from "src/components/Shared/Select";
|
import {
|
||||||
|
Icon,
|
||||||
import { OptionalField } from "./IncludeButton";
|
OperationButton,
|
||||||
import { OperationButton } from "../Shared/OperationButton";
|
PerformerSelect,
|
||||||
|
ValidTypes,
|
||||||
|
} from "src/components/Shared";
|
||||||
|
import { OptionalField } from "../IncludeButton";
|
||||||
|
|
||||||
interface IPerformerResultProps {
|
interface IPerformerResultProps {
|
||||||
performer: GQL.ScrapedPerformer;
|
performer: GQL.ScrapedPerformer;
|
||||||
@@ -3,9 +3,10 @@ import * as GQL from "src/core/generated-graphql";
|
|||||||
import { SceneQueue } from "src/models/sceneQueue";
|
import { SceneQueue } from "src/models/sceneQueue";
|
||||||
import { Button, Form } from "react-bootstrap";
|
import { Button, Form } from "react-bootstrap";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { Icon, LoadingIndicator } from "src/components/Shared";
|
import { Icon, LoadingIndicator } from "src/components/Shared";
|
||||||
import { OperationButton } from "src/components/Shared/OperationButton";
|
import { OperationButton } from "src/components/Shared/OperationButton";
|
||||||
import { TaggerStateContext } from "./context";
|
import { TaggerStateContext } from "../context";
|
||||||
import Config from "./Config";
|
import Config from "./Config";
|
||||||
import { TaggerScene } from "./TaggerScene";
|
import { TaggerScene } from "./TaggerScene";
|
||||||
import { SceneTaggerModals } from "./sceneTaggerModals";
|
import { SceneTaggerModals } from "./sceneTaggerModals";
|
||||||
@@ -2,22 +2,24 @@ import React, { useState, useEffect, useCallback, useMemo } from "react";
|
|||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { Badge, Button, Col, Form, Row } from "react-bootstrap";
|
import { Badge, Button, Col, Form, Row } from "react-bootstrap";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
import { uniq } from "lodash";
|
||||||
|
import { blobToBase64 } from "base64-blob";
|
||||||
|
import distance from "hamming-distance";
|
||||||
|
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import {
|
import {
|
||||||
|
HoverPopover,
|
||||||
Icon,
|
Icon,
|
||||||
LoadingIndicator,
|
LoadingIndicator,
|
||||||
SuccessIcon,
|
SuccessIcon,
|
||||||
TagSelect,
|
TagSelect,
|
||||||
TruncatedText,
|
TruncatedText,
|
||||||
|
OperationButton,
|
||||||
} from "src/components/Shared";
|
} from "src/components/Shared";
|
||||||
import { FormUtils } from "src/utils";
|
import { FormUtils } from "src/utils";
|
||||||
import { uniq } from "lodash";
|
|
||||||
import { blobToBase64 } from "base64-blob";
|
|
||||||
import { stringToGender } from "src/utils/gender";
|
import { stringToGender } from "src/utils/gender";
|
||||||
import { OptionalField } from "./IncludeButton";
|
import { IScrapedScene, TaggerStateContext } from "../context";
|
||||||
import { IScrapedScene, TaggerStateContext } from "./context";
|
import { OptionalField } from "../IncludeButton";
|
||||||
import { OperationButton } from "../Shared/OperationButton";
|
|
||||||
import { SceneTaggerModalsState } from "./sceneTaggerModals";
|
import { SceneTaggerModalsState } from "./sceneTaggerModals";
|
||||||
import PerformerResult from "./PerformerResult";
|
import PerformerResult from "./PerformerResult";
|
||||||
import StudioResult from "./StudioResult";
|
import StudioResult from "./StudioResult";
|
||||||
@@ -77,23 +79,58 @@ const getFingerprintStatus = (
|
|||||||
const checksumMatch = scene.fingerprints?.some(
|
const checksumMatch = scene.fingerprints?.some(
|
||||||
(f) => f.hash === stashScene.checksum || f.hash === stashScene.oshash
|
(f) => f.hash === stashScene.checksum || f.hash === stashScene.oshash
|
||||||
);
|
);
|
||||||
const phashMatch = scene.fingerprints?.some(
|
const phashMatches =
|
||||||
(f) => f.hash === stashScene.phash
|
scene.fingerprints?.filter(
|
||||||
|
(f) => f.algorithm === "PHASH" && distance(f.hash, stashScene.phash) <= 8
|
||||||
|
) ?? [];
|
||||||
|
|
||||||
|
const phashList = (
|
||||||
|
<div className="m-2">
|
||||||
|
{phashMatches.map((fp) => (
|
||||||
|
<div>
|
||||||
|
<b>{fp.hash}</b>
|
||||||
|
{fp.hash === stashScene.phash
|
||||||
|
? ", Exact match"
|
||||||
|
: `, distance ${distance(fp.hash, stashScene.phash)}`}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
if (checksumMatch || phashMatch)
|
|
||||||
|
if (checksumMatch || phashMatches.length > 0)
|
||||||
return (
|
return (
|
||||||
<div className="font-weight-bold">
|
<div className="font-weight-bold">
|
||||||
<SuccessIcon className="mr-2" />
|
<SuccessIcon className="mr-2" />
|
||||||
<FormattedMessage
|
{phashMatches.length > 0 ? (
|
||||||
id="component_tagger.results.hash_matches"
|
<HoverPopover
|
||||||
values={{
|
placement="bottom"
|
||||||
hash_type: (
|
content={phashList}
|
||||||
|
className="PHashPopover"
|
||||||
|
>
|
||||||
|
{phashMatches.length > 1 ? (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id={`media_info.${phashMatch ? "phash" : "checksum"}`}
|
id="component_tagger.results.phash_matches"
|
||||||
|
values={{
|
||||||
|
count: phashMatches.length,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
),
|
) : (
|
||||||
}}
|
<FormattedMessage
|
||||||
/>
|
id="component_tagger.results.hash_matches"
|
||||||
|
values={{
|
||||||
|
hash_type: <FormattedMessage id="media_info.phash" />,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</HoverPopover>
|
||||||
|
) : (
|
||||||
|
<FormattedMessage
|
||||||
|
id="component_tagger.results.hash_matches"
|
||||||
|
values={{
|
||||||
|
hash_type: <FormattedMessage id="media_info.checksum" />,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -2,9 +2,9 @@ import React, { useContext } from "react";
|
|||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { IconName } from "@fortawesome/fontawesome-svg-core";
|
import { IconName } from "@fortawesome/fontawesome-svg-core";
|
||||||
|
|
||||||
import { Icon, Modal, TruncatedText } from "src/components/Shared";
|
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { TaggerStateContext } from "./context";
|
import { Icon, Modal, TruncatedText } from "src/components/Shared";
|
||||||
|
import { TaggerStateContext } from "../context";
|
||||||
|
|
||||||
interface IStudioModalProps {
|
interface IStudioModalProps {
|
||||||
studio: GQL.ScrapedSceneStudioDataFragment;
|
studio: GQL.ScrapedSceneStudioDataFragment;
|
||||||
@@ -3,12 +3,15 @@ import { Button, ButtonGroup } from "react-bootstrap";
|
|||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
|
|
||||||
import { Icon, StudioSelect } from "src/components/Shared";
|
import {
|
||||||
|
Icon,
|
||||||
|
OperationButton,
|
||||||
|
StudioSelect,
|
||||||
|
ValidTypes,
|
||||||
|
} from "src/components/Shared";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { ValidTypes } from "src/components/Shared/Select";
|
|
||||||
|
|
||||||
import { OptionalField } from "./IncludeButton";
|
import { OptionalField } from "../IncludeButton";
|
||||||
import { OperationButton } from "../Shared/OperationButton";
|
|
||||||
|
|
||||||
interface IStudioResultProps {
|
interface IStudioResultProps {
|
||||||
studio: GQL.ScrapedStudio;
|
studio: GQL.ScrapedStudio;
|
||||||
@@ -1,14 +1,19 @@
|
|||||||
import React, { useState, useContext, PropsWithChildren } from "react";
|
import React, { useState, useContext, PropsWithChildren } from "react";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Icon, TagLink, TruncatedText } from "src/components/Shared";
|
|
||||||
import { Button, Collapse, Form, InputGroup } from "react-bootstrap";
|
import { Button, Collapse, Form, InputGroup } from "react-bootstrap";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
import { sortPerformers } from "src/core/performers";
|
import { sortPerformers } from "src/core/performers";
|
||||||
|
import {
|
||||||
|
Icon,
|
||||||
|
OperationButton,
|
||||||
|
TagLink,
|
||||||
|
TruncatedText,
|
||||||
|
} from "src/components/Shared";
|
||||||
import { parsePath, prepareQueryString } from "src/components/Tagger/utils";
|
import { parsePath, prepareQueryString } from "src/components/Tagger/utils";
|
||||||
import { OperationButton } from "src/components/Shared/OperationButton";
|
import { ScenePreview } from "src/components/Scenes/SceneCard";
|
||||||
import { TaggerStateContext } from "./context";
|
import { TaggerStateContext } from "../context";
|
||||||
import { ScenePreview } from "../Scenes/SceneCard";
|
|
||||||
|
|
||||||
interface ITaggerSceneDetails {
|
interface ITaggerSceneDetails {
|
||||||
scene: GQL.SlimSceneDataFragment;
|
scene: GQL.SlimSceneDataFragment;
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import React, { useState, useContext } from "react";
|
import React, { useState, useContext } from "react";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import PerformerModal from "./PerformerModal";
|
|
||||||
import StudioModal from "./StudioModal";
|
import StudioModal from "./StudioModal";
|
||||||
import { TaggerStateContext } from "./context";
|
import PerformerModal from "../PerformerModal";
|
||||||
|
import { TaggerStateContext } from "../context";
|
||||||
|
|
||||||
type PerformerModalCallback = (toCreate?: GQL.PerformerCreateInput) => void;
|
type PerformerModalCallback = (toCreate?: GQL.PerformerCreateInput) => void;
|
||||||
type StudioModalCallback = (toCreate?: GQL.StudioCreateInput) => void;
|
type StudioModalCallback = (toCreate?: GQL.StudioCreateInput) => void;
|
||||||
@@ -270,3 +270,8 @@ li.active .optional-field.excluded .scene-link {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.PHashPopover {
|
||||||
|
display: inline-block;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
}
|
||||||
|
|||||||
1
ui/v2.5/src/globals.d.ts
vendored
1
ui/v2.5/src/globals.d.ts
vendored
@@ -3,6 +3,7 @@ declare var STASH_BASE_URL: string;
|
|||||||
declare module "*.md";
|
declare module "*.md";
|
||||||
declare module "string.prototype.replaceall";
|
declare module "string.prototype.replaceall";
|
||||||
declare module "mousetrap-pause";
|
declare module "mousetrap-pause";
|
||||||
|
declare module "hamming-distance";
|
||||||
declare module "@formatjs/intl-pluralrules/locale-data/en";
|
declare module "@formatjs/intl-pluralrules/locale-data/en";
|
||||||
declare module "@formatjs/intl-numberformat/locale-data/en";
|
declare module "@formatjs/intl-numberformat/locale-data/en";
|
||||||
declare module "@formatjs/intl-numberformat/locale-data/en-GB";
|
declare module "@formatjs/intl-numberformat/locale-data/en-GB";
|
||||||
|
|||||||
@@ -137,7 +137,8 @@
|
|||||||
"match_success": "Scene successfully tagged",
|
"match_success": "Scene successfully tagged",
|
||||||
"unnamed": "Unnamed",
|
"unnamed": "Unnamed",
|
||||||
"duration_off": "Duration off by at least {number}s",
|
"duration_off": "Duration off by at least {number}s",
|
||||||
"duration_unknown": "Duration unknown"
|
"duration_unknown": "Duration unknown",
|
||||||
|
"phash_matches": "{count} PHashes match"
|
||||||
},
|
},
|
||||||
"verb_match_fp": "Match Fingerprints",
|
"verb_match_fp": "Match Fingerprints",
|
||||||
"verb_matched": "Matched",
|
"verb_matched": "Matched",
|
||||||
|
|||||||
@@ -7326,6 +7326,11 @@ gzip-size@5.1.1:
|
|||||||
duplexer "^0.1.1"
|
duplexer "^0.1.1"
|
||||||
pify "^4.0.1"
|
pify "^4.0.1"
|
||||||
|
|
||||||
|
hamming-distance@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/hamming-distance/-/hamming-distance-1.0.0.tgz#39bfa46c61f39e87421e4035a1be4f725dd7b931"
|
||||||
|
integrity sha1-Ob+kbGHznodCHkA1ob5Pcl3XuTE=
|
||||||
|
|
||||||
handle-thing@^2.0.0:
|
handle-thing@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e"
|
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e"
|
||||||
|
|||||||
Reference in New Issue
Block a user