| Summary | Adds a “Roles” tab to the scene page for tagging each performer’s role in the scene (e.g. Security Officer, Dominant, Top). | |
| Repository | https://codeberg.org/HeMan/role-tagger | |
| Source URL | https://heman.codeberg.page/role-tagger/index.yml | |
| Install | How to install a plugin? |
role-tagger
A Stash plugin that tags each performer’s role within a scene (Top, Teacher, Security Guard, or anything you define), with multi-select tagging, performer-card badges, per-performer rollups on the performer page, and automatic scraping from IAFD and GEVI.
Features
Scene page: “Roles” tab
Adds a Roles tab to the scene page, next to Details/Markers/Edit/etc. For each performer on the scene, pick one or more roles from a multi-select:
- Creatable: type a role that doesn’t exist yet and pick “Create …” to add it on the fly. It’s saved to the plugin settings immediately, so it’s available on every future scene without editing settings by hand.
- Multiple roles per performer per scene: a performer can be tagged e.g. both
Security GuardandAntagonistin the same scene. - Two scrape buttons in the toolbar (Scrape IAFD roles, Scrape GEVI roles). See Scraping below. Only shown for sources enabled in settings.
Performer cards
Wherever a performer’s card shows up on a scene page, it displays their role(s) for that specific scene as small pill badges underneath their name/age.
Performer page
On a performer’s Scenes tab, above the normal scene grid, there’s a role rollup: their scenes grouped into sections by role (Security Guard (12), Student (8), Unassigned (3), etc.), each rendered with Stash’s native scene cards (thumbnails, hover preview, everything). A toolbar above it has bulk scrape buttons that scrape every one of that performer’s scenes with a linked IAFD/GEVI URL in one go (see Scraping).
A multi-select Filter by role… dropdown below the toolbar narrows the rollup down to just the selected role(s) - handy once a performer has a long list of them. It’s populated from whatever roles that performer actually has (including Unassigned), and clearing the selection shows everything again.
“Always” a role
For performers who exclusively play one role across their whole scene history, the same toolbar has an Always… dropdown: pick a role and hit Apply to tag every one of that performer’s scenes that doesn’t already have a role with it. Scenes that already have a (possibly different) role are left untouched - existing role tags always win.
Checking Apply to future scenes too also saves the role on the performer itself, so any scene this performer gets newly linked to afterwards is auto-tagged with it too (as long as that scene doesn’t already have a role for them).
The performer’s bio details panel (below the usual details) also gets two summary lines:
- Role split: percentage breakdown of main roles, e.g.
50% Dominant, 50% Submissive - Act split: percentage breakdown of sub-splits, e.g.
60% Uses Leather, 40% Uses Restraints
(See Main vs. sub roles for what that distinction means.)
Experimental: a tab instead of a rollup
The [Experimental] Real "Roles" tab on performer page setting works around this by directly manipulating the DOM to insert a tab next to “Appears With” that shows the same rollup content. It looks and behaves like a native tab, but it’s not an officially supported mechanism. This could break in future updates. If that happens, turn the setting off; the rollup still works fine above the Scenes tab grid either way.
Main vs. sub roles
Roles are split into two settings:
- Main role options (
roles): broad splits, e.g.Security Officer, Dominant, Top. This is what you’d typically pick per performer per scene, and you get to define these to your preferences. - Sub role options (
subRoles): finer-grained, act-specific tags you may not want rolled up into the main splits, e.g.Oral Receive, Cunnilingus Give. Scraping discovers these on its own, but you can define your own hierarchy too.
This split is also why the role multi-select’s dropdown shows two grouped sections (“Main” / “Sub”), and why the performer bio shows two separate percentage lines instead of mixing both into one confusing list.
Scraping
IAFD
Scrape IAFD roles reads the scene’s linked iafd.com/title.rme/... URL, parses the cast list, and matches each name against the scene’s performers. IAFD lists a performer’s role directly. NSFW example here
GEVI
Scrape GEVI roles reads the scene’s linked gayeroticvideoindex.com/episode/... URL. GEVI encodes each performer’s activity as a short code instead of a plain word, decoded against a legend shown on the same page (“Action notes: A=Anal; b=bottom; t=top; …”):
- Codes are one or more
<Act letter><role letter(s)>pairs concatenated, e.g.At= Anal + top,OrAtRg= Oral+receive, Anal+top, Rim+give. - A role letter can repeat within one act, e.g.
Atb= Anal+top and Anal+bottom for the same performer, that gets collapsed intoVersatile. give/receiveonly make sense paired with an act (giving during Oral vs. Rim are different things), so those get tagged with the act name attached, e.g.Oral Receive,Rim Give.- If a performer has both
<Act> Giveand<Act> Receivefor the same act, those collapse into<Act> Versatile(e.g.Oral Give+Oral Receive→Oral Versatile). - The legend is read fresh from each scene page rather than hardcoded, since it only lists the codes actually used on that page and GEVI has more act types than just the examples above.
Bulk scraping
Each source also has a Bulk scrape … for performer task, run from the button in the performer page’s rollup toolbar. It scrapes every scene of that performer with a linked URL for that source, one request at a time with a short delay between each (to avoid tripping the source’s bot protection), and shows a confirmation dialog first since it overwrites existing role tags on every scene it touches.
Scanning your whole library
Each source also has a Scan library for … roles task. Unlike every other task here, it doesn’t need a specific scene or performer, so it’s meant to be run directly from Stash’s own Settings → Tasks page — it scrapes every scene in your entire library with a linked URL for that source, and reports live progress in the tasks bar at the top of the page while it runs. Same caveats as bulk scraping (one request per scene with a short delay, and it overwrites existing role tags), just at library scale — expect it to take a while on a large library.
Tagging Solo scenes
The Tag Solo scenes’ performers task (Settings → Tasks) finds every scene tagged Solo, Solo Male, or Solo Female (whichever of those exist in your tag list) and tags any performer on one who doesn’t already have a role there as Solo - no scraping involved, just reads your existing Stash tags. Scenes/performers with an existing role are left untouched.
Enabling/disabling a source
The Enable IAFD (on by default) / Enable GEVI (off by default) settings control whether that source’s scrape buttons show up at all. Turning one on/off also allows/blocks its scrape tasks from running server-side, in case they’re triggered directly from Stash’s own Tasks page rather than this plugin’s buttons.
Installation
This plugin only works on Stash v0.31 and above. custom_fields on scenes is required for this to work.
One-click, via a plugin source (recommended)
- In Stash, go to Settings → Plugins → Sources and add
https://heman.codeberg.page/role-tagger/index.ymlas a source. Role Taggernow shows up under Available Plugins - click Install. Future updates show up as an Update button here too, no manual re-copying needed.
Manual
- Copy the
role-taggerfolder into your Stashpluginsdirectory (Settings → Plugins shows the configured path). - In Stash, go to Settings → Plugins and click Reload plugins.
role-taggershould now appear in the plugin list, with its settings underneath.
Maintainers: publishing a new version
After bumping version in role-tagger.yml and pushing to main, run ./scripts/publish-pages.sh locally. It builds role-tagger.zip and index.yml and pushes them to the pages branch, which Codeberg Pages serves at https://heman.codeberg.page/role-tagger/. This is a manual step, not CI - Codeberg’s hosted Actions runners are currently capacity-limited.
Requirements
The scraping tasks need a few Python packages (cloudscraper, lxml, beautifulsoup4, stashapp-tools). These are installed automatically into whatever Python your Stash server uses the first time a scrape task runs, so the very first scrape may take a little longer than usual. No manual setup is required.
How role data is stored
Role assignments live in the scene’s custom_fields.roles value, as a JSON-encoded map of performer ID → array of role strings (e.g. {"123": ["Dominant"], "456": ["Submissive", "Restrained"]}). This is plain scene metadata, and it doesn’t create or remove any actual performer-to-scene relationship, so removing this plugin doesn’t lose anything about your library beyond the role tags themselves (which stay in the database as an orphaned custom field, in case you reinstall later).
Example
Here’s what tagging actually looks like end to end, using Big Buck Bunny as a stand-in scene. “Roles” are whatever tags you define, so this example uses them to mark which animal each performer plays.
Say you’ve imported big_buck_bunny_1080p.mp4 as a scene, with two performers attached: Big Buck Bunny and Frank.
- Open the scene, go to the Roles tab.
- For Big Buck Bunny, type
Bunnyinto the multi-select and pick Create “Bunny”. It’s not in your role list yet, so this adds it to the Main role options setting on the spot. - For Frank, do the same with
Squirrel.
That’s stored on the scene’s custom_fields.roles as:
{
"1": ["Bunny"],
"2": ["Squirrel"]
}
(keyed by each performer’s Stash Performer ID), and roles in settings now includes Bunny, Squirrel alongside whatever was there before.
Back on the scene page, Big Buck Bunny’s performer card now shows a small BUNNY badge, and Frank’s shows SQUIRREL.
Head to Big Buck Bunny’s own performer page, and the Scenes tab shows a rollup with one group — Bunny (1) — containing the Big Buck Bunny scene card. His bio panel shows Role split: 100% Bunny, since it’s his only tagged scene so far.
Adding sub-tags: protagonist / antagonist
Protagonist/Antagonist aren’t things scraping would ever discover on their own, so instead of typing them into the Roles tab (which would file them as main splits, same as Bunny/Squirrel), add them directly to the Sub role options setting: Protagonist, Antagonist. This is exactly the manual curation subRoles is there for — you decide the hierarchy, scraping just fills in what it can auto-classify (like GEVI’s Oral Receive/Rim Give).
Back on the scene’s Roles tab, the multi-select’s dropdown now has a Sub group alongside Main. Tag Big Buck Bunny with Protagonist and Frank with Antagonist, on top of their existing main tags:
{
"1": ["Bunny", "Protagonist"],
"2": ["Squirrel", "Antagonist"]
}
Big Buck Bunny’s performer card now shows two badges, BUNNY and PROTAGONIST. His performer page rollup gains a second group, Protagonist (1), alongside Bunny (1) (the same scene card shows up in both, since it’s grouped per role, not per performer). His bio panel now shows both lines: Role split: 100% Bunny and Act split: 100% Protagonist.
What this looks like
The scene’s Roles tab, mid-selection — the multi-select’s dropdown grouped into Main (Bunny, Squirrel) and Sub (Protagonist, Antagonist) sections:
Back on the scene’s Details tab, each performer card shows both of their tags as badges:
Big Buck Bunny’s own performer page: the Role split:/Act split: lines in his bio, above the normal scene grid:
With the experimental DOM-injected tab enabled instead, the rollup shows up as a real Roles tab next to Appears With, grouped the same way (Bunny (1), Protagonist (1)):
Known limitations
- Not all IAFD/GEVI scenes have roles: You’ll be notified if this is the case though.
- Name matching is exact. Performers are matched by exact (case-insensitive) name between the scraped cast list and the scene’s existing performers. If a name differs (aliases, disambiguation suffixes GEVI/IAFD didn’t strip, etc.), that performer is reported as unmatched rather than guessed at.
- The experimental performer tab is a DOM-injection hack. See above. It’s off by default for a reason.
- Bulk scraping is intentionally slow. One request per scene with a short delay between each, to avoid getting rate-limited or blocked by IAFD/GEVI.
- All 6 scrape tasks show up in Settings → Plugins / Tasks with a generic “Run” button. Only the two Scan library for … roles tasks are meant to be run from there. The other 4 (single-scene and bulk-per-performer scrapes) need a scene or performer only this plugin’s own buttons provide - running those from Settings will just fail with a “missing argument” error rather than do anything wrong.




