Create renamer as Task Plugin (#21)

This commit is contained in:
Belleyy
2022-01-16 00:05:48 +01:00
committed by GitHub
parent 975d179d8b
commit fe0f4d7256
6 changed files with 882 additions and 0 deletions

View File

@@ -7,6 +7,7 @@ This list keeps track of scripts and plugins in this repository. Please ensure t
Category|Triggers|Plugin Name|Description|Minimum Stash version
--------|-----------|-----------|-----------|---------------------
Scraper|Task|[GHScraper_Checker](plugins/GHScraper_Checker)|Compare local file against github file from the community scraper repo.|v0.8
Maintenance|Task|[renamer](plugins/renamer)|Rename your file based on Stash metadata in bulk.|v0.7
Maintenance|Scene.Update|[renamerOnUpdate](plugins/renamerOnUpdate)|Rename your file based on Stash metadata.|v0.7
Scenes|SceneMarker.Create<br />SceneMarker.Update|[markerTagToScene](plugins/markerTagToScene)|Adds primary tag of Scene Marker to the Scene on marker create/update.|v0.8 ([46bbede](https://github.com/stashapp/stash/commit/46bbede9a07144797d6f26cf414205b390ca88f9))
Scanning|Scene.Create<br />Gallery.Create|[filenameParser](plugins/filenameParser)|Tries to parse filenames, primarily in {studio}.{year}.{month}.{day}.{performer1firstname}.{performer1lastname}.{performer2}.{title} format, into the respective fields|v0.10

74
plugins/renamer/README.md Normal file
View File

@@ -0,0 +1,74 @@
# SQLITE Renamer for Stash (Task)
Using metadata from your stash to rename your file.
## Requirement
- Stash
- Python 3+ (Tested on Python v3.9.1 64bit, Win10)
- Request Module (https://pypi.org/project/requests/)
- Windows 10 ? (No idea if this work for all OS)
## Installation
- Download the whole folder 'renamer' (config.py, log.py, renamerTask.py/.yml)
- Place it in your **plugins** folder (where the `config.yml` is)
- Reload plugins (Settings > Plugins)
- renamerTask should appear.
### :exclamation: Make sure to configure the plugin by editing `config.py` before running it :exclamation:
## Usage
- You have tasks (Settings > Task):
- **Dry-Run 🔍**: Don't edit any file, just show in log. It will create `renamer_scan.txt` that contains every edit.
- **[DRYRUN] Check 10 scenes**: Check 10 scenes (by newest updated).
- **[DRYRUN] Check all scenes**: Check all scenes.
- **Process :pencil2:**: Edit your files, **don't touch Stash while doing this task**.
- **Process scanned scene from Dry-Run task**: Read `renamer_scan.txt` instead of checking all scenes.
- **Process 10 scenes**: Check 10 scenes (by newest updated).
- **Process all scenes**: Check all scenes.
## Configuration
- Read/Edit `config.py`
- I recommend setting the **log_file** as it can be useful to revert.
- If you have the **renamerOnUpdate** plugin, you can copy the `config.py` from it.
### Example
> Note: The priority is Tag > Studio > Default
The config will be:
```py
# Change filename for scenes from 'Vixen' or 'Slayed' studio.
studio_templates = {
"Slayed": "$date $performer - $title [$studio]",
"Vixen": "$performer - $title [$studio]"
}
# Change filename if the tag 'rename_tag' is present.
tag_templates = {
"rename_tag": "$year $title - $studio $resolution $video_codec",
}
# Change filename no matter what
use_default_template = True
default_template = "$date $title"
# Use space as a performer separator
performer_splitchar = " "
# If the scene has more than 3 performers, the $performer field will be ignored.
performer_limit = 3
```
The scene was just scanned, everything is default (Title = Filename).
Current filename: `Slayed.21.09.02.Ariana.Marie.Emily.Willis.And.Eliza.Ibarra.XXX.1080p.mp4`
|Stash Field | Value | Filename | Trigger template |
|--|:---:|--|--|
| - | *Default* |`Slayed.21.09.02.Ariana.Marie.Emily.Willis.And.Eliza.Ibarra.XXX.1080p.mp4` | default_template
| ~Title| **Driver**| `Driver.mp4` | default_template
| +Date| **2021-09-02**| `2021-09-02 Driver.mp4` | default_template
| +Performer | **Ariana Marie<br>Emily Willis<br>Eliza Ibarra**| `2021-09-02 Driver.mp4` | default_template
| +Studio | **Vixen**| `Ariana Marie Emily Willis Eliza Ibarra - Driver [Vixen].mp4` | studio_templates [Vixen]
| ~Studio | **Slayed**| `2021-09-02 Ariana Marie Emily Willis Eliza Ibarra - Driver [Slayed].mp4` | studio_templates [Slayed]
| +Performer | **Elsa Jean**| `2021-09-02 Driver [Slayed].mp4` | studio_templates [Slayed]<br>**Reach performer_limit**.
| +Tag | **rename_tag**| `2021 Driver - Slayed HD h264.mp4` | tag_templates [rename_tag]

88
plugins/renamer/config.py Normal file
View File

@@ -0,0 +1,88 @@
###################################################################
#
# -----------------------------------------------------------------
# Available: $date $year $performer $title $height $resolution $studio $parent_studio $studio_family $video_codec $audio_codec
# -note:
# $studio_family: If parent studio exist use it, else use the studio name.
# $performer: If more than * performers, this field will be ignored. Limit to fix at Settings section below (default: 3)
# $resolution: SD/HD/UHD/VERTICAL (for phone) | $height: 720p 1080p 4k 8k
# -----------------------------------------------------------------
# e.g.:
# $title == Her Fantasy Ball
# $date $title == 2016-12-29 Her Fantasy Ball
# $year $title $height == 2016 Her Fantasy Ball 1080p
# $date $performer - $title [$studio] == 2016-12-29 Eva Lovia - Her Fantasy Ball [Sneaky Sex]
# $parent_studio $date $performer - $title == Reality Kings 2016-12-29 Eva Lovia - Her Fantasy Ball
#
####################################################################
# TEMPLATE #
# Priority : Tags > Studios > Default
# templates to use for given tags
# add or remove as needed
tag_templates = {
"!1. Western": "$date $performer - $title [$studio]",
"!1. JAV": "$title",
"!1. Anime": "$title $date [$studio]"
}
# 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 = False
# default template, adjust as needed
default_template = "$date $title"
######################################
# 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""
######################################
# Settings #
# 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 names!
performer_limit = 3
# ignore male performers.
performer_ignore_male = False
# 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
# Only rename 'Organized' scenes.
only_organized = 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", "$height", "$studio_family", "$studio", "$parent_studio","$performer"]
# Alternate way to show diff. Not useful at all.
alt_diff_display = False
######################################
# 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
# =========================

52
plugins/renamer/log.py Normal file
View File

@@ -0,0 +1,52 @@
import sys
# Log messages sent from a plugin instance are transmitted via stderr and are
# encoded with a prefix consisting of special character SOH, then the log
# level (one of t, d, i, w, e, or p - corresponding to trace, debug, info,
# warning, error and progress levels respectively), then special character
# STX.
#
# The LogTrace, LogDebug, LogInfo, LogWarning, and LogError methods, and their equivalent
# formatted methods are intended for use by plugin instances to transmit log
# messages. The LogProgress method is also intended for sending progress data.
#
def __prefix(level_char):
start_level_char = b'\x01'
end_level_char = b'\x02'
ret = start_level_char + level_char + end_level_char
return ret.decode()
def __log(level_char, s):
if level_char == "":
return
print(__prefix(level_char) + s + "\n", file=sys.stderr, flush=True)
def LogTrace(s):
__log(b't', s)
def LogDebug(s):
__log(b'd', s)
def LogInfo(s):
__log(b'i', s)
def LogWarning(s):
__log(b'w', s)
def LogError(s):
__log(b'e', s)
def LogProgress(p):
progress = min(max(0, p), 1)
__log(b'p', str(progress))

View File

@@ -0,0 +1,638 @@
import difflib
import json
import os
import re
import sqlite3
import subprocess
import sys
import time
import requests
try:
import psutil # pip install psutil
MODULE_PSUTIL = True
except:
MODULE_PSUTIL = False
try:
import unidecode # pip install Unidecode
MODULE_UNIDECODE = True
except:
MODULE_UNIDECODE = False
import config
import log
FRAGMENT = json.loads(sys.stdin.read())
FRAGMENT_SERVER = FRAGMENT["server_connection"]
PLUGIN_DIR = FRAGMENT_SERVER["PluginDir"]
PLUGIN_ARGS = FRAGMENT['args'].get("mode")
log.LogDebug("--Starting Plugin 'Renammer'--")
#log.LogDebug("{}".format(FRAGMENT))
def callGraphQL(query, variables=None, raise_exception=True):
# Session cookie for authentication
graphql_port = FRAGMENT_SERVER['Port']
graphql_scheme = FRAGMENT_SERVER['Scheme']
graphql_cookies = {
'session': FRAGMENT_SERVER.get('SessionCookie').get('Value')
}
graphql_headers = {
"Accept-Encoding": "gzip, deflate, br",
"Content-Type": "application/json",
"Accept": "application/json",
"Connection": "keep-alive",
"DNT": "1"
}
graphql_domain = 'localhost'
# Stash GraphQL endpoint
graphql_url = graphql_scheme + "://" + graphql_domain + ":" + str(graphql_port) + "/graphql"
json = {'query': query}
if variables is not None:
json['variables'] = variables
try:
response = requests.post(graphql_url, json=json,headers=graphql_headers, cookies=graphql_cookies, timeout=20)
except Exception as e:
exit_plugin(err="[FATAL] Exception with GraphQL request. {}".format(e))
if response.status_code == 200:
result = response.json()
if result.get("error"):
for error in result["error"]["errors"]:
if raise_exception:
raise Exception("GraphQL error: {}".format(error))
else:
log.LogError("GraphQL error: {}".format(error))
return None
if result.get("data"):
return result.get("data")
elif response.status_code == 401:
exit_plugin(err="HTTP Error 401, Unauthorised.")
else:
raise ConnectionError("GraphQL query failed: {} - {}".format(response.status_code, response.content))
def graphql_getScene(scene_id):
query = """
query FindScene($id: ID!, $checksum: String) {
findScene(id: $id, checksum: $checksum) {
...SceneData
}
}
fragment SceneData on Scene {
id
checksum
oshash
title
details
url
date
rating
o_counter
organized
path
phash
interactive
file {
size
duration
video_codec
audio_codec
width
height
framerate
bitrate
}
studio {
...SlimStudioData
}
movies {
movie {
...MovieData
}
scene_index
}
tags {
...SlimTagData
}
performers {
...PerformerData
}
}
fragment SlimStudioData on Studio {
id
name
parent_studio {
id
name
}
details
rating
aliases
}
fragment MovieData on Movie {
id
checksum
name
aliases
date
rating
director
studio {
...SlimStudioData
}
synopsis
url
}
fragment SlimTagData on Tag {
id
name
aliases
}
fragment PerformerData on Performer {
id
checksum
name
url
gender
twitter
instagram
birthdate
ethnicity
country
eye_color
height
measurements
fake_tits
career_length
tattoos
piercings
aliases
favorite
tags {
...SlimTagData
}
rating
details
death_date
hair_color
weight
}
"""
variables = {
"id": scene_id
}
result = callGraphQL(query, variables)
return result.get('findScene')
def graphql_getConfiguration():
query = """
query Configuration {
configuration {
general {
databasePath
}
}
}
"""
result = callGraphQL(query)
return result.get('configuration')
def graphql_findScene(perPage,direc="DESC"):
query = """
query FindScenes($filter: FindFilterType) {
findScenes(filter: $filter) {
count
scenes {
...SlimSceneData
}
}
}
fragment SlimSceneData on Scene {
id
checksum
oshash
title
details
url
date
rating
o_counter
organized
path
phash
interactive
scene_markers {
id
title
seconds
}
galleries {
id
path
title
}
studio {
id
name
}
movies {
movie {
id
name
}
scene_index
}
tags {
id
name
}
performers {
id
name
gender
favorite
}
}
"""
# ASC DESC
variables = {'filter': {"direction": direc, "page": 1, "per_page": perPage, "sort": "updated_at"}}
result = callGraphQL(query, variables)
return result.get("findScenes")
def makeFilename(scene_information, query):
new_filename = str(query)
for field in TEMPLATE_FIELD:
field_name = field.replace("$","")
if field in new_filename:
if scene_information.get(field_name):
if field == "$performer":
if re.search(r"\$performer[-\s_]*\$title", new_filename) and scene_information.get('title') and PREVENT_TITLE_PERF:
if re.search("^{}".format(scene_information["performer"]), scene_information["title"]):
log.LogInfo("Ignoring the performer field because it's already in start of title")
new_filename = re.sub('\$performer[-\s_]*', '', new_filename)
continue
new_filename = new_filename.replace(field, scene_information[field_name])
else:
new_filename = re.sub('\${}[-\s_]*'.format(field_name), '', new_filename)
# remove []
new_filename = re.sub('\[\W*]', '', new_filename)
# Remove multiple space/_ in row
new_filename = re.sub('[\s_]{2,}', ' ', new_filename)
# Remove multiple - in row
new_filename = re.sub('(?:[\s_]-){2,}', ' -', new_filename)
# Remove space at start/end
new_filename = new_filename.strip(" -")
return new_filename
def find_diff_text(a, b):
addi = minus = stay = ""
minus_ = addi_ = 0
for _, s in enumerate(difflib.ndiff(a, b)):
if s[0] == ' ':
stay += s[-1]
minus += "*"
addi += "*"
elif s[0] == '-':
minus += s[-1]
minus_ += 1
elif s[0] == '+':
addi += s[-1]
addi_ += 1
if minus_ > 20 or addi_ > 20:
log.LogDebug("Diff Checker: +{}; -{};".format(addi_,minus_))
log.LogDebug("OLD: {}".format(a))
log.LogDebug("NEW: {}".format(b))
else:
log.LogDebug("Original: {}\n- Charac: {}\n+ Charac: {}\n Result: {}".format(a, minus, addi, b))
return
def has_handle(fpath,all_result=False):
lst = []
for proc in psutil.process_iter():
try:
for item in proc.open_files():
if fpath == item.path:
if all_result:
lst.append(proc)
else:
return proc
except Exception:
pass
return lst
def exit_plugin(msg=None, err=None):
if msg is None and err is None:
msg = "plugin ended"
output_json = {"output": msg, "error": err}
print(json.dumps(output_json))
sys.exit()
def renamer(scene_id):
filename_template = None
STASH_SCENE = graphql_getScene(scene_id)
# ================================================================ #
# RENAMER #
# Tags > Studios > Default
# Default
if config.use_default_template:
filename_template = config.default_template
# Change by Studio
if STASH_SCENE.get("studio") and config.studio_templates:
if config.studio_templates.get(STASH_SCENE["studio"]["name"]):
filename_template = config.studio_templates[STASH_SCENE["studio"]["name"]]
# by Parent
if STASH_SCENE["studio"].get("parent_studio"):
if config.studio_templates.get(STASH_SCENE["studio"]["name"]):
filename_template = config.studio_templates[STASH_SCENE["studio"]["name"]]
# Change by Tag
if STASH_SCENE.get("tags") and config.tag_templates:
for tag in STASH_SCENE["tags"]:
if config.tag_templates.get(tag["name"]):
filename_template = config.tag_templates[tag["name"]]
break
# END #
####################################################################
if config.only_organized and not STASH_SCENE["organized"]:
return("Scene ignored (not organized)")
if not filename_template:
return("No template for this scene.")
#log.LogDebug("Using this template: {}".format(filename_template))
current_path = STASH_SCENE["path"]
# note: contain the dot (.mp4)
file_extension = os.path.splitext(current_path)[1]
# note: basename contains the extension
current_filename = os.path.basename(current_path)
current_directory = os.path.dirname(current_path)
# Grabbing things from Stash
scene_information = {}
# Grab Title (without extension if present)
if STASH_SCENE.get("title"):
# Removing extension if present in title
scene_information["title"] = re.sub("{}$".format(file_extension), "", STASH_SCENE["title"])
# Grab Date
scene_information["date"] = STASH_SCENE.get("date")
# Grab Performer
if STASH_SCENE.get("performers"):
perf_list = ""
if len(STASH_SCENE["performers"]) > PERFORMER_LIMIT:
log.LogInfo("More than {} performer(s). Ignoring $performer".format(PERFORMER_LIMIT))
else:
for perf in STASH_SCENE["performers"]:
#log.LogDebug(performer)
if PERFORMER_IGNORE_MALE:
if perf["gender"] != "MALE":
perf_list += perf["name"] + PERFORMER_SPLITCHAR
else:
perf_list += perf["name"] + PERFORMER_SPLITCHAR
# Remove last character
perf_list = perf_list[:-len(PERFORMER_SPLITCHAR)]
scene_information["performer"] = perf_list
# Grab Studio name
if STASH_SCENE.get("studio"):
scene_information["studio"] = STASH_SCENE["studio"].get("name")
scene_information["studio_family"] = scene_information["studio"]
# Grab Parent name
if STASH_SCENE["studio"].get("parent_studio"):
scene_information["parent_studio"] = STASH_SCENE["studio"]["parent_studio"]["name"]
scene_information["studio_family"] = scene_information["parent_studio"]
# Grab Height (720p,1080p,4k...)
scene_information["resolution"] = 'SD'
scene_information["height"] = "{}p".format(STASH_SCENE["file"]["height"])
if STASH_SCENE["file"]["height"] >= 720:
scene_information["resolution"] = 'HD'
if STASH_SCENE["file"]["height"] >= 2160:
scene_information["height"] = '4k'
scene_information["resolution"] = 'UHD'
if STASH_SCENE["file"]["height"] >= 4320:
scene_information["height"] = '8k'
# For Phone ?
if STASH_SCENE["file"]["height"] > STASH_SCENE["file"]["width"]:
scene_information["resolution"] = 'VERTICAL'
scene_information["video_codec"] = STASH_SCENE["file"]["video_codec"]
scene_information["audio_codec"] = STASH_SCENE["file"]["audio_codec"]
log.LogDebug("[{}] Scene information: {}".format(scene_id,scene_information))
if scene_information.get("date"):
scene_information["year"] = scene_information["date"][0:4]
# Create the new filename
new_filename = makeFilename(scene_information, filename_template) + file_extension
# Remove illegal character for Windows ('#' and ',' is not illegal you can remove it)
new_filename = re.sub('[\\/:"*?<>|#,]+', '', new_filename)
# Trying to remove non standard character
if MODULE_UNIDECODE and UNICODE_USE:
new_filename = unidecode.unidecode(new_filename, errors='preserve')
else:
# Using typewriter for Apostrophe
new_filename = re.sub("[]+", "'", new_filename)
# Replace the old filename by the new in the filepath
new_path = current_path.rstrip(current_filename) + new_filename
# Trying to prevent error with long path for Win10
# https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd
if len(new_path) > 240:
log.LogWarning("The Path is too long ({})...".format(len(new_path)))
for word in ORDER_SHORTFIELD:
if word not in filename_template:
continue
filename_template = re.sub('\{}[-\s_]*'.format(word), '', filename_template).strip()
log.LogDebug("Removing field: {}".format(word))
new_filename = makeFilename(scene_information, filename_template) + file_extension
new_path = current_path.rstrip(current_filename) + new_filename
if len(new_path) < 240:
log.LogInfo("Reduced filename to: {}".format(new_filename))
break
if len(new_path) > 240:
return("Can't manage to reduce the path, operation aborted.")
#log.LogDebug("Filename: {} -> {}".format(current_filename,new_filename))
#log.LogDebug("Path: {} -> {}".format(current_path,new_path))
if (new_path == current_path):
return("Filename already correct. ({})".format(current_filename))
if ALT_DIFF_DISPLAY:
find_diff_text(current_filename,new_filename)
else:
log.LogDebug("[OLD] Filename: {}".format(current_filename))
log.LogDebug("[NEW] Filename: {}".format(new_filename))
if DRY_RUN:
with open(FILE_DRYRUN_RESULT, 'a', encoding='utf-8') as f:
f.write("{}|{}|{}\n".format(scene_id, current_filename, new_filename))
return("[Dry-run] Writing in {}".format(FILE_DRYRUN_RESULT))
# Connect to the DB
try:
sqliteConnection = sqlite3.connect(STASH_DATABASE)
cursor = sqliteConnection.cursor()
log.LogDebug("Python successfully connected to SQLite")
except sqlite3.Error as error:
return("FATAL SQLITE Error: {}".format(error))
# Looking for duplicate filename
folder_name = os.path.basename(os.path.dirname(new_path))
cursor.execute("SELECT id FROM scenes WHERE path LIKE ? AND NOT id=?;", ["%" + folder_name + "_" + new_filename, scene_id])
dupl_check = cursor.fetchall()
if len(dupl_check) > 0:
for dupl_row in dupl_check:
log.LogError("Same path: [{}]".format(dupl_row[0]))
return("Duplicate path detected, check log!")
cursor.execute("SELECT id FROM scenes WHERE path LIKE ? AND NOT id=?;", ["%" + new_filename, scene_id])
dupl_check = cursor.fetchall()
if len(dupl_check) > 0:
for dupl_row in dupl_check:
log.LogInfo("Same filename: [{}]".format(dupl_row[0]))
# OS Rename
if (os.path.isfile(current_path) == True):
try:
os.rename(current_path, new_path)
except PermissionError as err:
if "[WinError 32]" in str(err) and MODULE_PSUTIL:
log.LogWarning("A process use this file, trying to find it (Probably FFMPEG)")
# Find what process access the file, it's ffmpeg for sure...
process_use = has_handle(current_path, PROCESS_ALLRESULT)
if process_use:
# Terminate the process then try again to rename
log.LogDebug("Process that use this file: {}".format(process_use))
if PROCESS_KILL:
p = psutil.Process(process_use.pid)
p.terminate()
p.wait(10)
# If we don't manage to close it, this will create a error again.
os.rename(current_path, new_path)
else:
return("A process prevent editing the file.")
else:
log.LogError(err)
return ""
if (os.path.isfile(new_path) == True):
log.LogInfo("[OS] File Renamed!")
if LOGFILE:
with open(LOGFILE, 'a', encoding='utf-8') as f:
f.write("{}|{}|{}\n".format(scene_id, current_path, new_path))
else:
# I don't think it's possible.
return("[OS] File failed to rename ? {}".format(new_path))
else:
return("[OS] File don't exist in your Disk/Drive ({})".format(current_path))
# Database rename
cursor.execute("UPDATE scenes SET path=? WHERE id=?;", [new_path, scene_id])
sqliteConnection.commit()
# Close DB
cursor.close()
sqliteConnection.close()
log.LogInfo("[SQLITE] Database updated and closed!")
return ""
# File that show what we will changed.
FILE_DRYRUN_RESULT = os.path.join(PLUGIN_DIR, "renamer_scan.txt")
STASH_CONFIG = graphql_getConfiguration()
STASH_DATABASE = STASH_CONFIG["general"]["databasePath"]
TEMPLATE_FIELD = "$date $year $performer $title $height $resolution $studio $parent_studio $studio_family $video_codec $audio_codec".split(" ")
# READING CONFIG
LOGFILE = config.log_file
PERFORMER_SPLITCHAR = config.performer_splitchar
PERFORMER_LIMIT = config.performer_limit
PERFORMER_IGNORE_MALE = config.performer_ignore_male
PREVENT_TITLE_PERF = config.prevent_title_performer
PROCESS_KILL = config.process_kill_attach
PROCESS_ALLRESULT = config.process_getall
UNICODE_USE = config.use_ascii
ORDER_SHORTFIELD = config.order_field
ALT_DIFF_DISPLAY = config.alt_diff_display
# Task
scenes = None
progress = 0
start_time = time.time()
if PLUGIN_ARGS in ["Process_test","Process_full","Process_dry"]:
DRY_RUN = False
else:
log.LogDebug("Dry-Run enable")
DRY_RUN = True
if PLUGIN_ARGS in ["DRYRUN_test","Process_test"]:
scenes = graphql_findScene(10, "DESC")
if PLUGIN_ARGS in ["DRYRUN_full","Process_full"]:
scenes = graphql_findScene(-1, "ASC")
if PLUGIN_ARGS == "Process_dry":
if os.path.exists(FILE_DRYRUN_RESULT):
scenes = {"scenes":[]}
with open(FILE_DRYRUN_RESULT, 'r') as f:
for line in f:
scene_id_file = line.split("|")[0]
scenes["scenes"].append(scene_id_file)
else:
exit_plugin(err="Can't find the file from the dry-run ({}). Be sure to run a Dry-Run task before.".format(FILE_DRYRUN_RESULT))
if not scenes:
exit_plugin(err="no scene")
log.LogDebug("Count scenes: {}".format(len(scenes["scenes"])))
progress_step = 1 / len(scenes["scenes"])
for scene in scenes["scenes"]:
msg = renamer(scene["id"])
if msg:
log.LogDebug(msg)
progress += progress_step
log.LogProgress(progress)
if PLUGIN_ARGS == "Process_dry":
os.remove(FILE_DRYRUN_RESULT)
if DRY_RUN:
num_lines = 0
if os.path.exists(FILE_DRYRUN_RESULT):
num_lines = sum(1 for _ in open(FILE_DRYRUN_RESULT))
if num_lines > 0:
log.LogInfo("[DRY-RUN] There wil be {} file(s) changed. Check {} for more details".format(num_lines, FILE_DRYRUN_RESULT))
else:
log.LogInfo("[DRY-RUN] No change to do.")
log.LogInfo("Took {} seconds".format(round(time.time() - start_time)))
exit_plugin("Successful!")

View File

@@ -0,0 +1,29 @@
name: renamerTask
description: Rename filename based to a template.
url: https://github.com/stashapp/CommunityScripts
version: 1.0
exec:
- python
- "{pluginDir}/renamerTask.py"
interface: raw
tasks:
- name: '[DRYRUN] Check 10 scenes'
description: Only check 10 scenes. Just show in log and create a file with the possible change.
defaultArgs:
mode: DRYRUN_test
- name: '[DRYRUN] Check all scenes'
description: Check all scenes. Just show in log and create a file with the possible change.
defaultArgs:
mode: DRYRUN_full
- name: 'Process scanned scene from Dry-Run task'
description: Edit scenes listed on the textfile from the Dry-Run task. ! Don't do anything in Stash in same time !
defaultArgs:
mode: Process_dry
- name: 'Process 10 scenes'
description: Edit the filename (if needed) for 10 scenes. ! Don't do anything in Stash in same time !
defaultArgs:
mode: Process_test
- name: 'Process all scenes'
description: Edit the filename (if needed) for all scenes. ! Don't do anything in Stash in same time !
defaultArgs:
mode: Process_full