Skip to content

Commit 69cc412

Browse files
Changes for version 1.0.0
### 0.5.8 - Added setting max_performers to renamefile_settings.py, which allows limitting the quantity of performers added to file name. ### 0.5.9 - Added rename associated file feature. When video file is renamed, associated files will get renamed. - Associated files are determind by file extensions listed in variable **associated_files_to_rename** which is in **renamefile_settings.py** file. - Option **rename_associated_files_enable** can be used to disable this feature. It's turned on by default. ### 1.0.0 - Fixed Dry-Run bug, which changed the file name in the database when Dry-Run was enabled.
1 parent 9bca8a8 commit 69cc412

File tree

6 files changed

+159
-111
lines changed

6 files changed

+159
-111
lines changed

plugins/RenameFile/README.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
# RenameFile: Ver 0.5.7 (By David Maisonave)
2-
3-
https://discourse.stashapp.cc/t/renamefile/1334
4-
1+
# RenameFile: Ver 1.0.0 (By David Maisonave)
52
RenameFile is a [Stash](https://github.com/stashapp/stash) plugin. Starting version 0.5.5, user can add the current title to the title input field by clicking on the current title. Also, the Stash database gets updated directly instead of running a scan task as long as the database is version 68.
63

74
- The plugin allows user to rename one scene at a time by editing the **[Title]** field and then clicking **[Save]**.

plugins/RenameFile/renamefile.js

Lines changed: 94 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,101 @@
1-
(function () {
2-
"use strict";
3-
function AppendTitleField(text, event) {
4-
if ((!event.altKey && !event.ctrlKey) || event.shiftKey) {
5-
try {
6-
var titleInput = document.getElementById("title");
7-
if (event.shiftKey) titleInput.value = text;
8-
else titleInput.value += text;
9-
console.log("Appended title to title field: " + text);
10-
} catch (err) {
11-
console.error("Failed to append text to title field:", err);
12-
}
1+
(function() {
2+
'use strict';
3+
function AppendTitleField(text, event) {
4+
if ((!event.altKey && !event.ctrlKey) || event.shiftKey) {
5+
try {
6+
var titleInput = document.getElementById('title');
7+
if (event.shiftKey)
8+
titleInput.value = text;
9+
else
10+
titleInput.value += text;
11+
console.log('Appended title to title field: ' + text);
12+
} catch (err) {
13+
console.error('Failed to append text to title field:', err);
14+
}
15+
}
16+
17+
var textArea = null;
18+
var fileckUrl = null;
19+
if (event.altKey) {
20+
try {
21+
const navElements = document.querySelectorAll("a");
22+
for (const element of navElements)
23+
{
24+
var ckUrl = decodeURI(element.href);
25+
if (ckUrl.startsWith("file://")){
26+
fileckUrl = ckUrl;
27+
ckUrl = ckUrl.replace("file:///", "");
28+
ckUrl = ckUrl.replace("file://", "");
29+
console.debug("Copping " + ckUrl + " to clipboard");
30+
textArea = document.createElement("textarea");
31+
textArea.value = ckUrl;
32+
document.body.appendChild(textArea);
33+
textArea.select();
34+
break;
35+
}
36+
}
37+
} catch (err) {
38+
console.error('Failed to get file URL:', err);
39+
}
40+
}
41+
else if (event.ctrlKey) {
42+
try {
43+
console.debug("Copping " + text + " to clipboard");
44+
textArea = document.createElement("textarea");
45+
textArea.value = text;
46+
document.body.appendChild(textArea);
47+
textArea.select();
48+
} catch (err) {
49+
console.error('Failed to get title:', err);
50+
}
51+
}
52+
53+
if (textArea != null)
54+
{
55+
try {
56+
var successful = document.execCommand('copy');
57+
var msg = successful ? 'successful' : 'unsuccessful';
58+
console.log('Text copy ' + msg + ':', textArea.value);
59+
} catch (err) {
60+
console.error('Failed to copy text to clipboard:', err);
61+
}
62+
document.body.removeChild(textArea);
63+
try {
64+
if (fileckUrl != null && event.ctrlKey){
65+
window.open(fileckUrl, "_blank");
66+
console.log('Opening link ' + fileckUrl);
67+
}
68+
} catch (err) {
69+
console.error("Failed to open link '" + fileckUrl + "' with error:", err);
70+
}
71+
72+
}
1373
}
1474

15-
var textArea = null;
16-
var fileckUrl = null;
17-
if (event.altKey) {
18-
try {
19-
const navElements = document.querySelectorAll("a");
20-
for (const element of navElements) {
21-
var ckUrl = decodeURI(element.href);
22-
if (ckUrl.startsWith("file://")) {
23-
fileckUrl = ckUrl;
24-
ckUrl = ckUrl.replace("file:///", "");
25-
ckUrl = ckUrl.replace("file://", "");
26-
console.debug("Copping " + ckUrl + " to clipboard");
27-
textArea = document.createElement("textarea");
28-
textArea.value = ckUrl;
29-
document.body.appendChild(textArea);
30-
textArea.select();
31-
break;
32-
}
33-
}
34-
} catch (err) {
35-
console.error("Failed to get file URL:", err);
36-
}
37-
} else if (event.ctrlKey) {
38-
try {
39-
console.debug("Copping " + text + " to clipboard");
40-
textArea = document.createElement("textarea");
41-
textArea.value = text;
42-
document.body.appendChild(textArea);
43-
textArea.select();
44-
} catch (err) {
45-
console.error("Failed to get title:", err);
46-
}
75+
function wrapElement(element) {
76+
var text = element.textContent.trim();
77+
var anchor = document.createElement('a');
78+
anchor.href = '#';
79+
anchor.textContent = text;
80+
anchor.classList.add('renamefile');
81+
anchor.title = 'Click to append title to [Title] input field; OR ctrl-key & mouse click to copy title to clipboard; OR shift-key click to copy to [Title] input field; OR alt-key click to copy file URI to clipboard.';
82+
anchor.addEventListener('click', function(event) {
83+
event.preventDefault();
84+
AppendTitleField(text, event);
85+
});
86+
element.innerHTML = '';
87+
element.appendChild(anchor);
4788
}
4889

49-
if (textArea != null) {
50-
try {
51-
var successful = document.execCommand("copy");
52-
var msg = successful ? "successful" : "unsuccessful";
53-
console.log("Text copy " + msg + ":", textArea.value);
54-
} catch (err) {
55-
console.error("Failed to copy text to clipboard:", err);
56-
}
57-
document.body.removeChild(textArea);
58-
try {
59-
if (fileckUrl != null && event.ctrlKey) {
60-
window.open(fileckUrl, "_blank");
61-
console.log("Opening link " + fileckUrl);
62-
}
63-
} catch (err) {
64-
console.error(
65-
"Failed to open link '" + fileckUrl + "' with error:",
66-
err
67-
);
68-
}
69-
}
70-
}
71-
72-
function wrapElement(element) {
73-
var text = element.textContent.trim();
74-
var anchor = document.createElement("a");
75-
anchor.href = "#";
76-
anchor.textContent = text;
77-
anchor.classList.add("renamefile");
78-
anchor.title =
79-
"Click to append title to [Title] input field; OR ctrl-key & mouse click to copy title to clipboard; OR shift-key click to copy to [Title] input field; OR alt-key click to copy file URI to clipboard.";
80-
anchor.addEventListener("click", function (event) {
81-
event.preventDefault();
82-
AppendTitleField(text, event);
83-
});
84-
element.innerHTML = "";
85-
element.appendChild(anchor);
86-
}
87-
88-
function handleMutations(mutationsList, observer) {
89-
for (const mutation of mutationsList) {
90-
for (const addedNode of mutation.addedNodes) {
91-
if (
92-
addedNode.nodeType === Node.ELEMENT_NODE &&
93-
addedNode.querySelector(".scene-header div.TruncatedText")
94-
) {
95-
wrapElement(
96-
addedNode.querySelector(".scene-header div.TruncatedText")
97-
);
90+
function handleMutations(mutationsList, observer) {
91+
for(const mutation of mutationsList) {
92+
for(const addedNode of mutation.addedNodes) {
93+
if (addedNode.nodeType === Node.ELEMENT_NODE && addedNode.querySelector('.scene-header div.TruncatedText')) {
94+
wrapElement(addedNode.querySelector('.scene-header div.TruncatedText'));
95+
}
96+
}
9897
}
99-
}
10098
}
101-
}
102-
const observer = new MutationObserver(handleMutations);
103-
observer.observe(document.body, { childList: true, subtree: true });
99+
const observer = new MutationObserver(handleMutations);
100+
observer.observe(document.body, { childList: true, subtree: true });
104101
})();

plugins/RenameFile/renamefile.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Description: This is a Stash plugin which allows users to rename the video (scene) file name by editing the [Title] field located in the scene [Edit] tab.
2-
# By David Maisonave (aka Axter) Jul-2024 (https://www.axter.com/)
2+
# By David Maisonave (aka Axter) Jul-2025 (https://www.axter.com/)
33
# Get the latest developers version from following link: https://github.com/David-Maisonave/Axter-Stash/tree/main/plugins/RenameFile
44
# Based on source code from https://github.com/Serechops/Serechops-Stash/tree/main/plugins/Renamer
55

@@ -139,13 +139,20 @@ def should_exclude_path(scene_details):
139139
return True
140140
return False
141141

142-
include_keyField_if_in_name = stash.pluginSettings["z_keyFIeldsIncludeInFileName"]
143-
excludeIgnoreAutoTags = config["excludeIgnoreAutoTags"]
142+
include_keyField_if_in_name = stash.pluginSettings["z_keyFIeldsIncludeInFileName"]
143+
excludeIgnoreAutoTags = config["excludeIgnoreAutoTags"]
144+
max_performers = int(config["max_performers"])
145+
rename_associated_files_enable = config["rename_associated_files_enable"]
146+
associated_files_to_rename = config["associated_files_to_rename"]
144147

145148
def getPerformers(scene, title):
146149
title = title.lower()
147150
results = ""
151+
qtyPerformers = 0
148152
for performer in scene['performers']:
153+
qtyPerformers += 1
154+
if max_performers > -1 and qtyPerformers > max_performers:
155+
break
149156
name = performer['name']
150157
stash.Trace(f"performer = {name}")
151158
if not include_keyField_if_in_name:
@@ -389,6 +396,8 @@ def rename_scene(scene_id):
389396
new_filename = truncated_filename + '_' + hash_suffix + Path(original_file_path).suffix
390397
newFilenameWithExt = new_filename + Path(original_file_path).suffix
391398
new_file_path = f"{original_parent_directory}{os.sep}{new_filename}{Path(original_file_name).suffix}"
399+
org_file_root_stem = f"{original_parent_directory}{os.sep}{original_file_stem}"
400+
new_file_root_stem = f"{original_parent_directory}{os.sep}{new_filename}"
392401
stash.Trace(f"(original_file_name={original_file_name}) (newFilenameWithExt={newFilenameWithExt})(new_file_path={new_file_path}) (FileID={scene_details['files'][0]['id']})")
393402
if original_file_name == newFilenameWithExt or original_file_name == new_filename:
394403
stash.Log(f"Nothing to do, because new file name matches original file name: (newFilenameWithExt={newFilenameWithExt})")
@@ -401,12 +410,32 @@ def rename_scene(scene_id):
401410
stash.Warn(f"Had to close '{original_file_path}', because it was opened by following pids:{results['pids']}")
402411
if move_files:
403412
if not dry_run:
413+
stash.Trace(f"Moving file '{original_file_path}' to '{new_file_path}'")
404414
shutil.move(original_file_path, new_file_path)
415+
if rename_associated_files_enable:
416+
stash.Trace(f"rename_associated_files_enable is enabled")
417+
for ext in associated_files_to_rename:
418+
associted_filename = org_file_root_stem + ext
419+
# stash.Trace(f"Checking if file exist: '{associted_filename}'")
420+
if os.path.isfile(associted_filename):
421+
new_associted_filename = new_file_root_stem + ext
422+
stash.Log(f"Renaming file '{associted_filename}' to '{new_associted_filename}'")
423+
shutil.move(associted_filename, new_associted_filename)
405424
exitMsg = f"{dry_run_prefix}Moved file to '{new_file_path}' from '{original_file_path}'"
406425
else:
407426
stash.Trace(f"Rename('{original_file_path}', '{new_file_path}')")
408427
if not dry_run:
428+
stash.Trace(f"Renaming file '{original_file_path}' to '{new_file_path}'")
409429
os.rename(original_file_path, new_file_path)
430+
if rename_associated_files_enable:
431+
stash.Trace(f"rename_associated_files_enable is enabled...")
432+
for ext in associated_files_to_rename:
433+
associted_filename = org_file_root_stem + ext
434+
# stash.Trace(f"Checking if file exist: '{associted_filename}'")
435+
if os.path.isfile(associted_filename):
436+
new_associted_filename = new_file_root_stem + ext
437+
stash.Log(f"Renaming file '{associted_filename}' to '{new_associted_filename}'")
438+
os.rename(associted_filename, new_associted_filename)
410439
exitMsg = f"{dry_run_prefix}Renamed file to '{new_file_path}' from '{original_file_path}'"
411440
except OSError as e:
412441
exitMsg = f"Failed to move/rename file: From {original_file_path} to {new_file_path}; targetDidExist={targetDidExist}. Error: {e}"
@@ -424,7 +453,9 @@ def rename_scene(scene_id):
424453
# ToDo: Add delay rename here
425454
raise
426455

427-
if stash.renameFileNameInDB(scene_details['files'][0]['id'], original_file_name, newFilenameWithExt):
456+
if dry_run:
457+
stash.Log("Dry-Run, so skipping DB renaming")
458+
elif stash.renameFileNameInDB(scene_details['files'][0]['id'], original_file_name, newFilenameWithExt):
428459
stash.Trace("DB rename success")
429460
elif not taskqueue.tooManyScanOnTaskQueue(maxScanCountForUpdate):
430461
stash.Trace(f"Calling [metadata_scan] for path {original_parent_directory.resolve().as_posix()}")

plugins/RenameFile/renamefile.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: RenameFile
22
description: Renames video (scene) file names when the user edits the [Title] field located in the scene [Edit] tab.
3-
version: 0.5.7
3+
version: 1.0.0
44
url: https://github.com/David-Maisonave/Axter-Stash/tree/main/plugins/RenameFile
55
ui:
66
css:

plugins/RenameFile/renamefile_settings.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# By David Maisonave (aka Axter) 2024
1+
# By David Maisonave (aka Axter) 2025
22
# RenameFile plugin main configuration options are available on the Stash GUI under Settings->Plugins->Plugins->[RenameFile].
33
# Most users should only use the GUI options.
44
# The configuration options in this file are for advanced users ONLY!!!
@@ -57,6 +57,23 @@
5757
"tagAppendEnable": True,
5858
# Enable to move file instead of rename file. (Not recommended for Windows OS)
5959
"fileRenameViaMove": False,
60+
# Maximum allowed performers to be added to file name. If value -1=no limit.
61+
"max_performers": -1,
62+
# When value set to true, will enable feature for associated_files_to_rename.
63+
"rename_associated_files_enable": True, # See associated_files_to_rename option.
64+
# Add or remove file extensions which are to get renamed when renaming a video file. For example if having a.mp4 and a.funscript, if a.mp4 gets renamed to foofoo.mp4, then a.funscript will get renamed to foofoo.funscript.
65+
"associated_files_to_rename": [".funscript", ".srt", ".vtt", ".scc", ".ttml", ".dfxp", ".lrc", ".cap", ".sami", ".stl", ".mcc", ".info", ".txt", ".xml"],
66+
# funscript = A file format that is the standard format for scripting interactive devices by syncing them up to a video
67+
# SRT = SubRip Text -> A widely used, simple text file format that displays caption text with timecodes
68+
# VTT = WebVTT -> A text-based format that is part of the HTML5 standard
69+
# SCC = Scenarist Closed Caption -> A format used by broadcast and digital media producers for TV and movies.
70+
# TTML = Timed Text Markup Language
71+
# DFXP = Distribution Format Exchange Profile
72+
# LRC = A computer file format that synchronizes song lyrics with an audio file, such as MP3, AAC, or MIDI.
73+
# CAP = Primarily used for Japanese subtitles
74+
# SAMI = Synchronized Accessible Media Interchange -> A Microsoft accessibility initiative released in the summer of 1998.
75+
# STL = Spruce Subtitle File -> Text file for Apple's DVD Studio Pro
76+
# MCC = MacCaption
6077

6178
# handleExe is for Windows only.
6279
# In Windows, a file can't be renamed if the file is opened by another process.
Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
##### This page was added starting on version 0.5.6 to keep track of newly added features between versions.
2-
32
### 0.5.6
4-
53
- Fixed bug with studio getting the studio ID instead of the name of the studio in rename process.
64
- Improved performance by having code get all required scene details in one call to stash.
75
- To remove UI clutter, move rarely used options (performerAppendEnable, studioAppendEnable, tagAppendEnable, & fileRenameViaMove) to renamefile_settings.py
86
- Change options (performerAppendEnable, studioAppendEnable, tagAppendEnable) to default to True (enabled)
9-
107
### 0.5.7
11-
128
- Uploaded missing renamefile.js and renamefile.css files to CommunityScripts
9+
### 0.5.8
10+
- Added setting max_performers to renamefile_settings.py, which allows limitting the quantity of performers added to file name.
11+
### 0.5.9
12+
- Added rename associated file feature. When video file is renamed, associated files will get renamed.
13+
- Associated files are determind by file extensions listed in variable **associated_files_to_rename** which is in **renamefile_settings.py** file.
14+
- Option **rename_associated_files_enable** can be used to disable this feature. It's turned on by default.
15+
### 1.0.0
16+
- Fixed Dry-Run bug, which changed the file name in the database when Dry-Run was enabled.
17+
### 1.0.1
18+
-

0 commit comments

Comments
 (0)