Added additional fields

Added fields resolution, width, and galleries.
Fixed bug with studio.
Modified GUI option to simplify.
Added postfix styles option to advance renamefile_settings.py file.
Added logic to avoid running rename logic twice for the same file.
Implemented code to limit max logging file size.
This commit is contained in:
David Maisonave
2024-07-26 06:28:22 -04:00
parent 0e50b59957
commit 2f20c5507c
6 changed files with 117 additions and 34 deletions

View File

@@ -1,18 +1,19 @@
# 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.
# By David Maisonave (aka Axter) 2024
# Get the latest developers version from following link: https://github.com/David-Maisonave/Axter-Stash/tree/main/plugins/RenameFile
# Based on source code from https://github.com/Serechops/Serechops-Stash/tree/main/plugins/Renamer
import requests
import os
import logging
import stashapi.log as log # Importing stashapi.log as log for critical events ONLY
import shutil
from pathlib import Path
import hashlib
import json
import sys
from stashapi.stashapp import StashInterface
# 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.
# Importing stashapi.log as log for critical events
import stashapi.log as log
from logging.handlers import RotatingFileHandler
# Import settings from renamefile_settings.py
from renamefile_settings import config
@@ -21,14 +22,24 @@ script_dir = Path(__file__).resolve().parent
# Configure logging for your script
log_file_path = script_dir / 'renamefile.log'
rfh = RotatingFileHandler(
filename=log_file_path,
mode='a',
maxBytes=2*1024*1024,
backupCount=2,
encoding=None,
delay=0
)
FORMAT = "[%(asctime)s - LN:%(lineno)s] %(message)s"
logging.basicConfig(filename=log_file_path, level=logging.INFO, format=FORMAT)
logging.basicConfig(level=logging.INFO, format=FORMAT, datefmt="%y%m%d %H:%M:%S", handlers=[rfh])
logger = logging.getLogger('renamefile')
DEFAULT_ENDPOINT = "http://localhost:9999/graphql" # Default GraphQL endpoint
DEFAULT_FIELD_KEY_LIST = "title,performers,studio,tags" # Default Field Key List with the desired order
DEFAULT_SEPERATOR = "-"
PLUGIN_ARGS = False
errOccurred = False
inputToUpdateScenePost = False
exitMsg = "Change success!!"
# ------------------------------------------
@@ -65,7 +76,11 @@ try:
PLUGIN_ARGS = json_input['args']["mode"]
except:
pass
logger.info(f"\nStarting (debugTracing={debugTracing}) (dry_run={dry_run}) (PLUGIN_ARGS={PLUGIN_ARGS})************************************************")
try:
if json_input['args']['hookContext']['input']: inputToUpdateScenePost = True # This avoid calling rename logic twice
except:
pass
logger.info(f"\nStarting (debugTracing={debugTracing}) (dry_run={dry_run}) (PLUGIN_ARGS={PLUGIN_ARGS}) (inputToUpdateScenePost={inputToUpdateScenePost})************************************************")
if debugTracing: logger.info("settings: %s " % (settings,))
if dry_run:
logger.info("Dry run mode is enabled.")
@@ -102,7 +117,14 @@ separator = settings["zseparators"]
# ------------------------------------------
double_separator = separator + separator
# Extract styles from config
wrapper_styles = config["wrapper_styles"]
postfix_styles = config["postfix_styles"]
try:
if debugTracing: logger.info(f"Debug Tracing (json_input['args']={json_input['args']})................")
except:
pass
# GraphQL query to fetch all scenes
query_all_scenes = """
@@ -142,7 +164,7 @@ def should_exclude_path(scene_details):
return False
# Function to form the new filename based on scene details and user settings
def form_filename(original_file_stem, scene_details, wrapper_styles):
def form_filename(original_file_stem, scene_details):
if debugTracing: logger.info("Debug Tracing................")
filename_parts = []
tag_keys_added = 0
@@ -165,7 +187,6 @@ def form_filename(original_file_stem, scene_details, wrapper_styles):
def add_tag(tag_name):
nonlocal tag_keys_added
nonlocal filename_parts
nonlocal wrapper_styles
if debugTracing: logger.info(f"Debug Tracing (tag_name={tag_name})................")
if max_tag_keys == -1 or (max_tag_keys is not None and tag_keys_added >= int(max_tag_keys)):
return # Skip adding more tags if the maximum limit is reached
@@ -194,6 +215,7 @@ def form_filename(original_file_stem, scene_details, wrapper_styles):
studio_name = scene_details.get('studio', {}).get('name', '')
if debugTracing: logger.info(f"Debug Tracing (studio_name={studio_name})................")
if studio_name:
studio_name += postfix_styles.get('studio')
if debugTracing: logger.info("Debug Tracing................")
if include_keyField_if_in_name or studio_name.lower() not in title.lower():
if wrapper_styles.get('studio'):
@@ -202,6 +224,7 @@ def form_filename(original_file_stem, scene_details, wrapper_styles):
filename_parts.append(studio_name)
elif key == 'title':
if title: # This value has already been fetch in start of function because it needs to be defined before tags and performers
title += postfix_styles.get('title')
if wrapper_styles.get('title'):
filename_parts.append(f"{wrapper_styles['title'][0]}{title}{wrapper_styles['title'][1]}")
else:
@@ -210,6 +233,7 @@ def form_filename(original_file_stem, scene_details, wrapper_styles):
if settings["performerAppend"]:
performers = '-'.join([performer.get('name', '') for performer in scene_details.get('performers', [])])
if performers:
performers += postfix_styles.get('performers')
if debugTracing: logger.info(f"Debug Tracing (include_keyField_if_in_name={include_keyField_if_in_name})................")
if include_keyField_if_in_name or performers.lower() not in title.lower():
if debugTracing: logger.info(f"Debug Tracing (performers={performers})................")
@@ -221,15 +245,33 @@ def form_filename(original_file_stem, scene_details, wrapper_styles):
scene_date = scene_details.get('date', '')
if debugTracing: logger.info("Debug Tracing................")
if scene_date:
scene_date += postfix_styles.get('date')
if debugTracing: logger.info("Debug Tracing................")
if wrapper_styles.get('date'):
filename_parts.append(f"{wrapper_styles['date'][0]}{scene_date}{wrapper_styles['date'][1]}")
else:
filename_parts.append(scene_date)
elif key == 'resolution':
width = str(scene_details.get('files', [{}])[0].get('width', '')) # Convert width to string
height = str(scene_details.get('files', [{}])[0].get('height', '')) # Convert height to string
if width and height:
resolution = width + postfix_styles.get('width_height_seperator') + height + postfix_styles.get('resolution')
if wrapper_styles.get('resolution'):
filename_parts.append(f"{wrapper_styles['resolution'][0]}{resolution}{wrapper_styles['width'][1]}")
else:
filename_parts.append(resolution)
elif key == 'width':
width = str(scene_details.get('files', [{}])[0].get('width', '')) # Convert width to string
if width:
width += postfix_styles.get('width')
if wrapper_styles.get('width'):
filename_parts.append(f"{wrapper_styles['width'][0]}{width}{wrapper_styles['width'][1]}")
else:
filename_parts.append(width)
elif key == 'height':
height = str(scene_details.get('files', [{}])[0].get('height', '')) # Convert height to string
if height:
height += 'p'
height += postfix_styles.get('height')
if wrapper_styles.get('height'):
filename_parts.append(f"{wrapper_styles['height'][0]}{height}{wrapper_styles['height'][1]}")
else:
@@ -237,6 +279,7 @@ def form_filename(original_file_stem, scene_details, wrapper_styles):
elif key == 'video_codec':
video_codec = scene_details.get('files', [{}])[0].get('video_codec', '').upper() # Convert to uppercase
if video_codec:
video_codec += postfix_styles.get('video_codec')
if wrapper_styles.get('video_codec'):
filename_parts.append(f"{wrapper_styles['video_codec'][0]}{video_codec}{wrapper_styles['video_codec'][1]}")
else:
@@ -244,6 +287,7 @@ def form_filename(original_file_stem, scene_details, wrapper_styles):
elif key == 'frame_rate':
frame_rate = str(scene_details.get('files', [{}])[0].get('frame_rate', '')) + 'FPS' # Convert to string and append ' FPS'
if frame_rate:
frame_rate += postfix_styles.get('frame_rate')
if wrapper_styles.get('frame_rate'):
filename_parts.append(f"{wrapper_styles['frame_rate'][0]}{frame_rate}{wrapper_styles['frame_rate'][1]}")
else:
@@ -254,6 +298,7 @@ def form_filename(original_file_stem, scene_details, wrapper_styles):
for gallery_name in galleries:
if debugTracing: logger.info(f"Debug Tracing (include_keyField_if_in_name={include_keyField_if_in_name}) (gallery_name={gallery_name})................")
if include_keyField_if_in_name or gallery_name.lower() not in title.lower():
gallery_name += postfix_styles.get('galleries')
if wrapper_styles.get('galleries'):
filename_parts.append(f"{wrapper_styles['galleries'][0]}{gallery_name}{wrapper_styles['galleries'][1]}")
if debugTracing: logger.info("Debug Tracing................")
@@ -269,11 +314,11 @@ def form_filename(original_file_stem, scene_details, wrapper_styles):
for tag_name in tags:
if debugTracing: logger.info(f"Debug Tracing (include_keyField_if_in_name={include_keyField_if_in_name}) (tag_name={tag_name})................")
if include_keyField_if_in_name or tag_name.lower() not in title.lower():
add_tag(tag_name)
add_tag(tag_name + postfix_styles.get('tag'))
if debugTracing: logger.info(f"Debug Tracing (tag_name={tag_name})................")
if debugTracing: logger.info("Debug Tracing................")
if debugTracing: logger.info("Debug Tracing................")
if debugTracing: logger.info(f"Debug Tracing (filename_parts={filename_parts})................")
new_filename = separator.join(filename_parts).replace(double_separator, separator)
if debugTracing: logger.info(f"Debug Tracing (new_filename={new_filename})................")
@@ -293,6 +338,7 @@ def find_scene_by_id(scene_id):
date
files {
path
width
height
video_codec
frame_rate
@@ -316,6 +362,7 @@ def find_scene_by_id(scene_id):
return scene_result.get('data', {}).get('findScene')
def move_or_rename_files(scene_details, new_filename, original_parent_directory):
global exitMsg
studio_directory = None
for file_info in scene_details['files']:
path = file_info['path']
@@ -354,10 +401,12 @@ def move_or_rename_files(scene_details, new_filename, original_parent_directory)
except FileNotFoundError:
log.error(f"File not found: {path}. Skipping...")
logger.error(f"File not found: {path}. Skipping...")
exitMsg = "File not found"
continue
except OSError as e:
log.error(f"Failed to move or rename file: {path}. Error: {e}")
logger.error(f"Failed to move or rename file: {path}. Error: {e}")
exitMsg = "Failed to move or rename file"
continue
return new_path # Return the new_path variable after the loop
@@ -374,7 +423,8 @@ def perform_metadata_scan(metadata_scan_path):
logger.info(f"Mutation string: {mutation_metadata_scan}")
graphql_request(mutation_metadata_scan)
def rename_scene(scene_id, wrapper_styles, stash_directory):
def rename_scene(scene_id, stash_directory):
global exitMsg
scene_details = find_scene_by_id(scene_id)
if debugTracing: logger.info(f"Debug Tracing (scene_details={scene_details})................")
if not scene_details:
@@ -401,7 +451,7 @@ def rename_scene(scene_id, wrapper_styles, stash_directory):
original_file_stem = Path(original_file_path).stem
original_file_name = Path(original_file_path).name
new_filename = form_filename(original_file_stem, scene_details, wrapper_styles)
new_filename = form_filename(original_file_stem, scene_details)
newFilenameWithExt = new_filename + Path(original_file_path).suffix
if debugTracing: logger.info(f"Debug Tracing (original_file_name={original_file_name})(newFilenameWithExt={newFilenameWithExt})................")
if original_file_name == newFilenameWithExt:
@@ -429,6 +479,7 @@ def rename_scene(scene_id, wrapper_styles, stash_directory):
os.rename(original_file_path, new_file_path)
logger.info(f"{dry_run_prefix}Renamed file: {original_file_path} -> {new_file_path}")
except Exception as e:
exitMsg = "Failed to rename file"
log.error(f"Failed to rename file: {original_file_path}. Error: {e}")
logger.error(f"Failed to rename file: {original_file_path}. Error: {e}")
@@ -442,11 +493,13 @@ def rename_scene(scene_id, wrapper_styles, stash_directory):
truncated_filename = new_filename[:max_base_filename_length]
hash_suffix = hashlib.md5(new_filename.encode()).hexdigest()
new_filename = truncated_filename + '_' + hash_suffix + Path(original_file_path).suffix
if debugTracing: logger.info(f"Debug Tracing (exitMsg={exitMsg})................")
return new_filename, original_path_info, new_path_info
# Main default function for rename scene
def rename_files_task():
global exitMsg
if debugTracing: logger.info("Debug Tracing................")
# Execute the GraphQL query to fetch all scenes
scene_result = graphql_request(query_all_scenes)
@@ -466,10 +519,6 @@ def rename_files_task():
# Extract the ID of the latest scene
latest_scene_id = latest_scene.get('id')
# Extract wrapper styles
wrapper_styles = config["wrapper_styles"]
# Read stash directory from renamefile_settings.py
stash_directory = config.get('stash_directory', '')
if debugTracing: logger.info("Debug Tracing................")
@@ -477,8 +526,8 @@ def rename_files_task():
if debugTracing: logger.info("Debug Tracing................")
# Rename the latest scene and trigger metadata scan
new_filename = rename_scene(latest_scene_id, wrapper_styles, stash_directory)
if debugTracing: logger.info("Debug Tracing................")
new_filename = rename_scene(latest_scene_id, stash_directory)
if debugTracing: logger.info(f"Debug Tracing (exitMsg={exitMsg})................")
# Log dry run state and indicate if no changes were made
if dry_run:
@@ -487,7 +536,7 @@ def rename_files_task():
elif not new_filename:
logger.info("No changes were made.")
else:
logger.info("Change success!")
logger.info(f"{exitMsg}")
return
def fetch_dup_filename_tags(): # Place holder for new implementation
@@ -497,7 +546,7 @@ if PLUGIN_ARGS == "fetch_dup_filename_tags":
fetch_dup_filename_tags()
elif PLUGIN_ARGS == "rename_files_task":
rename_files_task()
else:
elif inputToUpdateScenePost:
rename_files_task()
if debugTracing: logger.info("\n*********************************\nEXITING ***********************\n*********************************")