diff --git a/comicapi/genericmetadata.py b/comicapi/genericmetadata.py
index 5045ba0..ed8baf0 100644
--- a/comicapi/genericmetadata.py
+++ b/comicapi/genericmetadata.py
@@ -332,3 +332,93 @@ class GenericMetadata:
outstr += fmt_str.format(i[0] + ":", i[1])
return outstr
+
+
+md_test = GenericMetadata()
+
+md_test.is_empty = False
+md_test.tag_origin = None
+md_test.series = "Cory Doctorow's Futuristic Tales of the Here and Now"
+md_test.issue = "1"
+md_test.title = "Anda's Game"
+md_test.publisher = "IDW Publishing"
+md_test.month = 10
+md_test.year = 2007
+md_test.day = 1
+md_test.issue_count = 6
+md_test.volume = 1
+md_test.genre = "Sci-Fi"
+md_test.language = "en"
+md_test.comments = (
+ "For 12-year-old Anda, getting paid real money to kill the characters of players who were cheating in her favorite online "
+ "computer game was a win-win situation. Until she found out who was paying her, and what those characters meant to the "
+ "livelihood of children around the world."
+)
+md_test.volume_count = None
+md_test.critical_rating = None
+md_test.country = None
+md_test.alternate_series = "Tales"
+md_test.alternate_number = "2"
+md_test.alternate_count = 7
+md_test.imprint = "craphound.com"
+md_test.notes = "Tagged with ComicTagger 1.3.2a5 using info from Comic Vine on 2022-04-16 15:52:26. [Issue ID 140529]"
+md_test.web_link = "https://comicvine.gamespot.com/cory-doctorows-futuristic-tales-of-the-here-and-no/4000-140529/"
+md_test.format = "Series"
+md_test.manga = "No"
+md_test.black_and_white = None
+md_test.page_count = 24
+md_test.maturity_rating = "Everyone 10+"
+md_test.story_arc = "Here and Now"
+md_test.series_group = "Futuristic Tales"
+md_test.scan_info = "(CC BY-NC-SA 3.0)"
+md_test.characters = "Anda"
+md_test.teams = "Fahrenheit"
+md_test.locations = "lonely cottage"
+md_test.credits = [
+ {"person": "Dara Naraghi", "role": "Writer"},
+ {"person": "Esteve Polls", "role": "Penciller"},
+ {"person": "Esteve Polls", "role": "Inker"},
+ {"person": "Neil Uyetake", "role": "Letterer"},
+ {"person": "Sam Kieth", "role": "Cover"},
+ {"person": "Ted Adams", "role": "Editor"},
+]
+md_test.tags = []
+md_test.pages = [
+ {"Image": "0", "ImageHeight": "1280", "ImageSize": "195977", "ImageWidth": "800", "Type": "FrontCover"},
+ {"Image": "1", "ImageHeight": "2039", "ImageSize": "611993", "ImageWidth": "1327"},
+ {"Image": "2", "ImageHeight": "2039", "ImageSize": "783726", "ImageWidth": "1327"},
+ {"Image": "3", "ImageHeight": "2039", "ImageSize": "679584", "ImageWidth": "1327"},
+ {"Image": "4", "ImageHeight": "2039", "ImageSize": "788179", "ImageWidth": "1327"},
+ {"Image": "5", "ImageHeight": "2039", "ImageSize": "864433", "ImageWidth": "1327"},
+ {"Image": "6", "ImageHeight": "2039", "ImageSize": "765606", "ImageWidth": "1327"},
+ {"Image": "7", "ImageHeight": "2039", "ImageSize": "876427", "ImageWidth": "1327"},
+ {"Image": "8", "ImageHeight": "2039", "ImageSize": "852622", "ImageWidth": "1327"},
+ {"Image": "9", "ImageHeight": "2039", "ImageSize": "800205", "ImageWidth": "1327"},
+ {"Image": "10", "ImageHeight": "2039", "ImageSize": "746243", "ImageWidth": "1326"},
+ {"Image": "11", "ImageHeight": "2039", "ImageSize": "718062", "ImageWidth": "1327"},
+ {"Image": "12", "ImageHeight": "2039", "ImageSize": "532179", "ImageWidth": "1326"},
+ {"Image": "13", "ImageHeight": "2039", "ImageSize": "686708", "ImageWidth": "1327"},
+ {"Image": "14", "ImageHeight": "2039", "ImageSize": "641907", "ImageWidth": "1327"},
+ {"Image": "15", "ImageHeight": "2039", "ImageSize": "805388", "ImageWidth": "1327"},
+ {"Image": "16", "ImageHeight": "2039", "ImageSize": "668927", "ImageWidth": "1326"},
+ {"Image": "17", "ImageHeight": "2039", "ImageSize": "710605", "ImageWidth": "1327"},
+ {"Image": "18", "ImageHeight": "2039", "ImageSize": "761398", "ImageWidth": "1326"},
+ {"Image": "19", "ImageHeight": "2039", "ImageSize": "743807", "ImageWidth": "1327"},
+ {"Image": "20", "ImageHeight": "2039", "ImageSize": "552911", "ImageWidth": "1326"},
+ {"Image": "21", "ImageHeight": "2039", "ImageSize": "556827", "ImageWidth": "1327"},
+ {"Image": "22", "ImageHeight": "2039", "ImageSize": "675078", "ImageWidth": "1326"},
+ {
+ "Bookmark": "Interview",
+ "Image": "23",
+ "ImageHeight": "2032",
+ "ImageSize": "800965",
+ "ImageWidth": "1338",
+ "Type": "Letters",
+ },
+]
+md_test.price = None
+md_test.is_version_of = None
+md_test.rights = None
+md_test.identifier = None
+md_test.last_mark = None
+md_test.cover_image = None
diff --git a/comictaggerlib/cli.py b/comictaggerlib/cli.py
index a386095..47d1ecb 100644
--- a/comictaggerlib/cli.py
+++ b/comictaggerlib/cli.py
@@ -449,20 +449,35 @@ def process_file_cli(filename, opts, settings, match_results: OnlineMatchResults
renamer.set_template(settings.rename_template)
renamer.set_issue_zero_padding(settings.rename_issue_number_padding)
renamer.set_smart_cleanup(settings.rename_use_smart_string_cleanup)
+ renamer.move = settings.rename_move_dir
- new_name = renamer.determine_name(ca.path, ext=new_ext)
-
- if new_name == os.path.basename(ca.path):
- logger.error(msg_hdr + "Filename is already good!")
+ try:
+ new_name = renamer.determine_name(filename, ext=new_ext)
+ except Exception as e:
+ print(
+ msg_hdr + "Invalid format string!\nYour rename template is invalid!\n\n"
+ "{}\n\nPlease consult the template help in the settings "
+ "and the documentation on the format at "
+ "https://docs.python.org/3/library/string.html#format-string-syntax",
+ file=sys.stderr,
+ )
return
- folder = os.path.dirname(os.path.abspath(ca.path))
+ folder = os.path.dirname(os.path.abspath(filename))
+ if settings.rename_move_dir and len(settings.rename_dir.strip()) > 3:
+ folder = settings.rename_dir.strip()
+
new_abs_path = utils.unique_file(os.path.join(folder, new_name))
+ if os.path.join(folder, new_name) == os.path.abspath(filename):
+ print(msg_hdr + "Filename is already good!", file=sys.stderr)
+ return
+
suffix = ""
if not opts.dryrun:
# rename the file
- os.rename(ca.path, new_abs_path)
+ os.makedirs(os.path.dirname(new_abs_path), 0o777, True)
+ os.rename(filename, new_abs_path)
else:
suffix = " (dry-run, no change)"
diff --git a/comictaggerlib/filerenamer.py b/comictaggerlib/filerenamer.py
index e35aa63..3a79e44 100644
--- a/comictaggerlib/filerenamer.py
+++ b/comictaggerlib/filerenamer.py
@@ -17,7 +17,9 @@
import datetime
import logging
import os
-import re
+import string
+
+from pathvalidate import sanitize_filepath
from comicapi.genericmetadata import GenericMetadata
from comicapi.issuestring import IssueString
@@ -25,12 +27,82 @@ from comicapi.issuestring import IssueString
logger = logging.getLogger(__name__)
+class MetadataFormatter(string.Formatter):
+ def __init__(self, smart_cleanup=False):
+ super().__init__()
+ self.smart_cleanup = smart_cleanup
+
+ def format_field(self, value, format_spec):
+ if value is None or value == "":
+ return ""
+ return super().format_field(value, format_spec)
+
+ def _vformat(self, format_string, args, kwargs, used_args, recursion_depth, auto_arg_index=0):
+ if recursion_depth < 0:
+ raise ValueError("Max string recursion exceeded")
+ result = []
+ lstrip = False
+ for literal_text, field_name, format_spec, conversion in self.parse(format_string):
+
+ # output the literal text
+ if literal_text:
+ if lstrip:
+ result.append(literal_text.lstrip("-_)}]#"))
+ else:
+ result.append(literal_text)
+ lstrip = False
+ # if there's a field, output it
+ if field_name is not None:
+ # this is some markup, find the object and do
+ # the formatting
+
+ # handle arg indexing when empty field_names are given.
+ if field_name == "":
+ if auto_arg_index is False:
+ raise ValueError(
+ "cannot switch from manual field " "specification to automatic field " "numbering"
+ )
+ field_name = str(auto_arg_index)
+ auto_arg_index += 1
+ elif field_name.isdigit():
+ if auto_arg_index:
+ raise ValueError(
+ "cannot switch from manual field " "specification to automatic field " "numbering"
+ )
+ # disable auto arg incrementing, if it gets
+ # used later on, then an exception will be raised
+ auto_arg_index = False
+
+ # given the field_name, find the object it references
+ # and the argument it came from
+ obj, arg_used = self.get_field(field_name, args, kwargs)
+ used_args.add(arg_used)
+
+ # do any conversion on the resulting object
+ obj = self.convert_field(obj, conversion)
+
+ # expand the format spec, if needed
+ format_spec, auto_arg_index = self._vformat(
+ format_spec, args, kwargs, used_args, recursion_depth - 1, auto_arg_index=auto_arg_index
+ )
+
+ # format the object and append to the result
+ fmt_obj = self.format_field(obj, format_spec)
+ if fmt_obj == "" and len(result) > 0 and self.smart_cleanup:
+ lstrip = True
+ result.pop()
+ result.append(fmt_obj)
+
+ return "".join(result), auto_arg_index
+
+
class FileRenamer:
def __init__(self, metadata):
- self.template = "%series% v%volume% #%issue% (of %issuecount%) (%year%)"
+ self.template = "{publisher}/{series}/{series} v{volume} #{issue} (of {issueCount}) ({year})"
self.smart_cleanup = True
self.issue_zero_padding = 3
self.metadata = metadata
+ self.move = False
def set_metadata(self, metadata: GenericMetadata):
self.metadata = metadata
@@ -44,101 +116,39 @@ class FileRenamer:
def set_template(self, template: str):
self.template = template
- def replace_token(self, text, value, token):
- # helper func
- def is_token(word):
- return word[0] == "%" and word[-1:] == "%"
-
- if value is not None:
- return text.replace(token, str(value))
-
- if self.smart_cleanup:
- # smart cleanup means we want to remove anything appended to token if it's empty (e.g "#%issue%" or "v%volume%")
- # (TODO: This could fail if there is more than one token appended together, I guess)
- text_list = text.split()
-
- # special case for issuecount, remove preceding non-token word,
- # as in "...(of %issuecount%)..."
- if token == "%issuecount%":
- for idx, word in enumerate(text_list):
- if token in word and not is_token(text_list[idx - 1]):
- text_list[idx - 1] = ""
-
- text_list = [x for x in text_list if token not in x]
- return " ".join(text_list)
-
- return text.replace(token, "")
-
def determine_name(self, filename, ext=None):
+ class Default(dict):
+ def __missing__(self, key):
+ return "{" + key + "}"
md = self.metadata
- new_name = self.template
- new_name = self.replace_token(new_name, md.series, "%series%")
- new_name = self.replace_token(new_name, md.volume, "%volume%")
+ # padding for issue
+ md.issue = IssueString(md.issue).as_string(pad=self.issue_zero_padding)
- if md.issue is not None:
- issue_str = IssueString(md.issue).as_string(pad=self.issue_zero_padding)
- else:
- issue_str = None
- new_name = self.replace_token(new_name, issue_str, "%issue%")
+ template = self.template
- new_name = self.replace_token(new_name, md.issue_count, "%issuecount%")
- new_name = self.replace_token(new_name, md.year, "%year%")
- new_name = self.replace_token(new_name, md.publisher, "%publisher%")
- new_name = self.replace_token(new_name, md.title, "%title%")
- new_name = self.replace_token(new_name, md.month, "%month%")
- month_name = None
- if md.month is not None:
- if (isinstance(md.month, str) and md.month.isdigit()) or isinstance(md.month, int):
- if int(md.month) in range(1, 13):
- dt = datetime.datetime(1970, int(md.month), 1, 0, 0)
- month_name = dt.strftime("%B")
- new_name = self.replace_token(new_name, month_name, "%month_name%")
+ path_components = template.split(os.sep)
+ new_name = ""
- new_name = self.replace_token(new_name, md.genre, "%genre%")
- new_name = self.replace_token(new_name, md.language, "%language_code%")
- new_name = self.replace_token(new_name, md.critical_rating, "%criticalrating%")
- new_name = self.replace_token(new_name, md.alternate_series, "%alternateseries%")
- new_name = self.replace_token(new_name, md.alternate_number, "%alternatenumber%")
- new_name = self.replace_token(new_name, md.alternate_count, "%alternatecount%")
- new_name = self.replace_token(new_name, md.imprint, "%imprint%")
- new_name = self.replace_token(new_name, md.format, "%format%")
- new_name = self.replace_token(new_name, md.maturity_rating, "%maturityrating%")
- new_name = self.replace_token(new_name, md.story_arc, "%storyarc%")
- new_name = self.replace_token(new_name, md.series_group, "%seriesgroup%")
- new_name = self.replace_token(new_name, md.scan_info, "%scaninfo%")
+ fmt = MetadataFormatter(self.smart_cleanup)
+ for Component in path_components:
+ new_name = os.path.join(
+ new_name, fmt.vformat(Component, args=[], kwargs=Default(vars(md))).replace("/", "-")
+ )
- if self.smart_cleanup:
- # remove empty braces,brackets, parentheses
- new_name = re.sub(r"\(\s*[-:]*\s*\)", "", new_name)
- new_name = re.sub(r"\[\s*[-:]*\s*]", "", new_name)
- new_name = re.sub(r"{\s*[-:]*\s*}", "", new_name)
-
- # remove duplicate spaces
- new_name = " ".join(new_name.split())
-
- # remove remove duplicate -, _,
- new_name = re.sub(r"[-_]{2,}\s+", "-- ", new_name)
- new_name = re.sub(r"(\s--)+", " --", new_name)
- new_name = re.sub(r"(\s-)+", " -", new_name)
-
- # remove dash or double dash at end of line
- new_name = re.sub(r"[-]{1,2}\s*$", "", new_name)
-
- # remove duplicate spaces (again!)
- new_name = " ".join(new_name.split())
-
- if ext is None:
+ if ext is None or ext == "":
ext = os.path.splitext(filename)[1]
new_name += ext
# some tweaks to keep various filesystems happy
- new_name = new_name.replace("/", "-")
- new_name = new_name.replace(" :", " -")
new_name = new_name.replace(": ", " - ")
new_name = new_name.replace(":", "-")
- new_name = new_name.replace("?", "")
- return new_name
+ # remove padding
+ md.issue = IssueString(md.issue).as_string()
+ if self.move:
+ return sanitize_filepath(new_name.strip())
+ else:
+ return os.path.basename(sanitize_filepath(new_name.strip()))
diff --git a/comictaggerlib/renamewindow.py b/comictaggerlib/renamewindow.py
index bb02a84..d452d92 100644
--- a/comictaggerlib/renamewindow.py
+++ b/comictaggerlib/renamewindow.py
@@ -82,7 +82,22 @@ class RenameWindow(QtWidgets.QDialog):
if md.is_empty:
md = ca.metadata_from_filename(self.settings.parse_scan_info)
self.renamer.set_metadata(md)
- new_name = self.renamer.determine_name(ca.path, ext=new_ext)
+ self.renamer.move = self.settings.rename_move_dir
+
+ try:
+ new_name = self.renamer.determine_name(ca.path, ext=new_ext)
+ except Exception as e:
+ QtWidgets.QMessageBox.critical(
+ self,
+ "Invalid format string!",
+ "Your rename template is invalid!"
+ "
{}
"
+ "Please consult the template help in the "
+ "settings and the documentation on the format at "
+ ""
+ "https://docs.python.org/3/library/string.html#format-string-syntax".format(e),
+ )
+ return
row = self.twList.rowCount()
self.twList.insertRow(row)
@@ -150,7 +165,13 @@ class RenameWindow(QtWidgets.QDialog):
center_window_on_parent(prog_dialog)
QtCore.QCoreApplication.processEvents()
- if item["new_name"] == os.path.basename(item["archive"].path):
+ folder = os.path.dirname(os.path.abspath(item["archive"].path))
+ if self.settings.rename_move_dir and len(self.settings.rename_dir.strip()) > 3:
+ folder = self.settings.rename_dir.strip()
+
+ new_abs_path = utils.unique_file(os.path.join(folder, item["new_name"]))
+
+ if os.path.join(folder, item["new_name"]) == item["archive"].path:
print(item["new_name"], "Filename is already good!")
logger.info(item["new_name"], "Filename is already good!")
continue
@@ -158,9 +179,7 @@ class RenameWindow(QtWidgets.QDialog):
if not item["archive"].is_writable(check_rar_status=False):
continue
- folder = os.path.dirname(os.path.abspath(item["archive"].path))
- new_abs_path = utils.unique_file(os.path.join(folder, item["new_name"]))
-
+ os.makedirs(os.path.dirname(new_abs_path), 0o777, True)
os.rename(item["archive"].path, new_abs_path)
item["archive"].rename(new_abs_path)
diff --git a/comictaggerlib/settings.py b/comictaggerlib/settings.py
index 0af8402..307d439 100644
--- a/comictaggerlib/settings.py
+++ b/comictaggerlib/settings.py
@@ -110,10 +110,12 @@ class ComicTaggerSettings:
self.apply_cbl_transform_on_bulk_operation = False
# Rename settings
- self.rename_template = "%series% #%issue% (%year%)"
+ self.rename_template = "{publisher}/{series}/{series} #{issue} - {title} ({year})"
self.rename_issue_number_padding = 3
self.rename_use_smart_string_cleanup = True
self.rename_extension_based_on_archive = True
+ self.rename_dir = ""
+ self.rename_move_dir = False
# Auto-tag stickies
self.save_on_low_confidence = False
@@ -183,10 +185,12 @@ class ComicTaggerSettings:
self.apply_cbl_transform_on_bulk_operation = False
# Rename settings
- self.rename_template = "%series% #%issue% (%year%)"
+ self.rename_template = "{publisher}/{series}/{series} #{issue} - {title} ({year})"
self.rename_issue_number_padding = 3
self.rename_use_smart_string_cleanup = True
self.rename_extension_based_on_archive = True
+ self.rename_dir = ""
+ self.rename_move_dir = False
# Auto-tag stickies
self.save_on_low_confidence = False
@@ -344,6 +348,10 @@ class ComicTaggerSettings:
self.rename_extension_based_on_archive = self.config.getboolean(
"rename", "rename_extension_based_on_archive"
)
+ if self.config.has_option("rename", "rename_dir"):
+ self.rename_dir = self.config.get("rename", "rename_dir")
+ if self.config.has_option("rename", "rename_move_dir"):
+ self.rename_move_dir = self.config.getboolean("rename", "rename_move_dir")
if self.config.has_option("autotag", "save_on_low_confidence"):
self.save_on_low_confidence = self.config.getboolean("autotag", "save_on_low_confidence")
@@ -441,6 +449,8 @@ class ComicTaggerSettings:
self.config.set("rename", "rename_issue_number_padding", self.rename_issue_number_padding)
self.config.set("rename", "rename_use_smart_string_cleanup", self.rename_use_smart_string_cleanup)
self.config.set("rename", "rename_extension_based_on_archive", self.rename_extension_based_on_archive)
+ self.config.set("rename", "rename_dir", self.rename_dir)
+ self.config.set("rename", "rename_move_dir", self.rename_move_dir)
if not self.config.has_section("autotag"):
self.config.add_section("autotag")
diff --git a/comictaggerlib/settingswindow.py b/comictaggerlib/settingswindow.py
index 904ee36..cba663b 100644
--- a/comictaggerlib/settingswindow.py
+++ b/comictaggerlib/settingswindow.py
@@ -21,8 +21,10 @@ import platform
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from comicapi import utils
+from comicapi.genericmetadata import md_test
from comictaggerlib.comicvinecacher import ComicVineCacher
from comictaggerlib.comicvinetalker import ComicVineTalker
+from comictaggerlib.filerenamer import FileRenamer
from comictaggerlib.imagefetcher import ImageFetcher
from comictaggerlib.settings import ComicTaggerSettings
@@ -111,6 +113,14 @@ class SettingsWindow(QtWidgets.QDialog):
self.btnClearCache.clicked.connect(self.clear_cache)
self.btnResetSettings.clicked.connect(self.reset_settings)
self.btnTestKey.clicked.connect(self.test_api_key)
+ self.btnTemplateHelp.clicked.connect(self.show_template_help)
+
+ def config_renamer(self):
+
+ self.renamer = FileRenamer(md_test)
+ self.renamer.set_template(str(self.leRenameTemplate.text()))
+ self.renamer.set_issue_zero_padding(self.settings.rename_issue_number_padding)
+ self.renamer.set_smart_cleanup(self.settings.rename_use_smart_string_cleanup)
def settings_to_form(self):
@@ -166,9 +176,29 @@ class SettingsWindow(QtWidgets.QDialog):
self.cbxSmartCleanup.setCheckState(QtCore.Qt.CheckState.Checked)
if self.settings.rename_extension_based_on_archive:
self.cbxChangeExtension.setCheckState(QtCore.Qt.CheckState.Checked)
+ if self.settings.rename_move_dir:
+ self.cbxMoveFiles.setCheckState(QtCore.Qt.CheckState.Checked)
+ self.leDirectory.setText(self.settings.rename_dir)
def accept(self):
+ self.config_renamer()
+
+ try:
+ new_name = self.renamer.determine_name("test.cbz")
+ except Exception as e:
+ QtWidgets.QMessageBox.critical(
+ self,
+ "Invalid format string!",
+ "Your rename template is invalid!"
+ "
{}
"
+ "Please consult the template help in the "
+ "settings and the documentation on the format at "
+ ""
+ "https://docs.python.org/3/library/string.html#format-string-syntax".format(e),
+ )
+ return
+
# Copy values from form to settings and save
self.settings.rar_exe_path = str(self.leRarExePath.text())
@@ -213,6 +243,8 @@ class SettingsWindow(QtWidgets.QDialog):
self.settings.rename_issue_number_padding = int(self.leIssueNumPadding.text())
self.settings.rename_use_smart_string_cleanup = self.cbxSmartCleanup.isChecked()
self.settings.rename_extension_based_on_archive = self.cbxChangeExtension.isChecked()
+ self.settings.rename_move_dir = self.cbxMoveFiles.isChecked()
+ self.settings.rename_dir = self.leDirectory.text()
self.settings.save()
QtWidgets.QDialog.accept(self)
@@ -262,3 +294,15 @@ class SettingsWindow(QtWidgets.QDialog):
def show_rename_tab(self):
self.tabWidget.setCurrentIndex(5)
+
+ def show_template_help(self):
+ template_help_win = TemplateHelpWindow(self)
+ template_help_win.setModal(False)
+ template_help_win.show()
+
+
+class TemplateHelpWindow(QtWidgets.QDialog):
+ def __init__(self, parent):
+ super(TemplateHelpWindow, self).__init__(parent)
+
+ uic.loadUi(ComicTaggerSettings.get_ui_file("TemplateHelp.ui"), self)
diff --git a/comictaggerlib/ui/TemplateHelp.ui b/comictaggerlib/ui/TemplateHelp.ui
new file mode 100644
index 0000000..0a692f1
--- /dev/null
+++ b/comictaggerlib/ui/TemplateHelp.ui
@@ -0,0 +1,110 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 702
+ 452
+
+
+
+ Template Help
+
+
+ true
+
+
+
+ 0
+
+
+ 2
+
+
+ 2
+
+ -
+
+
+ true
+
+
+ <html>
+ <head/>
+ <body>
+ <h1 style="text-align: center">Template help</h1>
+ <p>The template uses Python format strings, in the simplest use it replaces the field (e.g. {issue}) with the value for that particular comic (e.g. 1) for advanced formatting please reference the
+
+ <a href="https://docs.python.org/3/library/string.html#format-string-syntax">Python 3 documentation</a></p>
+ <pre>Accepts the following variables:
+{isEmpty} (boolean)
+{tagOrigin} (string)
+{series} (string)
+{issue} (string)
+{title} (string)
+{publisher} (string)
+{month} (integer)
+{year} (integer)
+{day} (integer)
+{issueCount} (integer)
+{volume} (integer)
+{genre} (string)
+{language} (string)
+{comments} (string)
+{volumeCount} (integer)
+{criticalRating} (string)
+{country} (string)
+{alternateSeries} (string)
+{alternateNumber} (string)
+{alternateCount} (integer)
+{imprint} (string)
+{notes} (string)
+{webLink} (string)
+{format} (string)
+{manga} (string)
+{blackAndWhite} (boolean)
+{pageCount} (integer)
+{maturityRating} (string)
+{storyArc} (string)
+{seriesGroup} (string)
+{scanInfo} (string)
+{characters} (string)
+{teams} (string)
+{locations} (string)
+{credits} (list of dict({'role': 'str', 'person': 'str', 'primary': boolean}))
+{tags} (list of str)
+{pages} (list of dict({'Image': 'str(int)', 'Type': 'str'}))
+
+CoMet-only items:
+{price} (float)
+{isVersionOf} (string)
+{rights} (string)
+{identifier} (string)
+{lastMark} (string)
+{coverImage} (string)
+
+Examples:
+
+{series} {issue} ({year})
+Spider-Geddon 1 (2018)
+
+{series} #{issue} - {title}
+Spider-Geddon #1 - New Players; Check In
+
+</pre>
+ </body>
+</html>
+
+
+ true
+
+
+
+
+
+
+
+
diff --git a/comictaggerlib/ui/settingswindow.ui b/comictaggerlib/ui/settingswindow.ui
index c64e7ce..7ca3dbf 100644
--- a/comictaggerlib/ui/settingswindow.ui
+++ b/comictaggerlib/ui/settingswindow.ui
@@ -547,7 +547,7 @@
QFormLayout::AllNonFixedFieldsGrow
-
-
+
Template:
@@ -556,31 +556,80 @@
-
- <html><head/><body><p>The template for the new filename. Accepts the following variables:</p><p>%series%<br/>%issue%<br/>%volume%<br/>%issuecount%<br/>%year%<br/>%month%<br/>%month_name%<br/>%publisher%<br/>%title%<br/>
-%genre%<br/>
-%language_code%<br/>
-%criticalrating%<br/>
-%alternateseries%<br/>
-%alternatenumber%<br/>
-%alternatecount%<br/>
-%imprint%<br/>
-%format%<br/>
-%maturityrating%<br/>
-%storyarc%<br/>
-%seriesgroup%<br/>
-%scaninfo%
-</p><p>Examples:</p><p><span style=" font-style:italic;">%series% %issue% (%year%)</span><br/><span style=" font-style:italic;">%series% #%issue% - %title%</span></p></body></html>
+ <pre>The template for the new filename. Uses python format strings https://docs.python.org/3/library/string.html#format-string-syntax
+Accepts the following variables:
+{isEmpty} (boolean)
+{tagOrigin} (string)
+{series} (string)
+{issue} (string)
+{title} (string)
+{publisher} (string)
+{month} (integer)
+{year} (integer)
+{day} (integer)
+{issueCount} (integer)
+{volume} (integer)
+{genre} (string)
+{language} (string)
+{comments} (string)
+{volumeCount} (integer)
+{criticalRating} (string)
+{country} (string)
+{alternateSeries} (string)
+{alternateNumber} (string)
+{alternateCount} (integer)
+{imprint} (string)
+{notes} (string)
+{webLink} (string)
+{format} (string)
+{manga} (string)
+{blackAndWhite} (boolean)
+{pageCount} (integer)
+{maturityRating} (string)
+{storyArc} (string)
+{seriesGroup} (string)
+{scanInfo} (string)
+{characters} (string)
+{teams} (string)
+{locations} (string)
+{credits} (list of dict({'role': 'str', 'person': 'str', 'primary': boolean}))
+{tags} (list of str)
+{pages} (list of dict({'Image': 'str(int)', 'Type': 'str'}))
+
+CoMet-only items:
+{price} (float)
+{isVersionOf} (string)
+{rights} (string)
+{identifier} (string)
+{lastMark} (string)
+{coverImage} (string)
+
+Examples:
+
+{series} {issue} ({year})
+Spider-Geddon 1 (2018)
+
+{series} #{issue} - {title}
+Spider-Geddon #1 - New Players; Check In
+</pre>
-
-
+
+
+ Template Help
+
+
+
+ -
+
Issue # Zero Padding
- -
+
-
@@ -599,7 +648,7 @@
- -
+
-
<html><head/><body><p><span style=" font-weight:600;">"Smart Text Cleanup" </span>will attempt to clean up the new filename if there are missing fields from the template. For example, removing empty braces, repeated spaces and dashes, and more. Experimental feature.</p></body></html>
@@ -609,13 +658,33 @@
- -
+
-
Change Extension Based On Archive Type
+ -
+
+
+ If checked moves files to specified folder
+
+
+ Move files when renaming
+
+
+
+ -
+
+
+ Destination Directory:
+
+
+
+ -
+
+
diff --git a/requirements.txt b/requirements.txt
index e929a15..c6e833d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,5 +2,6 @@ beautifulsoup4 >= 4.1
natsort>=8.1.0
pillow>=4.3.0
requests==2.*
+pathvalidate
pycountry
py7zr