Skip to content

Commit

Permalink
add performer custom fields plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
7dJx1qP committed Feb 12, 2024
1 parent 4b75d43 commit a7aedb5
Show file tree
Hide file tree
Showing 7 changed files with 578 additions and 0 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,19 @@ Adds a button to the performers page to check for duplicate performer urls. Task

![Performers page](images/Stash%20Performer%20Audit%20Task%20Button/performers-page.png?raw=true "Performers page")

### Stash Performer Custom Fields

Adds custom fields to performers that are stored in performer details as JSON.

**[VIEW DOCUMENTATION](plugins/stashPerformerCustomFields/README.md)**

**Note: Make sure you fully understand how this plugin will affect your performer details data before attempting to use it.**

#### Requirements

* Python 3.9+
* PyStashLib (https://pypi.org/project/pystashlib/)

### Stash Performer Image Cropper

Adds ability to crop performer image from performer page
Expand Down
110 changes: 110 additions & 0 deletions plugins/stashPerformerCustomFields/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Stash Performer Custom Fields

Adds custom fields to performers that are stored in performer details as JSON.

**Note: Make sure you fully understand how this plugin will affect your performer details data before attempting to use it.**

## Overview

This plugin provides the following:

* A task for migrating existing performer detail data to JSON format
* A performer creation hook that adds custom fields to performer details data
* UI updates to performer pages for viewing and editing performer details and custom fields

## Consequences

Since this plugin combines existing performer details data and custom field data in JSON format for storage as performer details in the stash database, this has some consequences and limitations to consider:

* Custom fields cannot be filtered on directly

* Filtering on performer details will include custom fields data.

* Direct editing of performer details is discouraged
* However, an alternative is provided by the updated UI.

* The raw performer details data is less human-readable due to it being JSON instead of just unformatted text.
* This shouldn't matter if you just use the updated UI for editing details and custom fields data.

## Performer Details Migration Task

The first thing the task does is run the backup task. Then it goes through all performers and converts details data to JSON with custom fields. Existing details data is preserved within the JSON.

A performer without any existing details will end up with `{"custom": {"notes": null, "paths": null, "urls": null}}`

A performer with existing details of `"This is a performer bio"` will have their details changed to `{"custom": {"notes": null, "paths": null, "urls": null}, "details": "This is a performer bio"}`.

No existing details data is lost, because it is still embedded within the JSON.

*Examples assume the default fields setting `notes,paths,urls`*

## Performer Creation Hook

Whenever a new performer is created, the plugin hook with the default field setting `notes,paths,urls` will set the performer details to `{"custom": {"notes": null, "paths": null, "urls": null}}`.

## Performer Page UI Changes

The plugin displays custom fields just as the normal performer fields are displayed. Data in the `urls` and `paths` custom fields will display as links. Clicking a url link will open the link in a new tab and clicking a path will open File Explorer at that location.

A toggle button is added to the Details section that allows you to switch to the editing mode. The editing mode allows adding and remove custom field data entries.

The editing mode also provides a textbox for editing the normal Details section. This is a substitute for editing performer details the normal way.

Manually editing performer details data the normal way is no longer recommended unless you are familiar with JSON and understand the data format used by the plugin.

## Plugin Settings

### Fields

The fields setting defines the custom fields that will be used. The value should be a comma-separated list of custom field names. The default value is `notes,paths,urls`. Only the `paths` and `urls` custom field names have special UI behavior.

Field names should not contain spaces. You can use underscores instead and they will be replaced with spaces in the field name labels on the performers page.

### Show Edit

This setting is tied to the toggle button that is added to the performer page details that switches between view and edit mode.

## JSON Format

Custom field data along with performer details data is saved to the existing performer details field as JSON.

The performer details JSON is an object with a `custom` key and an optional `details` key. The `custom` value is an object and the `details` value is a string.

The field names in the field setting are used as keys in the `custom` object. The key values are either null or an array of strings.

*Examples assume the default fields setting `notes,paths,urls`*

Without performer details and no urls, paths, or notes:
```
{
"custom": {
"notes": null,
"paths": null,
"urls": null
}
}
```

With performer details and no urls, paths, or notes:
```
{
"custom": {
"notes": null,
"paths": null,
"urls": null
},
"details": "Performer details go here"
}
```

With performer details, urls, paths, and notes:
```
{
"custom": {
"notes": ["Note 1","Note 2"],
"paths": ["C:\Videos\Alice","C:\Videos\Alice 2"],
"urls": ["https://github.com/stashapp/stash","https://github.com/7dJx1qP/stash-plugins"]
},
"details": "Performer details go here"
}
```
82 changes: 82 additions & 0 deletions plugins/stashPerformerCustomFields/performer_details.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import json
import math
import yaml
from tqdm import tqdm
from stashlib.logger import logger as log
from stashlib.stash_database import StashDatabase
from stashlib.stash_models import PerformersRow

def update_performer_details(db: StashDatabase, performer_id: int, details, commit=True):
encoded_details = json.dumps(details, ensure_ascii=False)
db.performers.update_details_by_id(performer_id, encoded_details, commit)

def fields_to_dict(fields: list[str]):
result = {}
for field in fields:
result[field] = None
return result

def init_performer_details(db: StashDatabase, fields: list[str], performer: PerformersRow, commit=True):
log.LogDebug(f"Initializing performer... {performer.id} {performer.name}")
if not performer.details:
log.LogTrace(f"No details. Updating performer...")
details = {
'custom': fields_to_dict(fields)
}
encoded_details = json.dumps(details, ensure_ascii=False)
db.performers.update_details_by_id(performer.id, encoded_details, commit)
else:
log.LogTrace(f"Checking for performer details JSON...")
details = {
'custom': fields_to_dict(fields)
}
needs_update = False
try:
docs = json.loads(performer.details)
if type(docs) is dict:
details = docs
log.LogTrace(f"JSON is dict, details: {'details' in details}, custom: {'custom' in details}")
if 'details' in details and type(details['details']) is not str:
log.LogWarning(f"Malformed details key value. Expected str, got {type(details['details']).__name__}. Skipping... {performer.id} {performer.name}")
return
if 'custom' not in details:
details['custom'] = fields_to_dict(fields)
needs_update = True
log.LogTrace(f"Added missing custom dict")
elif type(details['custom']) is not dict:
log.LogWarning(f"Malformed custom key value. Expected dict, got {type(details['custom']).__name__}. Skipping... {performer.id} {performer.name}")
return
else:
log.LogTrace(f"Has custom dict")
for field in fields:
if field not in details['custom']:
details['custom'][field] = None
needs_update = True
log.LogTrace(f"Added missing {field} field to custom dict")
else:
log.LogWarning(f"JSON detected but expected dict, got {type(docs).__name__}... {performer.id} {performer.name}")
details['details'] = performer.details
needs_update = True
except:
log.LogTrace(f"Invalid JSON")
details['details'] = performer.details
needs_update = True

if needs_update:
log.LogTrace(f"Updating performer details... {performer.id} {performer.name}")
encoded_details = json.dumps(details, ensure_ascii=False)
db.performers.update_details_by_id(performer.id, encoded_details, commit)
else:
log.LogTrace(f"No update needed... {performer.id} {performer.name}")

def init_performer_details_by_id(db: StashDatabase, fields: list[str], performer_id, commit=True):
performer = db.performers.selectone_id(performer_id)
if performer:
init_performer_details(db, fields, performer, commit)

def init_all_performer_details(db: StashDatabase, fields: list[str]):
performers = [PerformersRow().from_sqliterow(row) for row in db.performers.select()]
log.LogInfo(f"Migrating {len(performers)} performer details...")
for performer in performers:
init_performer_details(db, fields, performer, commit=False)
db.commit()
1 change: 1 addition & 0 deletions plugins/stashPerformerCustomFields/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pystashlib==0.4.2
Loading

0 comments on commit a7aedb5

Please sign in to comment.