renamerOnUpdate

PR = Handle UNIQUE constraint on folders.parent_folder_id, folders.basename by kroeberd · Pull Request #21 · f4bio/stash-plugins · GitHub

Fix and cleanup

1 Like

a gentle reminder that plugins are YMMV and at your own risk - please make frequent backups

1 Like

The only thing that’s changed is the way stash works. You can now pull. I’ve tested it with about 500 manual entries.

I’ve also run your code through several AI models, and they’ve confirmed it too

umm doesn’t someone need to review, approve, merge, push what you’ve done to be of any use for this plugin though? that PR is just sitting there at the moment

Yes, the maintainer must confirm this.

Error [Plugin / renamerOnUpdate] error when trying to update the database (NOT NULL constraint failed: folders.basename)

I’m guessing this has to do with changes in v0.31.0

Bump to 2.5.2, Fixed. Pls Update

nice work in becoming a maintainer @kroeberd

i went to add the repo at https://f4bio.github.io/stash-plugins/main/index.yml but i don’t see renamerOnUpdate - only the develop version and Organizer On Update - is this wrong for your new update?

2.5.2 worked like a charm. Thank you!

This is also working for me. Thank you for the update.

Looking to see if anyone has successfully used the “exclude” function. Here is my code to have it ignore a specific folder.

######################################
#            Exclude Settings        #

# Enable/disable exclude functionality
exclude_enabled = True

# Exclude scenes based on tag patterns
# Format: {"tag_name": {"type": "exact|regex", "pattern": "pattern_to_match"}}
# Examples:
# exclude_tag_patterns = {
#     "!1. Western": {"type": "exact", "pattern": "!1. Western"},
#     "temp": {"type": "regex", "pattern": r"temp.*"},
# }
exclude_tag_patterns = {}

# Exclude scenes based on studio patterns (includes parent studios)
# Format: {"studio_name": {"type": "exact|regex", "pattern": "pattern_to_match"}}
# Examples:
# exclude_studio_patterns = {
#     "Test Studio": {"type": "exact", "pattern": "Test Studio"},
#     "temp": {"type": "regex", "pattern": r"temp.*"},
# }
exclude_studio_patterns = {}

# Exclude scenes based on file path patterns
# Format: {"path_identifier": {"type": "exact|regex", "pattern": "pattern_to_match"}}
# Examples:
# exclude_path_patterns = {
#     "temp_folder": {"type": "regex", "pattern": r".*[/\\]temp[/\\].*"},
#     "specific_path": {"type": "exact", "pattern": r"E:\Movies\ToSort"},
# }
exclude_path_patterns = {
"specific_path": {"type": "exact", "pattern": r"/data/media/porn/Movies"}}

Last line is my code.

I cannot get the exclude path pattern or regex to work, either. I have whisparr shoving stuff into an /organized/scenes/ directory structure. I’d like this plugin to not rename those files if they’re updated since they’re already marked as organized, but despite trying regex or exact patterns, they seem to get renamed anyway…. This confuses whisparr and it loses track of the file because renameronupdate matches filenames closely but cannot do the exact same filename that whisparr does if the whisparr name includes quality info ie “webdl” etc….. My config is attached below.


tag_templates = {
    #"movie": "$date $performer - $title [$studio]",
    # "!1. JAV": "$title",
    # "!1. Anime": "$title $date [$studio]"
    "movie": "$title ($year) {$movie_scene -} [$resolution$height]",
    "Movies": "$title ($year) {$movie_scene -} [$resolution$height]"
}

# Adjust the below if you want to use studio names instead of tags for the renaming templates
studio_templates = {}

# Change to True to use the default template if no specific tag/studio is found
use_default_template = True
# Default template, adjust as needed
#default_template = "$date $performer $title {$movie_scene -} $height"
default_template = "$date $performer $title $resolution$height"
#default_template = " $title - $resolution$height"


####################################################################
#           TEMPLATE PATH  (Move your files)

# $studio_hierarchy: create the whole hierarchy folder (MindGeek/Brazzers/Hot And Mean/video.mp4)
# ^* = parent of folder (E:\Movies\video.mp4 -> E:\Movies\)

# trigger with a specific tag
# "tagname": "path"
# ex: "plugin_move": r"E:\Movies\R18\$studio_hierarchy"
p_tag_templates = {
    "movie": "/data/organized/movies/$parent_studio/$studio/$title ($year)",
    "Movies": "/data/organized/movies/$parent_studio/$studio/$title ($year)"
}


p_studio_templates = {}

# match a path
# "match path": "destination"
# ex: r"E:\Film\R18\2. Test\A trier": r"E:\Film\R18\2. Test\A trier\$performer",
p_path_templates = {}

# change to True to use the default template if no specific tag/studio is found
p_use_default_template = True
# default template, adjust as needed
#p_default_template = r"/data/organized/scenes/$studio_hierarchy/"
p_default_template = r"/data/organized/scenes/$parent_studio/$studio/$title"
#p_default_template = r"/data/organized/$parent_studio/$studio/"

# if unorganized, ignore other templates, use this path
p_non_organized = r""

# option if tag is present
# "tagname": [option]
# clean_tag: remove the tag after the rename
# inverse_performer: change the last/first name (Jane Doe -> Doe Jane)
# dry_run: activate dry_run for this scene
# ex: "plugin_move": ["clean_tag"]
p_tag_option = {}
######################################
#               Logging              #

# File to save what is renamed, can be useful if you need to revert changes.
# Will look like: IDSCENE|OLD_PATH|NEW_PATH
# Leave Blank ("") or use None if you don't want to use a log file, or a working path like: C:\Users\USERNAME\.stash\plugins\Hooks\rename_log.txt
log_file = r"/root/.stash/plugins/renamerOnUpdate/ROU.log"

######################################
#               Settings             #

# rename associated file (subtitle, funscript) if present
associated_extension = ["srt", "vtt", "funscript"]

# use filename as title if no title is set
# it will cause problem if you update multiple time the same scene without title.
filename_as_title = False

# Character which replaces every space in the filename
# Common values are "." and "_"
# e. g.:
# "."
# 2016-12-29.Eva.Lovia.-.Her.Fantasy.Ball
filename_splitchar = " "

# replace space for stash field (title, performer...), if you have a title 'I love Stash' it can become 'I_love_Stash'
field_whitespaceSeperator = ""
# Remove/Replace character from field (not using regex)
# "field": {"replace": "foo","with": "bar"}
# ex: "$studio": {"replace": "'","with": ""} My Dad's Hot Girlfriend --> My Dads Hot Girlfriend
field_replacer = {
#"$title": {"replace": ": ", "with": " - "}
#"$studio": {"replace": "'", "with": ""}
}

# Match and replace.
# "match": ["replace with", "system"] the second element of the list determine the system used. If you don't put this element, the default is word
# regex: match a regex, word: match a word, any: match a term
# difference between 'word' & 'any': word is between seperator (space, _, -), any is anything ('ring' would replace 'during')
# ex:   "Scene": ["Sc.", "word"]    - Replace Scene by Sc.
#       r"S\d+:E\d+": ["", "regex"] - Remove Sxx:Ex (x is a digit)
replace_words = {
#    r"[a-zA-Z'\-]+" : ["","regex"]
}

# Date format for $date_format field, check: https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes
date_format = r"%Y-%m-%d"
# Duration format, check table: https://docs.python.org/3/library/time.html#time.strftime
# exemple: %H;%M;%S -> 00;35;20 (You can't have ':' character in filename)
# If empty, it will give you the duration as seconds
duration_format = r""

# put the filename in lowercase
lowercase_Filename = False
# filename in title case (Capitalises each word and lowercases the rest)
titlecase_Filename = False
# remove these characters if there are present in the filename
removecharac_Filename = "#"

# Character to use as a performer separator.
performer_splitchar = " "
# Maximum number of performer names in the filename. If there are more than that in a scene the filename will not include any performer name!
performer_limit = 3
# The filename with have the name of performer before reaching the limit (if limit=3, the filename can contains 3 performers for a 4 performers scenes)
performer_limit_keep = False
# sorting performer (name, id, rating, favorite, mix (favorite > rating > name), mixid (..>..> id))
performer_sort = "id"
# ignore certain gender. Available "MALE" "FEMALE" "TRANSGENDER_MALE" "TRANSGENDER_FEMALE" "INTERSEX" "NON_BINARY" "UNDEFINED"
performer_ignoreGender = []

# word attached at end if multiple file for same scene [FileRefactor]
duplicate_suffix = ["", "_1", "_2", "_3", "_4", "_5", "_6", "_7", "_8", "_9", "_10"]

# If $performer is before $title, prevent having duplicate text.
# e.g.:
# Template used: $year $performer - $title
# 2016 Dani Daniels - Dani Daniels in ***.mp4 --> 2016 Dani Daniels in ***.mp4
prevent_title_performer = False

## Path mover related
# remove consecutive (/FolderName/FolderName/video.mp4 -> FolderName/video.mp4
prevent_consecutive = True
# check when the file has moved that the old directory is empty, if empty it will remove it.
remove_emptyfolder = True
# the folder only contains 1 performer name. Else it will look the same as for filename
path_one_performer = True
# if there is no performer on the scene, the $performer field will be replaced by "NoPerformer" so a folder "NoPerformer" will be created
path_noperformer_folder = False
# if the folder already have a performer name, it won't change it
path_keep_alrperf = True

# Removes prepositions from the beginning of titles
prepositions_list = ["The", "A", "An"]
prepositions_removal = False

# Squeeze studio names removes all spaces in studio, parent studio and studio family name
# e. g.:
# Reality Kings --> RealityKings
# Team Skeet Extras --> TeamSkeetExtras
squeeze_studio_names = False

# Rating indicator option to identify the number correctly in your OS file search
# Separated from the template handling above to avoid having only "RTG" in the filename for scenes without ratings
# e. g.:
# "{}" with scene rating of 5       == 5
# "RTG{}" with scene rating of 5    == RTG5
# "{}-stars" with scene rating 3    == 3-stars
rating_format = "{}"

# Character to use as a tag separator.
tags_splitchar = " "
# Include and exclude tags
# 	Tags will be compared strictly. "pantyhose" != "Pantyhose" and "panty hose" != "pantyhose"
# Option 1: If you're using whitelist, every other tag which is not listed there will be ignored in the filename
# Option 2: All tags in the tags_blacklist array will be ignored in the filename. Every other tag will be used.
# Option 3: Leave both arrays empty if you're looking for every tag which is linked to the scene.
# 			Attention: Only recommended if the scene linked tags number is not that big due to maxiumum filename length
tags_whitelist = [
    # "Brunette", "Blowjob"
]

tags_blacklist = [
    # ignored tags...
]

# Only rename 'Organized' scenes.
only_organized = True

# If the new path is over 240 characters, the plugin will try to reduce it. Set to True to ignore that.
ignore_path_length = False

# Field to remove if the path is too long. First in list will be removed then second then ... if length is still too long.
order_field = [
    "$video_codec",
    "$audio_codec",
    "$resolution",
    "tags",
    "rating",
    "$height",
    "$studio_family",
    "$studio",
    "$parent_studio",
    "$performer",
]

# Alternate way to show diff. Not useful at all.
alt_diff_display = False

# number of scene process by the task renamer. -1 = all scenes
batch_number_scene = -1

# disable/enable the hook. You can edit this value in 'Plugin Tasks' inside of Stash.
enable_hook = True
# disable/enable dry mode. Do a trial run with no permanent changes. Can write into a file (dryrun_renamerOnUpdate.txt), set a path for log_file.
# You can edit this value in 'Plugin Tasks' inside of Stash.
dry_run = False
# Choose if you want to append to (True) or overwrite (False) the dry-run log file.
dry_run_append = True
######################################
#            Module Related          #

# ! OPTIONAL module settings. Not needed for basic operation !

# = psutil module (https://pypi.org/project/psutil/) =
# Gets a list of all processes instead of stopping after the first one. Enabling it slows down the plugin
process_getall = False
# If the file is used by a process, the plugin will kill it. IT CAN MAKE STASH CRASH TOO.
process_kill_attach = False
# =========================

# = Unidecode module (https://pypi.org/project/Unidecode/) =
# Check site mentioned for more details.
# TL;DR: Prevent having non common characters by replacing them.
# Warning: If you have non-latin characters (Cyrillic, Kanji, Arabic, ...), the result will be extremely different.
use_ascii = False
# =========================

exclude_enabled = True

# Exclude scenes based on tag patterns
# Format: {"tag_name": {"type": "exact|regex", "pattern": "pattern_to_match"}}
# Examples:
# exclude_tag_patterns = {
#     "!1. Western": {"type": "exact", "pattern": "!1. Western"},
#     "temp": {"type": "regex", "pattern": r"temp.*"},
# }
exclude_tag_patterns = {}

# Exclude scenes based on studio patterns (includes parent studios)
# Format: {"studio_name": {"type": "exact|regex", "pattern": "pattern_to_match"}}
# Examples:
# exclude_studio_patterns = {
#     "Test Studio": {"type": "exact", "pattern": "Test Studio"},
#     "temp": {"type": "regex", "pattern": r"temp.*"},
# }
exclude_studio_patterns = {}

# Exclude scenes based on file path patterns
# Format: {"path_identifier": {"type": "exact|regex", "pattern": "pattern_to_match"}}
# Examples:
# exclude_path_patterns = {
#     "temp_folder": {"type": "regex", "pattern": r".*[/\\]temp[/\\].*"},
#     "specific_path": {"type": "exact", "pattern": r"E:\Movies\ToSort"},
# }

exclude_path_patterns = {
    "whisparr_scenes": {"type": "regex", "pattern": r"^/data/organized/scenes(/.*)?$"},
    "whisparr_movies": {"type": "regex", "pattern": r"^/data/organized/movies(/.*)?$"},
}

After upgrading and moving over my configurations I’m finding that it is renaming and moving my files as before but it is not updating the File Path in the database correctly and it breaks the video link unless I do a rescan of the destination. So far, it appears to occur when using $performer tag in the file path and if there are multiple performers.

Example:
Configured to rename and move to Z:\Performers\$performer\Video
File - Z:/Unmatched/video_file.mp4
After renamerOnUpdate - Z:\Performers\Jane Doe1, Jane Doe2\Video\new_video_title.mp4
File path in Stash: Z:\Performers\Video\new_video_title.mp4

While it is renaming and moving correctly, it is leaving out the $performer part of the path when updating the database. Looking at it in the Log file, it logs it with all of the correct paths.

My first thought was the 240 character limit on the new path - but that is not the case. These do not exceed 240 characters, and my ignore_path_length value is False.

I dug around on this some more. I don’t really understand well how github works but it looks like the code to exclude paths is merged in a PR but it’s not in the actual code that you download(?) - see here [Feature Request][renamerOnUpdate] Ability to exclude paths · Issue #6 · f4bio/stash-plugins · GitHub I queried the maintainer but haven’t heard back.

Fix for Date YYYY-MM and YYYY done

feat: add exclude functionality and bump version to 2.6.0 by kroeberd · Pull Request #25 · f4bio/stash-plugins · GitHub new Function :wink:

Huge Update!!!

Summary

This PR implements three feature requests and one bugfix in a single branch, all with tests.


Closes #4 — Sort performers by gender

performer_sortGender was already implemented in the Python code via getattr fallback but was completely missing from the config template, making it invisible to users.

Added performer_sortGender = [] to the config with full documentation covering all 7 Stash gender values, interaction with performer_limit, and the recommended sort order. Solves the blank $performer problem that occurs with performer_ignoreGender = ["MALE"] on male-only scenes.

python

# Females and trans women first — falls back to males if no female present
performer_sortGender = ["FEMALE", "TRANSGENDER_FEMALE", "NON_BINARY", "INTERSEX", "MALE", "TRANSGENDER_MALE", "UNDEFINED"]

Closes #6 — Exclude paths / tags / studios

Scenes can now be excluded from renaming based on tags, studios (incl. parent), or file paths using exact or regex patterns.

python

exclude_enabled = True
exclude_tag_patterns    = {"wip": {"type": "exact", "pattern": "WIP"}}
exclude_studio_patterns = {"mg":  {"type": "exact", "pattern": "MindGeek"}}
exclude_path_patterns   = {"del": {"type": "regex", "pattern": r".*/loeschen/.*"}}

Closes #11 — Skip rename when required fields are missing

Scenes with no metadata (unscraped) no longer get renamed to garbage filenames like [1080p]_1.mp4. Fields inside {} optional groups are not checked.

python

require_fields = True  # default — safe for existing setups

Also included

  • Bugfix: NOT NULL constraint failed: folders.basename — missing basename column in INSERT INTO folders

  • Bugfix: UNIQUE constraint failed: folders.parent_folder_id, folders.basename — folder already exists under different path normalization

  • Bugfix: UNIQUE constraint failed: files.parent_folder_id, files.basename — concurrent rename of resolution variants

  • Bugfix: ValueError: time data does not match format '%Y-%m-%d' — partial dates (YYYY-MM, YYYY) now parsed gracefully

  • CI: New .github/workflows/test.yml runs on push/PR to the plugin directory


Version

2.5.5 → 2.6.2


Tests

50 unit tests — 50 passed in 0.14s

  • _matches_pattern (exact/regex/case)

  • is_excluded (tags / studio / parent studio / path / disabled)

  • get_missing_required_fields (required vs optional fields)

  • sort_by_gender (all 7 genders, limit interaction, empty order)

  • Date parsing (YYYY-MM-DD / YYYY-MM / YYYY / invalid)

2 Likes

Thank you very very much for all of the work!!!

1 Like

For new features, bugs, and other issues, please always open an issue and tag me if possible. I’m happy to take on general work on the plugin, but I’m not the original developer.

If you want extra work or ideas, there are many issues on Issues · stashapp/CommunityScripts · GitHub from before the original plugin was deprecated.