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({&apos;role&apos;: &apos;str&apos;, &apos;person&apos;: &apos;str&apos;, &apos;primary&apos;: boolean})) +{tags} (list of str) +{pages} (list of dict({&apos;Image&apos;: &apos;str(int)&apos;, &apos;Type&apos;: &apos;str&apos;})) + +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;">&quot;Smart Text Cleanup&quot; </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