') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); fix(RenameFile): fix doubled file extension causing 'File name too long' error by garfolino · Pull Request #768 · stashapp/CommunityScripts · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions plugins/RenameFile/renamefile.css
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
.renamefile {
color: unset;
cursor: pointer;
&:hover {
text-decoration: unset;
text-decoration: underline;
}
&:active {
color: white;
Expand Down
54 changes: 35 additions & 19 deletions plugins/RenameFile/renamefile.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -72,30 +72,46 @@
}
}

function wrapElement(element) {
var text = element.textContent.trim();
var anchor = document.createElement('a');
anchor.href = '#';
anchor.textContent = text;
anchor.classList.add('renamefile');
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.';
anchor.addEventListener('click', function(event) {
event.preventDefault();
AppendTitleField(text, event);
});
element.innerHTML = '';
element.appendChild(anchor);
var TITLE_SELECTOR = '.scene-header div.TruncatedText';
var TOOLTIP = '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.';

// Use event delegation instead of replacing the title element. The scene
// title is a React-controlled component; restructuring its DOM (e.g.
// wrapping it in an <a>) detaches the text node React updates, so the title
// freezes on the previous scene when navigating with Next. By listening on
// document and reading the title at click time, we never touch React's DOM.
document.addEventListener('click', function(event) {
var title = event.target.closest(TITLE_SELECTOR);
if (!title) return;
event.preventDefault();
AppendTitleField(title.textContent.trim(), event);
});

// Non-destructive affordance: add the styling class and tooltip without
// touching the element's children, so React's text node stays intact.
function decorate(element) {
if (!element.classList.contains('renamefile')) {
element.classList.add('renamefile');
}
if (element.getAttribute('title') !== TOOLTIP) {
element.setAttribute('title', TOOLTIP);
}
}

function handleMutations(mutationsList, observer) {
for(const mutation of mutationsList) {
for(const addedNode of mutation.addedNodes) {
if (addedNode.nodeType === Node.ELEMENT_NODE && addedNode.querySelector('.scene-header div.TruncatedText')) {
wrapElement(addedNode.querySelector('.scene-header div.TruncatedText'));
}
function handleMutations(mutationsList) {
for (const mutation of mutationsList) {
for (const addedNode of mutation.addedNodes) {
if (addedNode.nodeType !== Node.ELEMENT_NODE) continue;
var title = addedNode.matches && addedNode.matches(TITLE_SELECTOR)
? addedNode
: addedNode.querySelector && addedNode.querySelector(TITLE_SELECTOR);
if (title) decorate(title);
}
}
}
const observer = new MutationObserver(handleMutations);
observer.observe(document.body, { childList: true, subtree: true });

var existing = document.querySelector(TITLE_SELECTOR);
if (existing) decorate(existing);
})();
16 changes: 8 additions & 8 deletions plugins/RenameFile/renamefile.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -388,12 +388,12 @@ def rename_scene(scene_id):
original_file_name = Path(original_file_path).name
new_filename = form_filename(original_file_stem, scene_details)
max_filename_length = int(config["max_filename_length"])
if len(new_filename) > max_filename_length:
extension_length = len(Path(original_file_path).suffix)
max_base_filename_length = max_filename_length - extension_length
truncated_filename = new_filename[:max_base_filename_length]
extension_length = len(Path(original_file_path).suffix)
if len(new_filename) + extension_length > max_filename_length:
hash_suffix = hashlib.md5(new_filename.encode()).hexdigest()
new_filename = truncated_filename + '_' + hash_suffix + Path(original_file_path).suffix
max_base_filename_length = max_filename_length - extension_length - len(hash_suffix) - 1
truncated_filename = new_filename[:max_base_filename_length]
new_filename = truncated_filename + '_' + hash_suffix
newFilenameWithExt = new_filename + Path(original_file_path).suffix
new_file_path = f"{original_parent_directory}{os.sep}{new_filename}{Path(original_file_name).suffix}"
org_file_root_stem = f"{original_parent_directory}{os.sep}{original_file_stem}"
Expand DownExpand Up@@ -444,15 +444,15 @@ def rename_scene(scene_id):
stash.Trace(f"Calling [metadata_scan] for path {original_parent_directory.resolve().as_posix()}")
stash.metadata_scan(paths=[original_parent_directory.resolve().as_posix()])
if targetDidExist:
raise
return None
if os.path.isfile(new_file_path):
if os.path.isfile(original_file_path):
os.remove(original_file_path)
pass
else:
# ToDo: Add delay rename here
raise
return None

if dry_run:
stash.Log("Dry-Run, so skipping DB renaming")
elif stash.renameFileNameInDB(scene_details['files'][0]['id'], original_file_name, newFilenameWithExt):
Expand Down
2 changes: 1 addition & 1 deletion plugins/RenameFile/renamefile.yml
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
name: RenameFile
description: Renames video (scene) file names when the user edits the [Title] field located in the scene [Edit] tab.
version: 1.0.0
version: 1.0.2
url: https://discourse.stashapp.cc/t/renamefile/1334
ui:
css:
Expand Down
3 changes: 3 additions & 0 deletions plugins/RenameFile/version_history/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,3 +16,6 @@
- Fixed Dry-Run bug, which changed the file name in the database when Dry-Run was enabled.
### 1.0.1
-
### 1.0.2
- Fixed OSError "File name too long" (macOS) caused by the long-filename truncation logic appending the file extension twice and not reserving space for the hash suffix.
- Rename/move failures now log a single clear error message instead of also dumping a raw Python traceback to the log.
Loading