Giter Site home page Giter Site logo

buried-in-code / perdoo Goto Github PK

View Code? Open in Web Editor NEW
2.0 2.0 1.0 103 KB

Perdoo's goal is to help sort and organize your comic collection by using the information stored in metadata files inside the comic archives.

License: MIT License

Python 100.00%
comics command-line hatchling metadata python-3 tagger xml

perdoo's Introduction

Perdoo

PyPI - Python PyPI - Status PyPI - Version PyPI - License

Rye Pre-Commit Ruff

Github - Contributors

Perdoo is designed to assist in sorting and organizing your comic collection by utilizing metadata files stored within comic archives.
Perdoo standardizes all your digital comics into a unified format (cb7, cbt, or cbz).
It adds and/or updates metadata files using supported services.
Unlike other tagging tools, Perdoo employs a manual approach when metadata files are absent, prompting users to enter the necessary Publisher/Series/Issue details for search purposes.

Installation

Pipx

  1. Ensure you have Pipx installed: pipx --version
  2. Install the project: pipx install perdoo

From Source

  1. Ensure you have a supported version of Python installed: python --version
  2. Clone the repository: git clone https://github.com/Buried-In-Code/Perdoo
  3. Install the project: pip install .

Execution

  • Perdoo <arguments>

Arguments

Argument Type Description
--force bool Forces the sync of archives, regardless of when they were last updated.
--version bool Displays the version of Perdoo running.
--debug bool Displays extra/debug messages while running.

Supported Formats

Input Extensions

  • .cbr
  • .cbt
  • .cbz
  • .cb7 (Requires installing cb7 dependencies: pip install perdoo[cb7])

Output Extensions

  • .cbt
  • .cbz (Default)
  • .cb7 (Requires installing cb7 dependencies: pip install perdoo[cb7])

Metadata Files

Services

File Organization

Series Naming

Series with a volume greater than 1 will display its volume in the title.

Comic Naming

The files are named based on the format of the comic:

  • Default/Comic: {Series Title}_#{Issue Number}.cbz
  • Annual: {Series Title}_Annual_#{Issue Number}.cbz
  • Digital Chapter: {Series Title}_Chapter_#{Issue Number}.cbz
  • Hardcover: {Series Title}_#{Issue Number}_HC.cbz
  • Trade Paperback: {Series Title}_#{Issue Number}_TP.cbz
  • Graphic Novel: {Series Title}_#{Issue Number}_GN.cbz

Collection Folder Structure

Collection Root
+-- Publisher
|  +-- Series
|  |  +-- Series_#001.cbz
|  |  +-- Series_Annual_#01.cbz
|  |  +-- Series_Chapter_#01.cbz
|  |  +-- Series_#01_HC.cbz
|  |  +-- Series_#01_TP.cbz
|  |  +-- Series_#01_GN.cbz
|  +-- Series-v2
|  |  +-- Series-v2_#001.cbz
|  |  +-- Series-v2_Annual_#01.cbz
|  |  +-- Series-v2_Chapter_#01.cbz
|  |  +-- Series-v2_#01_HC.cbz
|  |  +-- Series-v2_#01_TP.cbz
|  |  +-- Series-v2_#01_GN.cbz

Socials

Social - Fosstodon
Social - Matrix

perdoo's People

Contributors

buried-in-code avatar dependabot[bot] avatar domenicf avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar

Forkers

domenicf

perdoo's Issues

Error when selecting "n" after selection "None of the Above" on comic selection

I have not tested other API's (as I do not have access to the others at this time), but, this issue is happening with the Comicvine API.

When asked to "Try again" and selecting "n" on comic identification, the program exits with exit code 1 and the error AttributeError: 'NoneType' object has no attribute 'issue'.

The full log is pasted below:

Perdoo v0.2.0
Python v3.12.4
INFO     Starting Perdoo                                                        
────────── Descender Compendium (2024) (Digital) ──────────
INFO     Processing Descender Compendium (2024) (Digital)  
Series title: Descender
INFO     Fetching details from Comicvine                                        
+------------------------------ Comicvine Series ------------------------------+
| 1: 80426 | Image | Descender (2015)                                          |
| 2: 106862 | Image | Descender Deluxe Edition (2017)                          |
| 3: 90103 | Image | Descender: Machine Moon (2016)                            |
| 4: 102251 | Image | Descender: Orbital Mechanics (2017)                      |
| 5: 107643 | Image | Descender: Rise of the Robots (2018)                     |
| 6: 96656 | Image | Descender: Singularities (2016)                           |
| 7: 113701 | Image | Descender: The Machine War (2018)                        |
| 8: 84421 | Image | Descender: Tin Stars (2015)                               |
| 9: 86365 | Image | Image Firsts: Descender (2015)                            |
| 10: 105168 | Splitter | Descender (2015)                                     |
| 0: None of the Above                                                         |
+------------------------------------------------------------------------------+
Select (0): 0
Try Again [y/n]: n
╭───────────────────── Traceback (most recent call last) ──────────────────────╮
│ /home/d/Development/Python/Perdoo/perdoo/__main__.py:396 in <module>         │
│                                                                              │
│   393                                                                        │
│   394                                                                        │
│   395 if __name__ == "__main__":                                             │
│ ❱ 396 │   main()                                                             │
│   397                                                                        │
│                                                                              │
│ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
│ │         annotations = _Feature((3, 7, 0, 'beta', 1), None, 16777216)     │ │
│ │  ARCHIVE_EXTENSIONS = ('.cb7', '.cbr', '.cbt', '.cbz')                   │ │
│ │      ArgumentParser = <class 'argparse.ArgumentParser'>                  │ │
│ │         BaseArchive = <class 'perdoo.archives._base.BaseArchive'>        │ │
│ │          CB7Archive = <class 'perdoo.archives.cb7.CB7Archive'>           │ │
│ │          CBTArchive = <class 'perdoo.archives.cbt.CBTArchive'>           │ │
│ │          CBZArchive = <class 'perdoo.archives.cbz.CBZArchive'>           │ │
│ │           ComicInfo = <class 'perdoo.models.comic_info.ComicInfo'>       │ │
│ │           Comicvine = <class 'perdoo.services.comicvine.Comicvine'>      │ │
│ │             CONSOLE = <console width=80 None>                            │ │
│ │  convert_collection = <function convert_collection at 0x7e28f467f380>    │ │
│ │                date = <class 'datetime.date'>                            │ │
│ │             Details = <class 'perdoo.utils.Details'>                     │ │
│ │ fetch_from_services = <function fetch_from_services at 0x7e28f1f2f7e0>   │ │
│ │              Format = <enum 'Format'>                                    │ │
│ │   generate_filename = <function generate_filename at 0x7e28f1cc8040>     │ │
│ │         get_archive = <function get_archive at 0x7e28f3f47600>           │ │
│ │     get_metadata_id = <function get_metadata_id at 0x7e28f286f560>       │ │
│ │     Identifications = <class 'perdoo.utils.Identifications'>             │ │
│ │    IMAGE_EXTENSIONS = ('.jpg', '.jpeg', '.png', '.webp')                 │ │
│ │           InfoModel = <class 'perdoo.models._base.InfoModel'>            │ │
│ │   InformationSource = <enum 'InformationSource'>                         │ │
│ │              League = <class 'perdoo.services.league.League'>            │ │
│ │          list_files = <function list_files at 0x7e28f39eae80>            │ │
│ │       load_archives = <function load_archives at 0x7e28f1f2f740>         │ │
│ │              LOGGER = <Logger perdoo (DEBUG)>                            │ │
│ │             logging = <module 'logging' from                             │ │
│ │                       '/usr/lib/python3.12/logging/__init__.py'>         │ │
│ │                main = <function main at 0x7e28f1b50900>                  │ │
│ │              Marvel = <class 'perdoo.services.marvel.Marvel'>            │ │
│ │                Meta = <class 'perdoo.models.metadata.Meta'>              │ │
│ │            Metadata = <class 'perdoo.models.metadata.Metadata'>          │ │
│ │              Metron = <class 'perdoo.services.metron.Metron'>            │ │
│ │          MetronInfo = <class 'perdoo.models.metron_info.MetronInfo'>     │ │
│ │           Namespace = <class 'argparse.Namespace'>                       │ │
│ │        OutputFormat = <enum 'OutputFormat'>                              │ │
│ │     parse_arguments = <function parse_arguments at 0x7e28f4dee200>       │ │
│ │                Path = <class 'pathlib.Path'>                             │ │
│ │       process_pages = <function process_pages at 0x7e28f1b50b80>         │ │
│ │              Prompt = <class 'rich.prompt.Prompt'>                       │ │
│ │      python_version = <function python_version at 0x7e28f4751b20>        │ │
│ │           read_meta = <function read_meta at 0x7e28f2605f80>             │ │
│ │       rename_images = <function rename_images at 0x7e28f1b50ae0>         │ │
│ │            sanitize = <function sanitize at 0x7e28f286f4c0>              │ │
│ │             Service = <enum 'Service'>                                   │ │
│ │            Settings = <class 'perdoo.settings.Settings'>                 │ │
│ │       setup_logging = <function setup_logging at 0x7e28f3b234c0>         │ │
│ │              shutil = <module 'shutil' from                              │ │
│ │                       '/usr/lib/python3.12/shutil.py'>                   │ │
│ │              Source = <enum 'Source'>                                    │ │
│ │               start = <function start at 0x7e28f1b50a40>                 │ │
│ │  TemporaryDirectory = <class 'tempfile.TemporaryDirectory'>              │ │
│ │                Tool = <class 'perdoo.models.metadata.Tool'>              │ │
│ │     ValidationError = <class                                             │ │
│ │                       'pydantic_core._pydantic_core.ValidationError'>    │ │
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
│                                                                              │
│ /home/d/Development/Python/Perdoo/perdoo/__main__.py:390 in main             │
│                                                                              │
│   387 │   │   settings = Settings.load().save()                              │
│   388 │   │   if args.debug:                                                 │
│   389 │   │   │   CONSOLE.print(f"Settings: {settings}")                     │
│ ❱ 390 │   │   start(settings=settings, force=args.force)                     │
│   391 │   except KeyboardInterrupt:                                          │
│   392 │   │   pass                                                           │
│   393                                                                        │
│                                                                              │
│ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
│ │     args = Namespace(force=False, debug=False)                           │ │
│ │ settings = Settings(                                                     │ │
│ │            │   collection_folder=PosixPath('/home/d/Comics'),            │ │
│ │            │   comicvine=Comicvine(                                      │ │
│ │            │   │   api_key='<snip>'    │ │
│ │            │   ),                                                        │ │
│ │            │   league_of_comic_geeks=LeagueofComicGeeks(                 │ │
│ │            │   │   client_id='',                                         │ │
│ │            │   │   client_secret='',                                     │ │
│ │            │   │   access_token=''                                       │ │
│ │            │   ),                                                        │ │
│ │            │   marvel=Marvel(public_key='', private_key=''),             │ │
│ │            │   metron=Metron(password='', username=''),                  │ │
│ │            │   service_order=[                                           │ │
│ │            │   │   <Service.METRON: 'Metron'>,                           │ │
│ │            │   │   <Service.MARVEL: 'Marvel'>,                           │ │
│ │            │   │   <Service.COMICVINE: 'Comicvine'>,                     │ │
│ │            │   │   <Service.LEAGUE_OF_COMIC_GEEKS: 'League of Comic      │ │
│ │            Geeks'>                                                       │ │
│ │            │   ],                                                        │ │
│ │            │   output=Output(                                            │ │
│ │            │   │   create_comic_info=True,                               │ │
│ │            │   │   create_metron_info=True,                              │ │
│ │            │   │   create_metadata=True,                                 │ │
│ │            │   │   format=<OutputFormat.CBZ: 'cbz'>                      │ │
│ │            │   )                                                         │ │
│ │            )                                                             │ │
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
│                                                                              │
│ /home/d/Development/Python/Perdoo/perdoo/__main__.py:321 in start            │
│                                                                              │
│   318 │   │   )                                                              │
│   319 │   │                                                                  │
│   320 │   │   metadata, metron_info, comic_info = fetch_from_services(settin │
│ ❱ 321 │   │   new_file = generate_filename(                                  │
│   322 │   │   │   root=settings.collection_folder,                           │
│   323 │   │   │   extension=settings.output.format.value,                    │
│   324 │   │   │   metadata=metadata,                                         │
│                                                                              │
│ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
│ │     archive = <perdoo.archives.cbz.CBZArchive object at 0x7e28f1b01610>  │ │
│ │    archives = [                                                          │ │
│ │               │   (                                                      │ │
│ │               │   │   PosixPath('/home/d/Comics/Descender Compendium     │ │
│ │               (2024) (Digital).cbz'),               │ │
│ │               │   │   <perdoo.archives.cbz.CBZArchive object at          │ │
│ │               0x7e28f1b01610>,                                           │ │
│ │               │   │   None                                               │ │
│ │               │   ),                                                     │ │
│ │               │   (                                                      │ │
│ │               │   │   PosixPath('/home/d/Comics/Dog Biscuits - Alex      │ │
│ │               Graham.cbz'),                                              │ │
│ │               │   │   <perdoo.archives.cbr.CBRArchive object at          │ │
│ │               0x7e28f1c2f920>,                                           │ │
│ │               │   │   None                                               │ │
│ │               │   ),                                                     │ │
│ │               │   (                                                      │ │
│ │               │   │   PosixPath('/home/d/Comics/Joker-Harley - Criminal  │ │
│ │               Sanity.cbz'),                                              │ │
│ │               │   │   <perdoo.archives.cbz.CBZArchive object at          │ │
│ │               0x7e28f1ec03b0>,                                           │ │
│ │               │   │   None                                               │ │
│ │               │   ),                                                     │ │
│ │               │   (                                                      │ │
│ │               │   │   PosixPath('/home/d/Comics/Memetic (2015) (Digital) │ │
│ │              .cbz'),                                │ │
│ │               │   │   <perdoo.archives.cbz.CBZArchive object at          │ │
│ │               0x7e28f2220410>,                                           │ │
│ │               │   │   None                                               │ │
│ │               │   )                                                      │ │
│ │               ]                                                          │ │
│ │  comic_info = None                                                       │ │
│ │     details = Details(                                                   │ │
│ │               │   series=Identifications(                                │ │
│ │               │   │   search='Descender',                                │ │
│ │               │   │   comicvine=None,                                    │ │
│ │               │   │   league=None,                                       │ │
│ │               │   │   marvel=None,                                       │ │
│ │               │   │   metron=None                                        │ │
│ │               │   ),                                                     │ │
│ │               │   issue=Identifications(                                 │ │
│ │               │   │   search=None,                                       │ │
│ │               │   │   comicvine=None,                                    │ │
│ │               │   │   league=None,                                       │ │
│ │               │   │   marvel=None,                                       │ │
│ │               │   │   metron=None                                        │ │
│ │               │   )                                                      │ │
│ │               )                                                          │ │
│ │        file = PosixPath('/home/d/Comics/Descender Compendium (2024)      │ │
│ │               (Digital).cbz')                       │ │
│ │       force = False                                                      │ │
│ │    metadata = None                                                       │ │
│ │ metron_info = None                                                       │ │
│ │    settings = Settings(                                                  │ │
│ │               │   collection_folder=PosixPath('/home/d/Comics'),         │ │
│ │               │   comicvine=Comicvine(                                   │ │
│ │               │   │   api_key='<snip>' │ │
│ │               │   ),                                                     │ │
│ │               │   league_of_comic_geeks=LeagueofComicGeeks(              │ │
│ │               │   │   client_id='',                                      │ │
│ │               │   │   client_secret='',                                  │ │
│ │               │   │   access_token=''                                    │ │
│ │               │   ),                                                     │ │
│ │               │   marvel=Marvel(public_key='', private_key=''),          │ │
│ │               │   metron=Metron(password='', username=''),               │ │
│ │               │   service_order=[                                        │ │
│ │               │   │   <Service.METRON: 'Metron'>,                        │ │
│ │               │   │   <Service.MARVEL: 'Marvel'>,                        │ │
│ │               │   │   <Service.COMICVINE: 'Comicvine'>,                  │ │
│ │               │   │   <Service.LEAGUE_OF_COMIC_GEEKS: 'League of Comic   │ │
│ │               Geeks'>                                                    │ │
│ │               │   ],                                                     │ │
│ │               │   output=Output(                                         │ │
│ │               │   │   create_comic_info=True,                            │ │
│ │               │   │   create_metron_info=True,                           │ │
│ │               │   │   create_metadata=True,                              │ │
│ │               │   │   format=<OutputFormat.CBZ: 'cbz'>                   │ │
│ │               │   )                                                      │ │
│ │               )                                                          │ │
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
│                                                                              │
│ /home/d/Development/Python/Perdoo/perdoo/__main__.py:227 in                  │
│ generate_filename                                                            │
│                                                                              │
│   224                                                                        │
│   225                                                                        │
│   226 def generate_filename(root: Path, extension: str, metadata: Metadata)  │
│ ❱ 227 │   publisher_filename = metadata.issue.series.publisher.title         │
│   228 │   series_filename = (                                                │
│   229 │   │   f"{metadata.issue.series.title} v{metadata.issue.series.volume │
│   230 │   │   if metadata.issue.series.volume > 1                            │
│                                                                              │
│ ╭──────────────── locals ─────────────────╮                                  │
│ │ extension = 'cbz'                       │                                  │
│ │  metadata = None                        │                                  │
│ │      root = PosixPath('/home/d/Comics') │                                  │
│ ╰─────────────────────────────────────────╯                                  │
╰──────────────────────────────────────────────────────────────────────────────╯
AttributeError: 'NoneType' object has no attribute 'issue'

Process finished with exit code 1

Please let me know if you would like any more information.

Filter Services

  • Allow filtering Marvel service for non-marvel comics
  • Set order of services

Copy/move files from an input directory to an output directory

It should be possible to define an input directory that is different than where the files ultimately get stored with the default being the same as it is now. If both the input and output directories are defined as the same, the current behavior would not change.

One of the eventual goals of this change would be to have the option to leave the input directory untouched and to only copy the files instead of moving/re-arranging the original folder structure.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.