From 1c668db7f16de9533ceb6d01b6d489ab437c6b97 Mon Sep 17 00:00:00 2001 From: ZTF-Zeiram Date: Sat, 6 Dec 2025 03:36:49 +0000 Subject: [PATCH] Upload files to "/" --- README.md | 101 +++++++++++- main.py | 185 ++++++++++++++++++++++ plugin-import-name-EmbedComicMetadata.txt | Bin 0 -> 1024 bytes ui.py | 125 +++++++++++++++ 4 files changed, 409 insertions(+), 2 deletions(-) create mode 100644 main.py create mode 100644 plugin-import-name-EmbedComicMetadata.txt create mode 100644 ui.py diff --git a/README.md b/README.md index 10715d9..0e4a678 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,100 @@ -# EmbedMangaMetadata +# Embed Comic Metadata -A Calibre Plugin to embed calibres metadata into cbz comic archieves \ No newline at end of file +A Calibre Plugin to manage comic metadata in calibre + +## Special Notes + +Requires calibre version 1.0.0 or later. + +## Main Features + +- Can embed the metadata to the zip comment or a ComicInfo.xml file inside the archives +- Can read the above metadata formats and import them into calibre +- Can write many additional metadata into custom columns Can automatically convert cbr/zip/rar files to cbz +- Can embed the calibre cover into cbz comics (experimental) +- Can count the number of pages (image files) in a comic +- Can get the size of the images in the comic file + +## Usage + +### To embed calibres metadata into the comic archive: + +- Select the comics that should be updated in the library. +- Click the addon EmbedComicMetadata icon in your toolbar +- (You can select a specific action or open the configuration bei clicking on the small arrow on the icon and selecting the desired option) + +### To import the comic archive metadata into calibre: + +- Select the comics that should be updated in the library. +- Click the small arrow on the addon EmbedComicMetadata icon in your toolbar +- Click on "Import Metadata from the comic archive into calibre" + +### Custom Columns: + +You can make custom columns in calibre and populate them with metadata imported with the plugin. In the configuration use the dropdown menu for the columns to select what metadata should be written to what custom column. + +The custom columns you make in calibre should be of the following type, depending on the metadata stored in them. + +#### Comma seperated text, like tags, shown in the tag browser with "Contains names" checked: + +Penciller, Inker, Colorist, Letterer, Cover Artist, Editor + +#### Comma seperated text, like tags, shown in the tag browser: + +Characters, Teams, Locations, Genre + +#### Text, column shown in the tag browser: + +Story Arc, Volume, Number of Issues + +#### Text, but with a fixed set of permitted values: + +* Manga + * Values: No,Yes,YesAndRightToLeft + * Default Value: No + +#### Integer: + +Number of Issues, Volume + +#### Float: + +Image Size + +#### Series like: + +Story Arc + +#### Comment: + +Comic Vine Link + +### Customizing the main menu: + +The menu in the toolbar can be custimized to your liking through the options in the configuration. + +### Embed Cover: + +Use with care, just inserts the calibre cover as "00000000_cover" into the comic archive (previously inserted calibre covers are overwritten). + +### Get image size + +Tries to get the size of the first non cover and non landscape (in case there are double pages, after 9 pages, take that value) image in the comic file in megapixels. + +## Configuration + +- **Write metadata in zip comment**: This format is used by calibre, if you import comic files and by ComicbookLovers (default: on) +- **Write metadata in ComicInfo.xml**: This format is used by ComicRack and some other comic readers (default: on) +- **Auto convert cbr to cbz**: If a comic has only the cbr format, convert it to store the metadata (default: on) +- **Also convert rar and zip to cbz**: Expand the behaviour for cbr to rars and zips (default: off) +- **Auto convert while importing to calibre**: As above, but even when importing metadata into calibre (default: off) +- **Delete cbr after conversion**: Deletes the cbr format after the conversion (default: off) +- **Swap names to "LN, FN" when importing metadata**: Does just what it says (default: off) +- **Auto count pages if importing**: Count pages automatically if importing metadata into calibre (default: off) +- **Get the image size if importing**: Get the image size automatically if importing metadata into calibre (default: off) +- **Main Button Action**: You can set, what action should be performed if the big toolbar button is pressed. Needs a calibre restart (default: Embed metadata) +- **Menu Buttons**: The dropdown menu on the icon in the toolbar can be custimized to your liking through these options + +## Acknowledgement + +The handling of the comic metadata is done by using code from [ComicTagger](https://code.google.com/p/comictagger/) diff --git a/main.py b/main.py new file mode 100644 index 0000000..e279833 --- /dev/null +++ b/main.py @@ -0,0 +1,185 @@ +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2015, dloraine' +__docformat__ = 'restructuredtext en' + +from functools import partial +from calibre.gui2 import error_dialog, info_dialog + +from calibre_plugins.EmbedComicMetadata.config import prefs +from calibre_plugins.EmbedComicMetadata.languages.lang import _L +from calibre_plugins.EmbedComicMetadata.comicmetadata import ComicMetadata + +import sys + +python3 = sys.version_info[0] > 2 + +def import_to_calibre(ia, action): + def _import_to_calibre(metadata): + metadata.get_comic_metadata_from_file() + if action == "both" and metadata.comic_metadata: + metadata.import_comic_metadata_to_calibre(metadata.comic_metadata) + elif action == "cix" and metadata.cix_metadata: + metadata.import_comic_metadata_to_calibre(metadata.cix_metadata) + elif action == "cbi" and metadata.cbi_metadata: + metadata.import_comic_metadata_to_calibre(metadata.cbi_metadata) + else: + return False + return True + + iterate_over_books(ia, _import_to_calibre, + _L["Updated Calibre Metadata"], + _L['Updated calibre metadata for {} book(s)'], + _L['The following books had no metadata: {}'], + prefs['convert_reading']) + + +def embed_into_comic(ia, action): + def _embed_into_comic(metadata): + if metadata.format != "cbz": + return False + metadata.overlay_metadata() + if action == "both" or action == "cix": + metadata.embed_cix_metadata() + if action == "both" or action == "cbi": + metadata.embed_cbi_metadata() + metadata.add_updated_comic_to_calibre() + return True + + iterate_over_books(ia, _embed_into_comic, + _L["Updated comics"], + _L['Updated the metadata in the files of {} comics'], + _L['The following books were not updated: {}']) + + +def convert(ia): + iterate_over_books(ia, partial(convert_to_cbz, ia), + _L["Converted files"], + _L['Converted {} book(s) to cbz'], + _L['The following books were not converted: {}'], + False) + + +def embed_cover(ia): + def _embed_cover(metadata): + if metadata.format != "cbz": + return False + metadata.update_cover() + metadata.add_updated_comic_to_calibre() + return True + + iterate_over_books(ia, _embed_cover, + _L["Updated Covers"], + _L['Embeded {} covers'], + _L['The following covers were not embeded: {}']) + + +def count_pages(ia): + def _count_pages(metadata): + if metadata.format != "cbz": + return False + return metadata.action_count_pages() + + iterate_over_books(ia, _count_pages, + _L["Counted pages"], + _L['Counted pages in {} comics'], + _L['The following comics were not counted: {}']) + + +def remove_metadata(ia): + def _remove_metadata(metadata): + if metadata.format != "cbz": + return False + metadata.remove_embedded_metadata() + metadata.add_updated_comic_to_calibre() + return True + + iterate_over_books(ia, _remove_metadata, + _L["Removed metadata"], + _L['Removed metadata in {} comics'], + _L['The following comics did not have metadata removed: {}']) + + +def get_image_size(ia): + def _get_image_size(metadata): + if metadata.format != "cbz": + return False + return metadata.action_picture_size() + + iterate_over_books(ia, _get_image_size, + _L["Updated Calibre Metadata"], + _L['Updated calibre metadata for {} book(s)'], + _L['The following books were not updated: {}']) + + +def iterate_over_books(ia, func, title, ptext, notptext, + should_convert=None, + convtext=_L["The following comics were converted to cbz: {}"]): + ''' + Iterates over all selected books. For each book, it checks if it should be + converted to cbz and then applies func to the book. + After all books are processed, gives a completion message. + ''' + processed = [] + not_processed = [] + converted = [] + + if should_convert is None: + should_convert = prefs["convert_cbr"] + + # iterate through the books + for book_id in get_selected_books(ia): + metadata = ComicMetadata(book_id, ia) + + # sanity check + if metadata.format is None: + not_processed.append(metadata.info) + continue + + if should_convert and convert_to_cbz(ia, metadata): + converted.append(metadata.info) + + if func(metadata): + processed.append(metadata.info) + else: + not_processed.append(metadata.info) + + # show a completion message + msg = ptext.format(len(processed)) + if should_convert and len(converted) > 0: + msg += '\n' + convtext.format(lst2string(converted)) + if len(not_processed) > 0: + msg += '\n' + notptext.format(lst2string(not_processed)) + info_dialog(ia.gui, title, msg, show=True) + + +def get_selected_books(ia): + # Get currently selected books + rows = ia.gui.library_view.selectionModel().selectedRows() + if not rows or len(rows) == 0: + return error_dialog(ia.gui, _L['Cannot update metadata'], + _L['No books selected'], show=True) + # Map the rows to book ids + return map(ia.gui.library_view.model().id, rows) + + +def lst2string(lst): + if python3: + return "\n " + "\n ".join(lst) + return "\n " + "\n ".join(item.encode('utf-8') for item in lst) + + +def convert_to_cbz(ia, metadata): + if metadata.format == "cbr" or (metadata.format == "rar" and prefs['convert_archives']): + metadata.convert_cbr_to_cbz() + if prefs['delete_cbr']: + ia.gui.current_db.new_api.remove_formats({metadata.book_id: {"cbr", "rar"}}) + return True + elif metadata.format == "zip" and prefs['convert_archives']: + metadata.convert_zip_to_cbz() + if prefs['delete_cbr']: + ia.gui.current_db.new_api.remove_formats({metadata.book_id: {"zip"}}) + return True + return False diff --git a/plugin-import-name-EmbedComicMetadata.txt b/plugin-import-name-EmbedComicMetadata.txt new file mode 100644 index 0000000000000000000000000000000000000000..06d7405020018ddf3cacee90fd4af10487da3d20 GIT binary patch literal 1024 ScmZQz7zLvtFd70QH3R?z00031 literal 0 HcmV?d00001 diff --git a/ui.py b/ui.py new file mode 100644 index 0000000..9fcab13 --- /dev/null +++ b/ui.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python2 +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2015, dloraine' +__docformat__ = 'restructuredtext en' + +try: + from PyQt5.Qt import QMenu, QIcon, QPixmap +except ImportError: + from PyQt4.Qt import QMenu, QIcon, QPixmap + +from functools import partial + +from calibre.gui2.actions import InterfaceAction +from calibre.gui2 import error_dialog + +from calibre_plugins.EmbedComicMetadata.config import prefs +from calibre_plugins.EmbedComicMetadata.languages.lang import _L +from calibre_plugins.EmbedComicMetadata.ini import ( + get_configuration, CONFIG_NAME, CONFIG_DESCRIPTION, CONFIG_TRIGGER_FUNC, + CONFIG_TRIGGER_ARG, CONFIG_MENU) + + +config = get_configuration() + + +class EmbedComicMetadata(InterfaceAction): + + name = 'Embed Comic Metadata' + + # Declare the main action associated with this plugin + if prefs["main_import"]: + action_spec = (_L['Import Comic Metadata'], None, + _L['Imports the metadata from the comic to calibre'], None) + else: + action_spec = (_L['Embed Comic Metadata'], None, + _L['Embeds calibres metadata into the comic'], None) + + def genesis(self): + # menu + self.menu = QMenu(self.gui) + + # Get the icon for this interface action + icon = self.get_icon('images/embed_comic_metadata.png') + + # The qaction is automatically created from the action_spec defined + # above + self.qaction.setMenu(self.menu) + self.qaction.setIcon(icon) + self.qaction.triggered.connect(self.main_menu_triggered) + + # build menu + self.menu.clear() + self.build_menu() + self.toggle_menu_items() + + def build_menu(self): + for item in config[CONFIG_MENU]["UI_Action_Items"]: + if item[CONFIG_NAME] == "seperator": + self.menu.addSeparator() + continue + elif item[CONFIG_TRIGGER_ARG]: + triggerfunc = partial(item[CONFIG_TRIGGER_FUNC], self, item[CONFIG_TRIGGER_ARG]) + else: + triggerfunc = partial(item[CONFIG_TRIGGER_FUNC], self) + self.menu_action(item[CONFIG_NAME], item[CONFIG_DESCRIPTION], triggerfunc) + # add configuration entry + self.menu_action("configure", _L["Configure"], + partial(self.interface_action_base_plugin.do_user_config, (self.gui))) + + def toggle_menu_items(self): + for item in config[CONFIG_MENU]["Items"]: + action = getattr(self, item[CONFIG_NAME]) + action.setVisible(prefs[item[CONFIG_NAME]]) + + def main_menu_triggered(self): + from calibre_plugins.EmbedComicMetadata.main import embed_into_comic, import_to_calibre + + i = prefs["main_import"] + # Check the preferences for what should be done + if (i and prefs['read_cbi'] and prefs['read_cix']) or ( + not i and prefs['cbi_embed'] and prefs['cix_embed']): + action = "both" + elif (i and prefs['read_cbi']) or (not i and prefs['cbi_embed']): + action = "cbi" + elif (i and prefs['read_cix']) or (not i and prefs['cix_embed']): + action = "cix" + else: + return error_dialog(self.gui, _L['Cannot update metadata'], + _L['No embed format selected'], show=True) + + if i: + import_to_calibre(self, action) + else: + embed_into_comic(self, action) + + def apply_settings(self): + # In an actual non trivial plugin, you would probably need to + # do something based on the settings in prefs + prefs + + def menu_action(self, name, title, triggerfunc): + action = self.create_menu_action(self.menu, name, title, icon=None, + shortcut=None, description=None, + triggered=triggerfunc, shortcut_name=None) + setattr(self, name, action) + + def get_icon(self, icon_name): + import os + from calibre.utils.config import config_dir + + # Check to see whether the icon exists as a Calibre resource + # This will enable skinning if the user stores icons within a folder like: + # ...\AppData\Roaming\calibre\resources\images\Plugin Name\ + icon_path = os.path.join(config_dir, 'resources', 'images', self.name, + icon_name.replace('images/', '')) + if os.path.exists(icon_path): + pixmap = QPixmap() + pixmap.load(icon_path) + return QIcon(pixmap) + # As we did not find an icon elsewhere, look within our zip resources + return get_icons(icon_name)