├── .gitattributes ├── pynfogen ├── __init__.py ├── tracks │ ├── __init__.py │ ├── BaseTrack.py │ ├── Subtitle.py │ ├── Audio.py │ └── Video.py ├── helpers.py ├── config.py ├── cli │ ├── config.py │ ├── artwork.py │ ├── template.py │ ├── __init__.py │ └── generate.py ├── formatter.py └── nfo.py ├── .markdownlint.yaml ├── .flake8 ├── .deepsource.toml ├── examples ├── output │ ├── README.md │ ├── Star.vs.the.Forces.of.Evil.S01.1080p.DSNP.WEB-DL.AAC2.0.H.264-PHOENiX.desc.txt │ └── Star.vs.the.Forces.of.Evil.S01.1080p.DSNP.WEB-DL.AAC2.0.H.264-PHOENiX.nfo ├── templates │ ├── movie.txt │ ├── season.txt │ ├── season.nfo │ ├── episode.txt │ ├── movie.nfo │ └── episode.nfo └── art │ ├── LICENSE │ ├── shirt.nfo │ ├── rpg.nfo │ └── phoenix.nfo ├── .vscode └── extensions.json ├── .editorconfig ├── LICENSE ├── .github └── workflows │ ├── cd.yml │ └── ci.yml ├── .pre-commit-config.yaml ├── pyproject.toml ├── CONTRIBUTING.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── README.md ├── CHANGELOG.md └── poetry.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /pynfogen/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.1.1" 2 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | MD013: false 2 | MD024: 3 | siblings_only: true 4 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = 3 | # line break before binary operator (W503) 4 | W503, 5 | exclude = 6 | .venv, 7 | build, 8 | max-line-length=120 9 | -------------------------------------------------------------------------------- /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "python" 5 | enabled = true 6 | 7 | [analyzers.meta] 8 | runtime_version = "3.x.x" 9 | max_line_length = 120 10 | -------------------------------------------------------------------------------- /examples/output/README.md: -------------------------------------------------------------------------------- 1 | # /examples/output 2 | 3 | These are examples of untouched results from *some* version of this project. Even the filenames are untouched! 4 | 5 | Reminder that the artwork shown are copyright and only provided as an example. -------------------------------------------------------------------------------- /pynfogen/tracks/__init__.py: -------------------------------------------------------------------------------- 1 | from pynfogen.tracks import BaseTrack 2 | from pynfogen.tracks.Audio import Audio 3 | from pynfogen.tracks.Subtitle import Subtitle 4 | from pynfogen.tracks.Video import Video 5 | 6 | __ALL__ = (BaseTrack, Video, Audio, Subtitle) 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-python.python", 4 | "ms-python.vscode-pylance", 5 | "ms-python.mypy-type-checker", 6 | "tamasfe.even-better-toml", 7 | "EditorConfig.EditorConfig", 8 | "streetsidesoftware.code-spell-checker" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /pynfogen/helpers.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | import subprocess 4 | 5 | 6 | def open_file(path: str) -> None: 7 | """Open file in file-associated text-editor.""" 8 | if platform.system() == "Windows": 9 | os.startfile(path) 10 | elif platform.system() == "Darwin": 11 | subprocess.run(("open", path), check=True) 12 | else: 13 | # TODO: What about systems that do not use a WM/GUI? 14 | subprocess.run(("xdg-open", path), check=True) 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | # 3 | # All EditorConfig properties: 4 | # https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties 5 | 6 | 7 | # Top-most EditorConfig file 8 | root = true 9 | 10 | [*] 11 | end_of_line = lf 12 | charset = utf-8 13 | insert_final_newline = true 14 | indent_style = space 15 | indent_size = 4 16 | trim_trailing_whitespace = true 17 | 18 | [/*.md] 19 | trim_trailing_whitespace = false 20 | 21 | [/examples/**] 22 | insert_final_newline = false 23 | -------------------------------------------------------------------------------- /pynfogen/config.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import toml 4 | from appdirs import user_data_dir 5 | 6 | 7 | class Directories: 8 | user = Path(user_data_dir("pynfogen", "PHOENiX")) 9 | artwork = user / "artwork" 10 | templates = user / "templates" 11 | 12 | 13 | class Files: 14 | config = Directories.user / "config.toml" 15 | artwork = Directories.artwork / "{name}.nfo" 16 | template = Directories.templates / "{name}.nfo" 17 | description = Directories.templates / "{name}.txt" 18 | 19 | 20 | if Files.config.exists(): 21 | config = toml.load(Files.config) 22 | else: 23 | config = {} 24 | 25 | 26 | __ALL__ = (config, Directories, Files) 27 | -------------------------------------------------------------------------------- /examples/templates/movie.txt: -------------------------------------------------------------------------------- 1 | [align=center] 6 | {preview_images:bbimg:layout,2x2x0} 7 | [/align] 8 | 9 | [HR][/HR] 10 | >0x100} 12 | 13 | [HR][/HR] 14 | ?> 15 | Release: {file.stem} 16 | Title: {imdb[title]} 17 | Type: Movie ({imdb[year]}) 18 | IMDb: https://imdb.com/title/tt{imdb.movieID} 19 | TMDB: https://themoviedb.org/{tmdb} 20 | Preview: {preview} 21 | Chapters: {chapters_yes_no}>0x100}?> 23 | 24 | [HR][/HR] 25 | 26 | Video Tracks ({videos:len}): 27 | {videos_pretty:>>0x68} 28 | 29 | Audio Tracks ({audio:len}): 30 | {audio_pretty:>>0x68} 31 | 32 | Subtitle Tracks ({subtitles:len}): 33 | {subtitles_pretty:>>0x68} 34 | -------------------------------------------------------------------------------- /examples/templates/season.txt: -------------------------------------------------------------------------------- 1 | [align=center] 6 | {preview_images:bbimg:layout,2x2x0} 7 | [/align] 8 | 9 | [HR][/HR] 10 | >0x100} 12 | 13 | [HR][/HR] 14 | ?> 15 | Release: {file.parent.name} 16 | Title: {imdb[title]} 17 | Type: TV ({imdb[series years]}) 18 | Season: {season} ({episodes} Episodes) 19 | IMDb: https://imdb.com/title/tt{imdb.movieID} 20 | TMDB: https://themoviedb.org/{tmdb} 21 | TVDB: https://thetvdb.com/?tab=series&id={tvdb} 22 | Preview: {preview} 23 | Chapters: {chapters_yes_no}>0x100}?> 25 | 26 | [HR][/HR] 27 | 28 | Video Tracks ({videos:len}): 29 | {videos_pretty:>>0x68} 30 | 31 | Audio Tracks ({audio:len}): 32 | {audio_pretty:>>0x68} 33 | 34 | Subtitle Tracks ({subtitles:len}): 35 | {subtitles_pretty:>>0x68} -------------------------------------------------------------------------------- /examples/art/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020-2021 rlaphoenix 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of these artwork files, to view and study only, subject to the following 5 | conditions: 6 | 7 | These artwork files cannot be copied, modified, merged, published, 8 | distributed, sub-licensed, and/or sold. 9 | 10 | These artwork files must not be part of derivative work. 11 | 12 | THE ARTWORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | OUT OF OR IN CONNECTION WITH THE ARTWORK OR THE USE OR OTHER DEALINGS IN THE 18 | ARTWORK. 19 | -------------------------------------------------------------------------------- /examples/templates/season.nfo: -------------------------------------------------------------------------------- 1 | {file.parent.name:>>2x68} 2 | 3 | Title : {imdb[title]} 4 | Type : TV ({imdb[series years]}) 5 | Season : {season} ({episodes} Episodes) 6 | IMDb : https://imdb.com/title/tt{imdb.movieID} 7 | TMDB : https://themoviedb.org/{tmdb} 8 | TVDB : https://thetvdb.com/?tab=series&id={tvdb} 9 | Preview : {preview} 10 | Chapters : {chapters_yes_no} 11 | >2x65} 14 | ?>>2x65} 17 | ?> 18 | ──┤ Video ├─────────────────────────────────────────────[ {videos:len:0>2} ]── 19 | 20 | {videos_pretty:>>2x65} 21 | 22 | ──┤ Audio ├─────────────────────────────────────────────[ {audio:len:0>2} ]── 23 | 24 | {audio_pretty:>>2x65} 25 | 26 | ──┤ Subtitles ├────────────────────────────────────────[ {subtitles:len:0>2} ]── 27 | 28 | {subtitles_pretty:>>2x65} -------------------------------------------------------------------------------- /examples/templates/episode.txt: -------------------------------------------------------------------------------- 1 | [align=center] 6 | {preview_images:bbimg:layout,2x2x0} 7 | [/align] 8 | 9 | [HR][/HR] 10 | >0x100} 12 | 13 | [HR][/HR] 14 | ?> 15 | Release: {file.stem} 16 | Title: {imdb[title]} 17 | Type: TV ({imdb[series years]}) 18 | Episode: {season}x{episode} "{episode_name}" 19 | IMDb: https://imdb.com/title/tt{imdb.movieID} 20 | TMDB: https://themoviedb.org/{tmdb} 21 | TVDB: https://thetvdb.com/?tab=series&id={tvdb} 22 | Preview: {preview} 23 | Chapters: {chapters_yes_no}>0x100}?> 25 | 26 | [HR][/HR] 27 | 28 | Video Tracks ({videos:len}): 29 | {videos_pretty:>>0x68} 30 | 31 | Audio Tracks ({audio:len}): 32 | {audio_pretty:>>0x68} 33 | 34 | Subtitle Tracks ({subtitles:len}): 35 | {subtitles_pretty:>>0x68}>0x68}?> -------------------------------------------------------------------------------- /examples/templates/movie.nfo: -------------------------------------------------------------------------------- 1 | {file.stem:>>2x68} 2 | 3 | Title : {imdb[title]} 4 | Type : Movie ({imdb[year]}) 5 | IMDb : https://imdb.com/title/tt{imdb.movieID} 6 | TMDB : https://themoviedb.org/{tmdb} 7 | Preview : {preview} 8 | Chapters : {chapters_yes_no} 9 | >2x65} 12 | ?>>2x65} 15 | ?> 16 | ──┤ Video ├─────────────────────────────────────────────[ {videos:len:0>2} ]── 17 | 18 | {videos_pretty:>>2x65} 19 | 20 | ──┤ Audio ├─────────────────────────────────────────────[ {audio:len:0>2} ]── 21 | 22 | {audio_pretty:>>2x65} 23 | 24 | ──┤ Subtitles ├────────────────────────────────────────[ {subtitles:len:0>2} ]── 25 | 26 | {subtitles_pretty:>>2x65}2} ]── 29 | 30 | {chapter_entries:>>2x65}?> 31 | -------------------------------------------------------------------------------- /examples/templates/episode.nfo: -------------------------------------------------------------------------------- 1 | {file.stem:>>2x68} 2 | 3 | Title : {imdb[title]} 4 | Type : TV ({imdb[series years]}) 5 | Episode : {season}x{episode} "{episode_name}" 6 | IMDb : https://imdb.com/title/tt{imdb.movieID} 7 | TMDB : https://themoviedb.org/{tmdb} 8 | TVDB : https://thetvdb.com/?tab=series&id={tvdb} 9 | Preview : {preview} 10 | Chapters : {chapters_yes_no} 11 | >2x65} 14 | ?>>2x65} 17 | ?> 18 | ──┤ Video ├─────────────────────────────────────────────[ {videos:len:0>2} ]── 19 | 20 | {videos_pretty:>>2x65} 21 | 22 | ──┤ Audio ├─────────────────────────────────────────────[ {audio:len:0>2} ]── 23 | 24 | {audio_pretty:>>2x65} 25 | 26 | ──┤ Subtitles ├────────────────────────────────────────[ {subtitles:len:0>2} ]── 27 | 28 | {subtitles_pretty:>>2x65}2} ]── 31 | 32 | {chapter_entries:>>2x65}?> -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2021 rlaphoenix 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: cd 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | tagged-release: 10 | name: Tagged Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Set up Python 15 | uses: actions/setup-python@v4 16 | with: 17 | python-version: '3.10.x' 18 | - name: Install Poetry 19 | uses: abatilo/actions-poetry@v2 20 | with: 21 | poetry-version: '1.4.1' 22 | - name: Configure poetry 23 | run: poetry config virtualenvs.in-project true 24 | - name: Install project 25 | run: poetry install --no-dev 26 | - name: Build a wheel 27 | run: poetry build 28 | - name: Upload wheel 29 | uses: actions/upload-artifact@v3 30 | with: 31 | name: Python Wheel 32 | path: "dist/*.whl" 33 | - name: Deploy release 34 | uses: marvinpinto/action-automatic-releases@latest 35 | with: 36 | prerelease: false 37 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 38 | files: | 39 | dist/*.whl 40 | - name: Publish to PyPI 41 | env: 42 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }} 43 | run: poetry publish 44 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] 16 | poetry-version: [1.4.1] 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Set up Python ${{ matrix.python-version }} 21 | uses: actions/setup-python@v4 22 | with: 23 | python-version: ${{ matrix.python-version }} 24 | - name: Install poetry 25 | uses: abatilo/actions-poetry@v2 26 | with: 27 | poetry-version: ${{ matrix.poetry-version }} 28 | - name: Install project 29 | run: | 30 | poetry install --no-dev 31 | python -m pip install flake8 isort 32 | - name: Lint with flake8 33 | run: | 34 | # stop the build if there are Python syntax errors or undefined names 35 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 36 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 37 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 38 | - name: Check import order with isort 39 | run: isort --check-only --diff . 40 | - name: Build project 41 | run: poetry build 42 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pycqa/isort 3 | rev: 5.11.5 4 | hooks: 5 | - id: isort 6 | - repo: https://github.com/pycqa/flake8 7 | rev: 5.0.4 8 | hooks: 9 | - id: flake8 10 | args: ["--ignore=W503"] 11 | - repo: https://github.com/pre-commit/pre-commit-hooks 12 | rev: v4.4.0 13 | hooks: 14 | - id: check-added-large-files # Block large files from being committed 15 | args: ["--maxkb=1000"] # - >=1MB files are large 16 | - id: debug-statements # Block debugger imports and breakpoint calls 17 | - id: check-yaml # Verify syntax of all YAML files 18 | - id: check-toml # Verify syntax of all TOML files 19 | - id: trailing-whitespace # Trim trailing whitespace 20 | args: [--markdown-linebreak-ext=md] # - except hard linebreaks in markdown files 21 | - id: check-merge-conflict # Check for files that contain merge conflict strings 22 | - id: end-of-file-fixer # Make sure files end in a newline and only a newline 23 | - id: fix-encoding-pragma # Remove the coding pragma: # -*- coding: utf-8 -*- 24 | args: ["--remove"] 25 | - id: mixed-line-ending # Replace or check mixed line ending 26 | args: ["--fix=lf"] 27 | -------------------------------------------------------------------------------- /examples/output/Star.vs.the.Forces.of.Evil.S01.1080p.DSNP.WEB-DL.AAC2.0.H.264-PHOENiX.desc.txt: -------------------------------------------------------------------------------- 1 | [align=center] 2 | [URL=https://imgbox.com/LI1dS7sI][IMG]https://thumbs2.imgbox.com/c4/10/LI1dS7sI_t.png[/IMG][/URL][URL=https://imgbox.com/lrmf3e9R][IMG]https://thumbs2.imgbox.com/0e/6b/lrmf3e9R_t.png[/IMG][/URL] 3 | [URL=https://imgbox.com/N3S9mpGf][IMG]https://thumbs2.imgbox.com/4d/d8/N3S9mpGf_t.png[/IMG][/URL][URL=https://imgbox.com/0lz5EEGg][IMG]https://thumbs2.imgbox.com/77/21/0lz5EEGg_t.png[/IMG][/URL] 4 | [/align] 5 | 6 | [HR][/HR] 7 | 8 | Release: Star.vs.the.Forces.of.Evil.S01.1080p.DSNP.WEB-DL.AAC2.0.H.264-PHOENiX 9 | Title: Star vs. the Forces of Evil 10 | Type: TV (2015–2019) 11 | Season: 1 (24 Episodes) 12 | IMDb: https://imdb.com/title/tt2758770 13 | TMDB: https://themoviedb.org/tv/61923 14 | TVDB: https://thetvdb.com/?tab=series&id=282994 15 | Preview: https://imgbox.com/g/Ca6iCq7l7f 16 | Chapters: Yes (Named) 17 | Source: Disney+ 18 | 19 | [HR][/HR] 20 | 21 | Video Tracks (1): 22 | - English, AVC (High@L4) 1920x1080 (16:9) @ 8 713 kb/s (VBR) 23 | 24000/1001 FPS (CFR), YUV420P8, Progressive 24 | 25 | Audio Tracks (1): 26 | - English, AAC 2.0 @ 125 kb/s 27 | 28 | Subtitle Tracks (3): 29 | - English, SDH, SubRip (SRT) 30 | - Finnish, SubRip (SRT) 31 | - Chinese, Traditional, Hong Kong, SubRip (SRT) 32 | 33 | [HR][/HR] 34 | 35 | I sincerely hope you enjoy this release! 36 | Greetz: -RPG -MKVULTRA -FraMeSToR -BHDStudio -------------------------------------------------------------------------------- /examples/art/shirt.nfo: -------------------------------------------------------------------------------- 1 | :++++++++++++. 2 | .ooooohmmmmmmmmmmmmyooooo 3 | +ooooooymmmmmmmmmmmmmmmmmmmmmmmmooooooo: 4 | /sssdmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmhsss. 5 | -yyydmmmmm-:mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmy-+mmmmmhyyy 6 | ```:mmmmmmmmm `mmmmmo ymmmmmmmmmmmmm dmmmmy :mmmmmmmmm`` 7 | ```` ``ommm:```hhmy nhhhdmmmmh` mmmmmdhhhn mdho```smmm-` `` 8 | ` `````` ` ``` mmmmddddmmmmmmmmmddmmmmmmmmmddddmmmo ``` `` ` 9 | ``mmddddmmmmmmmdd :ddmmmmmmmddddms` 10 | hmdh oddmmmmm` +mmmmmdd +ddm+ 11 | `......... hm smmmmm` +mmmmm+ om+ 12 | .mmmmmmmmh-----` hmo+ oymmmmm` :yyydmoo oym+ 13 | .mmmmmmmmmmmmmmo:` hmmdoooymmmmmmm/: . . osdmooosmmm+ 14 | .mmmmmmmmmmmmmmmm+:oommmmmmmmmmmmmmm:::::- +mmm+ oommmmmho: 15 | .mmmmmmmmmmmmmmmmmm` mmmmmmmmmmmmmmmmmmmmy +ohmmm+ ommmo 16 | :++++++++mmmmmmmmmm` +ommmmmmmmmmmmmmmmmmmmy ++hmmm++ ++: 17 | //smmmmmmm` oommmmmmmmmmmhh::::::hdm: ymmmmh o: 18 | :ymmm` mmmmmmms:ymmmddyymmyhddmyo :dmmdos o 19 | -ssshm+: mmmmmmm+ ommmmmddddddmmmmm- hmmmmmmo 20 | yyyhmo---` symmmmmmm+ ommmmmmmmmmmmmmmmhydmmmmmmdy: 21 | shhhmy...` hmmd /mmmdh .ymmmmmmmmmmmmmmmmmmmmmmmmm+ 22 | ```-mdhhh/ hmmh -mmmmm: ymmmmmmmmmmmmmmmmmmmmmmmmm+ 23 | ```/mdddd- hmmmds /mmm: ymmmmmmmmmmmmmmmmmmmmmmmmm+ 24 | 25 | {nfo} -------------------------------------------------------------------------------- /examples/art/rpg.nfo: -------------------------------------------------------------------------------- 1 | `````````````````````````````````````````````````````````````````````` 2 | `````````````````````````````````````````````````````````````````````` 3 | ````````````-ddddddddddddd/`ddddddddddddds````:dddddddddd````````````` 4 | ````````````:MMMNNNNNNNMMM+`NMMNNNNNNNMMMh`.--/NNNNNNNNNm````````````` 5 | ````````````:MMM:``````MMM+`NMMo``````hMMh`yMMd``````````````````````` 6 | ````````````:MMMs++++++yyy:`NMMh++++++mMMh`yMMd``````````````````````` 7 | ````````````:MMMMMMMMMM`````NMMMMMMMMMMMMh`yMMd``````````````````````` 8 | ````````````:MMMMMMh///`````NMMy/////////:`yMMd```yhhhhhh````````````` 9 | ````````````:MMMMMMy````````NMMo```````````yMMd```mMMMMMM````````````` 10 | ````````````:MMM:``oMMM`````NMMo``-MMM/````yMMd``````oMMM````````````` 11 | ````````````:MMM:``/hhh///-`NMMo``.hhh:````ohhy//////ohhh````````````` 12 | ````````````:MMM:``````MMM+`NMMo``````````````:MMMMMMs```````````````` 13 | ````````````.ooo.``````ooo-`+oo:``````````````-ooo+++:```````````````` 14 | `````````````````````````````````````````````````````````````````````` 15 | ``````````````````````` RETRO PRODUCTION GROUP ``````````````````````` 16 | `````````````````````````` PROUDLY PRESENTS `````````````````````````` 17 | `````````````````````````````````````````````````````````````````````` 18 | `````````````````````````````````````````````````````````````````````` 19 | 20 | {nfo} 21 | 22 | RPG is currently looking for DVD REMUXers and DVD suppliers. 23 | Get in contact if you feel you have something to offer. 24 | 25 | `````````````````````````````````````````````````````````````````````` 26 | `````````````````````````````````````````````````````````````````````` -------------------------------------------------------------------------------- /pynfogen/cli/config.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Optional 3 | 4 | import click 5 | import toml 6 | 7 | from pynfogen.config import Files 8 | from pynfogen.config import config as data 9 | 10 | 11 | @click.command() 12 | @click.argument("key", type=str, required=False) 13 | @click.argument("value", type=str, required=False) 14 | @click.option("--unset", is_flag=True, default=False, help="Unset/remove the configuration value.") 15 | @click.option("--list", "list_", is_flag=True, default=False, help="List all set configuration values.") 16 | @click.pass_context 17 | def config(ctx: click.Context, key: Optional[str], value: Optional[str], unset: bool, list_: bool) -> None: 18 | """Manage configuration.""" 19 | if not key and not value and not list_: 20 | return click.echo(config.get_help(ctx)) 21 | 22 | log = logging.getLogger("config") 23 | 24 | if list_: 25 | print(toml.dumps(data).rstrip()) 26 | return 27 | 28 | tree = key.split(".") 29 | temp = data 30 | for t in tree[:-1]: 31 | if temp.get(t) is None: 32 | temp[t] = {} 33 | temp = temp[t] 34 | 35 | if unset: 36 | if tree[-1] in temp: 37 | del temp[tree[-1]] 38 | log.info(f"Unset {key}") 39 | else: 40 | if value is None: 41 | if tree[-1] not in temp: 42 | raise click.ClickException(f"Key {key} does not exist in the config.") 43 | print(f"{key}: {temp[tree[-1]]}") 44 | else: 45 | temp[tree[-1]] = value 46 | log.info(f"Set {key} to {repr(value)}") 47 | Files.config.parent.mkdir(parents=True, exist_ok=True) 48 | toml.dump(data, Files.config) 49 | -------------------------------------------------------------------------------- /pynfogen/tracks/BaseTrack.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from collections import defaultdict 4 | from pathlib import Path 5 | from typing import Any, Optional 6 | 7 | import pymediainfo 8 | from langcodes import Language 9 | 10 | 11 | class BaseTrack: 12 | """Track to aide in overriding properties of a PyMediaInfo Track instance.""" 13 | def __init__(self, track: pymediainfo.Track, path: Path): 14 | self._x = track 15 | self._path = path 16 | # common shorthands 17 | self.bitrate = self._x.other_bit_rate[0] 18 | 19 | def __getattr__(self, name: str) -> Any: 20 | return getattr(self._x, name) 21 | 22 | @property 23 | def all_properties(self) -> defaultdict[str, Any]: 24 | """Get all non-callable attributes from this and it's sub-track object.""" 25 | props = defaultdict(lambda: None) 26 | 27 | for obj in (self, self._x): 28 | for k, v in obj.__dict__.items(): 29 | if k in ("_x", "_path"): 30 | continue 31 | props[k] = v 32 | 33 | for subclass in (BaseTrack, self.__class__): 34 | for k, v in vars(subclass).items(): 35 | if not isinstance(v, property): 36 | continue 37 | if k in ("all_properties",): 38 | continue 39 | props[k] = getattr(self, k) 40 | 41 | return props 42 | 43 | @property 44 | def language(self) -> Optional[str]: 45 | """ 46 | Override MediaInfo language with English Display Name. 47 | Returns None if no language is specified. 48 | """ 49 | if self._x.language and self._x.language != "und": 50 | return Language.get(self._x.language).display_name("en") 51 | return None 52 | -------------------------------------------------------------------------------- /pynfogen/cli/artwork.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from pathlib import Path 3 | 4 | import click 5 | 6 | from pynfogen.config import Directories, Files 7 | from pynfogen.helpers import open_file 8 | 9 | 10 | @click.group() 11 | def artwork() -> None: 12 | """Manages artwork files.""" 13 | 14 | 15 | @artwork.command() 16 | @click.argument("name", type=str) 17 | def edit(name: str) -> None: 18 | """Edit an artwork file. If one does not exist, one will be created.""" 19 | log = logging.getLogger("artwork") 20 | location = Path(str(Files.artwork).format(name=name)) 21 | if not location.exists(): 22 | log.info(f"Creating new artwork named {name}") 23 | location.parent.mkdir(exist_ok=True, parents=True) 24 | location.open("a").close() 25 | else: 26 | log.info(f"Opening existing artwork {name} for editing") 27 | open_file(str(location)) 28 | 29 | 30 | @artwork.command() 31 | @click.argument("name", type=str) 32 | @click.confirmation_option(prompt="Are you sure you want to delete the artwork?") 33 | def delete(name: str) -> None: 34 | """Delete an artwork file.""" 35 | log = logging.getLogger("artwork") 36 | location = Path(str(Files.artwork).format(name=name)) 37 | if not location.exists(): 38 | raise click.ClickException(f"Artwork {name} does not exist.") 39 | location.unlink() 40 | log.info(f"Artwork {name} has been deleted.") 41 | 42 | 43 | @artwork.command(name="list") 44 | def list_() -> None: 45 | """List all available artworks.""" 46 | found = 0 47 | for nfo in Directories.artwork.glob("*.nfo"): 48 | print(nfo.stem) 49 | found += 1 50 | if not found: 51 | raise click.ClickException("No artworks found.") 52 | 53 | 54 | @artwork.command() 55 | def explore() -> None: 56 | """Open the artwork directory in your File Explorer.""" 57 | open_file(str(Directories.artwork)) 58 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["poetry-core>=1.0.0"] 3 | build-backend = "poetry.core.masonry.api" 4 | 5 | [tool.poetry] 6 | name = "pynfogen" 7 | version = "1.1.2" 8 | description = "Scriptable MediaInfo-fed NFO Generator for Movies and TV" 9 | license = "MIT" 10 | authors = ["PHOENiX "] 11 | readme = "README.md" 12 | homepage = "https://github.com/rlaphoenix/pynfogen" 13 | repository = "https://github.com/rlaphoenix/pynfogen" 14 | documentation = "https://github.com/rlaphoenix/pynfogen" 15 | keywords = ["python", "nfo", "generator", "scriptable"] 16 | classifiers = [ 17 | "Development Status :: 4 - Beta", 18 | "Environment :: Console", 19 | "Intended Audience :: End Users/Desktop", 20 | "Natural Language :: English", 21 | "Operating System :: OS Independent", 22 | "Topic :: Documentation", 23 | "Topic :: Multimedia", 24 | "Topic :: Multimedia :: Video", 25 | ] 26 | include = [ 27 | { path = "CHANGELOG.md", format = "sdist" }, 28 | { path = "LICENSE", format = "sdist" }, 29 | ] 30 | 31 | [tool.poetry.dependencies] 32 | python = ">=3.7,<4.0" 33 | pymediainfo = "^6.0.1" 34 | pyd2v = "^1.3.1" 35 | requests = "^2.31.0" 36 | appdirs = "^1.4.4" 37 | click = "^8.1.7" 38 | dunamai = "^1.19.0" 39 | Unidecode = "^1.3.7" 40 | langcodes = { extras = ["data"], version = "^3.3.0" } 41 | jsonpickle = "^3.0.2" 42 | tldextract = "^3.6.0" 43 | click-default-group = "^1.2.4" 44 | cinemagoer = "^2023.5.1" 45 | 46 | [tool.poetry.dev-dependencies] 47 | mypy = "^1.4.1" 48 | types-requests = "^2.31.0.7" 49 | flake8 = "^5.0.4" 50 | isort = "^5.11.5" 51 | pre-commit = "^2.21.0" 52 | 53 | [tool.poetry.scripts] 54 | nfo = "pynfogen.cli:cli" 55 | 56 | [tool.isort] 57 | line_length = 118 # 120-2 as I don't like imports right at the edge 58 | 59 | [tool.mypy] 60 | check_untyped_defs = true 61 | disallow_incomplete_defs = true 62 | disallow_untyped_defs = true 63 | ignore_missing_imports = true 64 | no_implicit_optional = true 65 | -------------------------------------------------------------------------------- /pynfogen/tracks/Subtitle.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pathlib import Path 4 | 5 | import pymediainfo 6 | 7 | from pynfogen.tracks.BaseTrack import BaseTrack 8 | 9 | 10 | class Subtitle(BaseTrack): 11 | def __init__(self, track: pymediainfo.Track, path: Path): 12 | super().__init__(track, path) 13 | 14 | @property 15 | def codec(self) -> str: 16 | """ 17 | Get track codec in common P2P simplified form. 18 | E.g., 'SubRip (SRT)' instead of 'UTF-8'. 19 | """ 20 | return { 21 | "UTF-8": "SubRip (SRT)", 22 | }.get(self._x.format, self._x.format) 23 | 24 | @property 25 | def title(self) -> str: 26 | """ 27 | Get track title in it's simplest form. 28 | 29 | Supports the following different language and title scenarios. 30 | The third scenario is the recommended option to choose if you are open to choosing any, 31 | but the fourth scenario should be used if you have nothing unique to state about the track. 32 | 33 | | Language | Track Title | Output | 34 | | ------------ | ----------------------------- | ----------------------------- | 35 | | es / Spanish | Spanish | Spanish | 36 | | es / Spanish | Spanish (Latin American, SDH) | Spanish (Latin American, SDH) | 37 | | es / Spanish | Latin American (SDH) | Spanish, Latin American (SDH) | 38 | | es / Spanish | None | Spanish | 39 | """ 40 | # TODO: Exclude Language from all Title scenarios. 41 | # Only return the title/tags. 42 | if not self._x.title: 43 | return self.language 44 | if not self.language: 45 | return self._x.title 46 | if self.language.lower() in self._x.title.lower(): 47 | return self._x.title 48 | return f"{self.language}, {self._x.title}" 49 | -------------------------------------------------------------------------------- /pynfogen/tracks/Audio.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pathlib import Path 4 | from typing import Optional 5 | 6 | import pymediainfo 7 | 8 | from pynfogen.tracks.BaseTrack import BaseTrack 9 | 10 | 11 | class Audio(BaseTrack): 12 | AUDIO_CHANNEL_LAYOUT_WEIGHT = { 13 | "LFE": 0.1 14 | } 15 | 16 | def __init__(self, track: pymediainfo.Track, path: Path): 17 | super().__init__(track, path) 18 | 19 | @property 20 | def codec(self) -> str: 21 | """ 22 | Get track codec in common P2P simplified form. 23 | E.g., 'DD+' instead of 'E-AC-3'. 24 | """ 25 | return { 26 | "E-AC-3": "DD+", 27 | "AC-3": "DD" 28 | }.get(self._x.format, self._x.format) 29 | 30 | @property 31 | def channels(self) -> float: 32 | """Get track channels as channel layout representation.""" 33 | if self._x.channel_layout: 34 | return float(sum( 35 | self.AUDIO_CHANNEL_LAYOUT_WEIGHT.get(x, 1) 36 | for x in self._x.channel_layout.split(" ") 37 | )) 38 | return float(self._x.channel_s) 39 | 40 | @property 41 | def title(self) -> Optional[str]: 42 | """ 43 | Get track title in it's simplest form. 44 | Returns None if the title is just stating the Language, Audio Codec, or Channels. 45 | """ 46 | if not self._x.title or any(str(x) in self._x.title.lower() for x in ( 47 | self.language.lower(), # Language Display Name (e.g. Spanish) 48 | self._x.language.lower(), # Language Code (e.g. und, or es) 49 | self._x.format.lower(), # Codec (e.g. E-AC-3) 50 | self._x.format.replace("-", "").lower(), # Alphanumeric Codec (e.g. EAC3) 51 | self.codec.lower(), # Simplified Codec (e.g. DD+) 52 | self.codec.replace("-", "").replace("+", "P").lower(), # Alphanumeric Simplified Codec (e.g. DDP) 53 | "stereo", 54 | "surround", 55 | self.channels, # Channel Layout Float Representation 56 | )): 57 | return None 58 | 59 | return self._x.title 60 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Development 2 | 3 | This project is managed using [Poetry](https://python-poetry.org), a fantastic Python packaging and dependency manager. 4 | Install the latest version of Poetry before continuing. Development requires Python 3.7+. 5 | 6 | ## Set up 7 | 8 | Starting from Zero? Not sure where to begin? Here's steps on setting up this Python project using Poetry. Note that 9 | Poetry installation instructions should be followed from the Poetry Docs: https://python-poetry.org/docs/#installation 10 | 11 | 1. While optional, It's recommended to configure Poetry to install Virtual environments within project folders: 12 | ```shell 13 | poetry config virtualenvs.in-project true 14 | ``` 15 | This makes it easier for Visual Studio Code to detect the Virtual Environment, as well as other IDEs and systems. 16 | I've also had issues with Poetry creating duplicate Virtual environments in the default folder for an unknown 17 | reason which quickly filled up my System storage. 18 | 2. Clone the Repository: 19 | ```shell 20 | git clone https://github.com/rlaphoenix/pynfogen 21 | cd pynfogen 22 | ``` 23 | 3. Install the Project with Poetry: 24 | ```shell 25 | poetry install 26 | ``` 27 | This creates a Virtual environment and then installs all project dependencies and executables into the Virtual 28 | environment. Your System Python environment is not affected at all. 29 | 4. Now activate the Virtual environment: 30 | ```shell 31 | poetry shell 32 | ``` 33 | Note: 34 | - You can alternatively just prefix `poetry run` to any command you wish to run under the Virtual environment. 35 | - I recommend entering the Virtual environment and all further instructions will have assumed you did. 36 | - JetBrains PyCharm has integrated support for Poetry and automatically enters Poetry Virtual environments, assuming 37 | the Python Interpreter on the bottom right is set up correctly. 38 | - For more information, see: https://python-poetry.org/docs/basic-usage/#using-your-virtual-environment 39 | 5. Install Pre-commit tooling to ensure safe and quality commits: 40 | ```shell 41 | pre-commit install 42 | ``` 43 | -------------------------------------------------------------------------------- /examples/art/phoenix.nfo: -------------------------------------------------------------------------------- 1 | ``--.` :hMMMs` ..` `.. `sMMMh: `.--`` 2 | .-` :dMMMd- `.......``` ```.......` -dMMMd: `-. 3 | `. .hMMMMs` `.---.` `..---.` `sMMMMh. .` 4 | ` /NMMMM+ ./+. ``...`` ``...`` .+/. +MMMMN/ ` 5 | `sMMMMMdhNMo` `..` `..` `oMNhmMMMMMs` 6 | `hMMMMMMMMd. ..` `.. .dMMMMMMMMh` 7 | dMMMMMMMMh` `.. ..` `hMMMMMMMMd 8 | MMMMMMMMd` `-` `-` `dMMMMMMMM 9 | MMMMMMMM: .. .. :MMMMMMMM 10 | MMMMMMMN `- ` -` NMMMMMMM 11 | MMMMMMMm `- `......`..` -` mMMMMMMM 12 | MMMMMMMN` `- `...`` ./-`-` -` `NMMMMMMM 13 | MMMMMMMM/ .. ` `..` ./sooy+. .. .. /MMMMMMMM 14 | MMMMMMMMm` `-` `--......` .omMMMMh` `..-. `-` `mMMMMMMMM 15 | MMMMMMMMMy `-` `-. -smdMMMMm` `- `` `-` yMMMMMMMMM 16 | MMMMMMMMMMy` `..` ..` .`.mMMMm- .. `..` `yMMMMMMMMMM 17 | MMMMMMMMMMMm: `.......--.` /NMMMMN` ..````.......` :mMMMMMMMMMMM 18 | NMMMMMMMMMMMMd+. `````` .NMMMMMMy` ````````` .+dMMMMMMMMMMMMN 19 | `:ohmMMMMMMMMMMMNhyoo+. /dMMMMMMMMNy/` .+ooyhNMMMMMMMMMMMmho:` 20 | ysssyhNMMMMMMMMMMMMMMMMNdyssyhNMMMMMMMdyssydNMMMMMMMMMMMMMMMMmhysssy 21 | MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM 22 | MMMMMMMMmsdMMMMMMMMMNmmMMMM : PHOENiX : MMMMMmmNMMMMMMMMMdsmMMMMMMMM 23 | MMNmhs/..dMMMMMMh+-``oNMMMMMMMMMMMMMMMMMMMMMMNo``-+hMMMMMMd../shmNMM 24 | `` `sMMMMMMy. /NMMMMmmMMMMMMMMMMMMMMmmMMMMN/ .yMMMMMMs` `` 25 | -sNMMMMMN/ oMMMNs--MMMMMMMMMMMMMMMN--sNMMMo /NMMMMMNs- 26 | `/ymMMMMMMNs` +MMd/` `dh/MMMMMMMMMMMN/hd` `/dMM+ `sNMMMMMMmy/` 27 | -/ooso/- `. `Nm: .` NMMMMMMMMMMN `. :mN` .` -/osoo/- 28 | ..``` ```...- :m` hMMMMMMMMMMh `m: -...``` ```.. 29 | ``....``` -` `- -MMMMMMMMMM- -` `- ```....`` 30 | .. `` +MMMMMMMM+ `` .. 31 | ..` `----.` :NMMMMN: `.---- `.. 32 | `..` ------. ``oMMMMo`` .------ `..` 33 | `..-----.. -MmMMMMmM- ..-----..` 34 | `--.` .+mMMMMMMMMm+. `.--` 35 | 36 | {nfo} -------------------------------------------------------------------------------- /pynfogen/cli/template.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from pathlib import Path 3 | 4 | import click 5 | 6 | from pynfogen.config import Directories, Files 7 | from pynfogen.helpers import open_file 8 | 9 | 10 | @click.group() 11 | def template() -> None: 12 | """Manages template files.""" 13 | 14 | 15 | @template.command() 16 | @click.argument("name", type=str) 17 | @click.option("-d", "--description", is_flag=True, default=False, help="Specify template as a Description template.") 18 | def edit(name: str, description: bool) -> None: 19 | """Edit a template file. If one does not exist, one will be created.""" 20 | log = logging.getLogger("template") 21 | location = Path(str(Files.description if description else Files.template).format(name=name)) 22 | if not location.exists(): 23 | log.info(f"Creating new template named {name}") 24 | location.parent.mkdir(exist_ok=True, parents=True) 25 | location.open("a").close() 26 | else: 27 | log.info(f"Opening existing template {name} for editing") 28 | open_file(str(location)) 29 | 30 | 31 | @template.command() 32 | @click.argument("name", type=str) 33 | @click.option("-d", "--description", is_flag=True, default=False, help="Specify template as a Description template.") 34 | @click.confirmation_option(prompt="Are you sure you want to delete the template?") 35 | def delete(name: str, description: bool) -> None: 36 | """Delete a template file.""" 37 | log = logging.getLogger("template") 38 | location = Path(str(Files.description if description else Files.template).format(name=name)) 39 | if not location.exists(): 40 | raise click.ClickException(f"Template {name} does not exist.") 41 | location.unlink() 42 | log.info(f"Template {name} has been deleted.") 43 | 44 | 45 | @template.command(name="list") 46 | def list_() -> None: 47 | """List all available templates.""" 48 | found = 0 49 | for nfo in Directories.templates.glob("*.nfo"): 50 | print(nfo.stem, "-", "NFO Template") 51 | found += 1 52 | for txt in Directories.templates.glob("*.txt"): 53 | print(txt.stem, "-", "Description Template") 54 | found += 1 55 | if not found: 56 | raise click.ClickException("No templates found.") 57 | 58 | 59 | @template.command() 60 | def explore() -> None: 61 | """Open the template directory in your File Explorer.""" 62 | open_file(str(Directories.templates)) 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # JetBrains project settings 124 | .idea 125 | 126 | # mkdocs documentation 127 | /site 128 | 129 | # mypy 130 | .mypy_cache/ 131 | .dmypy.json 132 | dmypy.json 133 | 134 | # Pyre type checker 135 | .pyre/ 136 | 137 | # pytype static type analyzer 138 | .pytype/ 139 | 140 | # Cython debug symbols 141 | cython_debug/ 142 | -------------------------------------------------------------------------------- /examples/output/Star.vs.the.Forces.of.Evil.S01.1080p.DSNP.WEB-DL.AAC2.0.H.264-PHOENiX.nfo: -------------------------------------------------------------------------------- 1 | ``--.` :hMMMs` ..` `.. `sMMMh: `.--`` 2 | .-` :dMMMd- `.......``` ```.......` -dMMMd: `-. 3 | `. .hMMMMs` `.---.` `..---.` `sMMMMh. .` 4 | ` /NMMMM+ ./+. ``...`` ``...`` .+/. +MMMMN/ ` 5 | `sMMMMMdhNMo` `..` `..` `oMNhmMMMMMs` 6 | `hMMMMMMMMd. ..` `.. .dMMMMMMMMh` 7 | dMMMMMMMMh` `.. ..` `hMMMMMMMMd 8 | MMMMMMMMd` `-` `-` `dMMMMMMMM 9 | MMMMMMMM: .. .. :MMMMMMMM 10 | MMMMMMMN `- ` -` NMMMMMMM 11 | MMMMMMMm `- `......`..` -` mMMMMMMM 12 | MMMMMMMN` `- `...`` ./-`-` -` `NMMMMMMM 13 | MMMMMMMM/ .. ` `..` ./sooy+. .. .. /MMMMMMMM 14 | MMMMMMMMm` `-` `--......` .omMMMMh` `..-. `-` `mMMMMMMMM 15 | MMMMMMMMMy `-` `-. -smdMMMMm` `- `` `-` yMMMMMMMMM 16 | MMMMMMMMMMy` `..` ..` .`.mMMMm- .. `..` `yMMMMMMMMMM 17 | MMMMMMMMMMMm: `.......--.` /NMMMMN` ..````.......` :mMMMMMMMMMMM 18 | NMMMMMMMMMMMMd+. `````` .NMMMMMMy` ````````` .+dMMMMMMMMMMMMN 19 | `:ohmMMMMMMMMMMMNhyoo+. /dMMMMMMMMNy/` .+ooyhNMMMMMMMMMMMmho:` 20 | ysssyhNMMMMMMMMMMMMMMMMNdyssyhNMMMMMMMdyssydNMMMMMMMMMMMMMMMMmhysssy 21 | MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM 22 | MMMMMMMMmsdMMMMMMMMMNmmMMMM : PHOENiX : MMMMMmmNMMMMMMMMMdsmMMMMMMMM 23 | MMNmhs/..dMMMMMMh+-``oNMMMMMMMMMMMMMMMMMMMMMMNo``-+hMMMMMMd../shmNMM 24 | `` `sMMMMMMy. /NMMMMmmMMMMMMMMMMMMMMmmMMMMN/ .yMMMMMMs` `` 25 | -sNMMMMMN/ oMMMNs--MMMMMMMMMMMMMMMN--sNMMMo /NMMMMMNs- 26 | `/ymMMMMMMNs` +MMd/` `dh/MMMMMMMMMMMN/hd` `/dMM+ `sNMMMMMMmy/` 27 | -/ooso/- `. `Nm: .` NMMMMMMMMMMN `. :mN` .` -/osoo/- 28 | ..``` ```...- :m` hMMMMMMMMMMh `m: -...``` ```.. 29 | ``....``` -` `- -MMMMMMMMMM- -` `- ```....`` 30 | .. `` +MMMMMMMM+ `` .. 31 | ..` `----.` :NMMMMN: `.---- `.. 32 | `..` ------. ``oMMMMo`` .------ `..` 33 | `..-----.. -MmMMMMmM- ..-----..` 34 | `--.` .+mMMMMMMMMm+. `.--` 35 | 36 | Star.vs.the.Forces.of.Evil.S01.1080p.DSNP.WEB- 37 | DL.AAC2.0.H.264-PHOENiX 38 | 39 | Title : Star vs. the Forces of Evil 40 | Type : TV (2015–2019) 41 | Season : 1 (24 Episodes) 42 | IMDb : https://imdb.com/title/tt2758770 43 | TMDB : https://themoviedb.org/tv/61923 44 | TVDB : https://thetvdb.com/?tab=series&id=282994 45 | Preview : https://imgbox.com/g/Ca6iCq7l7f 46 | Chapters : Yes (Named) 47 | 48 | Source : 49 | Disney+ 50 | 51 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 52 | 53 | Video : 54 | 55 | - English, AVC (High@L4) 1920x1080 (16:9) @ 8 713 kb/s (VBR) 56 | 24000/1001 FPS (CFR), YUV420P8, Progressive 57 | 58 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 59 | 60 | Audio : 61 | 62 | - English, AAC 2.0 @ 125 kb/s 63 | 64 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 65 | 66 | Subtitles : 67 | 68 | - English, SDH, SubRip (SRT) 69 | - Finnish, SubRip (SRT) 70 | - Chinese, Traditional, Hong Kong, SubRip (SRT) -------------------------------------------------------------------------------- /pynfogen/tracks/Video.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pathlib import Path 4 | 5 | import pymediainfo 6 | from pyd2v import D2V 7 | 8 | from pynfogen.tracks.BaseTrack import BaseTrack 9 | 10 | 11 | class Video(BaseTrack): 12 | DYNAMIC_RANGE_MAP = { 13 | "SMPTE ST 2086": "HDR10", 14 | "HDR10": "HDR10", 15 | "SMPTE ST 2094 App 4": "HDR10+", 16 | "HDR10+": "HDR10+", 17 | "Dolby Vision": "DV" 18 | } 19 | 20 | def __init__(self, track: pymediainfo.Track, path: Path): 21 | super().__init__(track, path) 22 | # quick shorthands 23 | self.profile = self._x.format_profile 24 | self.dar = self._x.other_display_aspect_ratio[0] 25 | if self._x.framerate_num: 26 | self.fps = f"{self._x.framerate_num}/{self._x.framerate_den}" 27 | else: 28 | self.fps = self._x.frame_rate 29 | 30 | @property 31 | def codec(self) -> str: 32 | """ 33 | Get video codec in common P2P simplified form. 34 | E.g., 'MPEG-2' instead of 'MPEG Video, Version 2'. 35 | """ 36 | return { 37 | "MPEG Video": f"MPEG-{(self._x.format_version or '').replace('Version ', '')}" 38 | }.get(self._x.format, self._x.format) 39 | 40 | @property 41 | def range(self) -> str: 42 | """ 43 | Get video range as typical shortname. 44 | Returns multiple ranges in space-separated format if a fallback range is 45 | available. E.g., 'DV HDR10'. 46 | """ 47 | if self._x.hdr_format: 48 | return " ".join([ 49 | self.DYNAMIC_RANGE_MAP.get(x) 50 | for x in self._x.hdr_format.split(" / ") 51 | ]) 52 | elif "HLG" in ((self._x.transfer_characteristics or ""), (self._x.transfer_characteristics_original or "")): 53 | return "HLG" 54 | return "SDR" 55 | 56 | @property 57 | def scan(self) -> str: 58 | """ 59 | Get video scan type in string form. 60 | Will accurately check using DGIndex if codec is MPEG-1/2. 61 | 62 | Examples: 63 | 'Interlaced' 64 | 'Progressive' 65 | When there's information on scan type percentages: 66 | 'Interlaced (CST)' 67 | 'Progressive (CST)' 68 | '99.78% Progressive (VST)' 69 | '0.01% Interlaced (VST)' 70 | """ 71 | scan_type = self._x.scan_type 72 | if not scan_type: 73 | # some videos may not state scan, presume progressive 74 | scan_type = "Progressive" 75 | 76 | if self.codec in ["MPEG-1", "MPEG-2"]: 77 | d2v = D2V.load(self._path) 78 | for ext in ("log", "d2v", "mpg", "mpeg"): 79 | d2v.path.with_suffix(f".{ext}").unlink(missing_ok=True) 80 | 81 | flags = [ 82 | dict(**flag, vob=d["vob"], cell=d["cell"]) 83 | for d in d2v.data 84 | for flag in d["flags"] 85 | ] 86 | progressive_frames = sum(f["progressive_frame"] for f in flags) 87 | progressive_percent = (progressive_frames / len(flags)) * 100 88 | is_constant = progressive_percent in (0.0, 100.0) 89 | 90 | scan_type = ["Interlaced", "Progressive"][progressive_percent >= 50.0] 91 | scan_type += f" ({['VST', 'CST'][is_constant]})" 92 | if not is_constant: 93 | scan_type = f"{progressive_percent:.2f}% {scan_type}" 94 | 95 | return scan_type 96 | -------------------------------------------------------------------------------- /pynfogen/cli/__init__.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | import gzip 3 | from datetime import datetime 4 | from pathlib import Path 5 | 6 | import click 7 | import jsonpickle 8 | import toml 9 | from click_default_group import DefaultGroup 10 | from dunamai import Style, Version 11 | from unidecode import unidecode 12 | 13 | from pynfogen import __version__ 14 | from pynfogen.cli.artwork import artwork 15 | from pynfogen.cli.config import config 16 | from pynfogen.cli.generate import generate 17 | from pynfogen.cli.template import template 18 | from pynfogen.config import Directories, Files 19 | from pynfogen.config import config as config_data 20 | 21 | 22 | @click.group( 23 | cls=DefaultGroup, default="generate", default_if_no_args=True, 24 | context_settings=dict( 25 | help_option_names=["-?", "-h", "--help"], 26 | max_content_width=116 # max PEP8 line-width, -4 to adjust for initial indent 27 | ) 28 | ) 29 | def cli() -> None: 30 | """ 31 | \b 32 | pynfogen 33 | Scriptable MediaInfo-fed NFO Generator for Movies and TV. 34 | https://github.com/rlaphoenix/pynfogen 35 | """ 36 | codecs.register_error("unidecode", lambda e: ( 37 | unidecode( 38 | e.object.decode("utf8") if isinstance(e.object, bytes) else e.object 39 | )[e.start:e.end], 40 | e.end 41 | )) 42 | 43 | 44 | @cli.command() 45 | def about() -> None: 46 | """Shows information about pynfogen.""" 47 | print( 48 | "pynfogen - Python NFO Generator.\n" 49 | "\n" 50 | "pynfogen is a scriptable MediaInfo-fed NFO Generator for Movies and TV.\n" 51 | "See: https://github.com/rlaphoenix/pynfogen for more information." 52 | ) 53 | 54 | 55 | @cli.command() 56 | def version() -> None: 57 | """Shows the version of the project.""" 58 | try: 59 | v = Version.from_git().serialize(style=Style.SemVer) 60 | except RuntimeError: 61 | v = __version__ 62 | print("pynfogen", v) 63 | 64 | 65 | @cli.command() 66 | @click.argument("out_dir", type=Path) 67 | def export(out_dir: Path) -> None: 68 | """Export all configuration, artwork, and templates.""" 69 | if not out_dir or not out_dir.is_dir(): 70 | raise click.ClickException("Save Path must be directory.") 71 | 72 | art = {x.stem: x.read_text(encoding="utf8") for x in Directories.artwork.glob("*.nfo")} 73 | nfo = {x.stem: x.read_text(encoding="utf8") for x in Directories.templates.glob("*.nfo")} 74 | txt = {x.stem: x.read_text(encoding="utf8") for x in Directories.templates.glob("*.txt")} 75 | json = jsonpickle.dumps({ 76 | "version": 1, 77 | "config": config_data, 78 | "art": art, 79 | "nfo": nfo, 80 | "txt": txt 81 | }) 82 | 83 | out_dir.mkdir(parents=True, exist_ok=True) 84 | out_path = out_dir / f"pynfogen.export.{datetime.now().strftime('%Y%m%d-%H%M%S')}.json.gz" 85 | 86 | out_path.write_bytes(gzip.compress(json.encode("utf8"))) 87 | 88 | print(f"Successfully exported to: {out_path}") 89 | 90 | 91 | @cli.command(name="import") 92 | @click.argument("file", type=Path) 93 | def import_(file: Path): 94 | """ 95 | Import all configuration, artwork, and templates from export. 96 | 97 | The configuration will be overwritten in it's entirety. 98 | Current artwork and template files will only be overwritten if 99 | they have the same name. 100 | """ 101 | if not file or not file.exists(): 102 | raise click.ClickException("File path does not exist.") 103 | 104 | decompress = gzip.open(file).read().decode("utf8") 105 | json = jsonpickle.decode(decompress) 106 | 107 | Files.config.parent.mkdir(parents=True, exist_ok=True) 108 | Directories.artwork.mkdir(parents=True, exist_ok=True) 109 | Directories.templates.mkdir(parents=True, exist_ok=True) 110 | 111 | Files.config.write_text(toml.dumps(json["config"])) 112 | print("Imported Configuration") 113 | 114 | for name, data in json["art"].items(): 115 | path = (Directories.artwork / name).with_suffix(".nfo") 116 | path.write_text(data, encoding="utf8") 117 | print(f"Imported Artwork: {name}") 118 | 119 | for name, data in json["nfo"].items(): 120 | path = (Directories.templates / name).with_suffix(".nfo") 121 | path.write_text(data, encoding="utf8") 122 | print(f"Imported NFO Template: {name}") 123 | 124 | for name, data in json["txt"].items(): 125 | path = (Directories.templates / name).with_suffix(".txt") 126 | path.write_text(data, encoding="utf8") 127 | print(f"Imported Description Template: {name}") 128 | 129 | print(f"Successfully Imported from {file}!") 130 | 131 | 132 | command: click.Command 133 | for command in (artwork, config, generate, template): 134 | cli.add_command(command) 135 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available 126 | at [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | -------------------------------------------------------------------------------- /pynfogen/formatter.py: -------------------------------------------------------------------------------- 1 | import re 2 | import textwrap 3 | from string import Formatter 4 | from typing import Any, List, Union 5 | 6 | 7 | class CustomFormats(Formatter): 8 | def __init__(self) -> None: 9 | super().__init__() 10 | self.custom_specs = [ 11 | # function, regex matcher, group casts (optional), return cast (optional) 12 | (self.boolean, r"^(?P!?(?:true|false))$", None, str), 13 | (self.length, "^len$", None, str), 14 | (self.bbimg, "^bbimg$", None, None), 15 | (self.layout, r"^layout,(?P\d+)x(?P\d+)x(?P\d+)$", (int, int, int), None), 16 | (self.wrap, r"^>>(?P\d+)x(?P\d+)$", (int, int), None), 17 | (self.center, r"^\^>(?P\d+)x(?P\d+)$", (int, int), None) 18 | ] 19 | 20 | def chain(self, value: Any, format_spec: str) -> Any: 21 | """Support chaining format specs separated by `:`.""" 22 | for spec in format_spec.split(":"): 23 | value = self.format_field(value, spec) 24 | return value 25 | 26 | @staticmethod 27 | def boolean(value: Any, spec: str) -> int: 28 | """Return evaluated boolean value of input as a bool-int.""" 29 | true = spec in ("true", "!false") 30 | false = spec in ("false", "!true") 31 | if not true and not false: 32 | raise ValueError("spec must be true, !false, false, or !true") 33 | b = bool(value) 34 | if false: 35 | b = not b 36 | return int(b) 37 | 38 | @staticmethod 39 | def length(value: Any) -> int: 40 | """Return object length.""" 41 | return len(value) 42 | 43 | @staticmethod 44 | def bbimg(value: Union[List[Union[dict, str]], Union[dict, str]]) -> Union[List[str], str]: 45 | """ 46 | Convert a list of values into a list of BBCode [LIST][IMG] strings. 47 | If only one item is provided, then a single BBCode string will be provided, not a list. 48 | 49 | Example: 50 | >>> f = CustomFormats() 51 | >>> f.bbimg("https://source.unsplash.com/random") 52 | '[URL=https://source.unsplash.com/random][IMG]https://source.unsplash.com/random[/IMG][/URL]' 53 | >>> f.bbimg({'url': 'https://picsum.photos/id/237/info', 'src': 'https://picsum.photos/id/237/200/300'}) 54 | '[URL=https://picsum.photos/id/237/info][IMG]https://picsum.photos/id/237/200/300[/IMG][/URL]' 55 | >>> f.bbimg([{'url': 'https://foo...', 'src': 'https://bar...'}, 'https://bizz...', ...]) 56 | ['[URL=https://foo...][IMG]https://bar...[/IMG][/URL]', 57 | '[URL=https://bizz...][IMG]https://bizz...[/IMG][/URL]', ...] 58 | """ 59 | if not value: 60 | return "" 61 | if not isinstance(value, list): 62 | value = [value] 63 | images = [ 64 | ({"url": x, "src": x} if not isinstance(x, dict) else x) 65 | for x in value 66 | ] 67 | bb = [f"[URL={x['url']}][IMG]{x['src']}[/IMG][/URL]" for x in images] 68 | if len(bb) == 1: 69 | return bb[0] 70 | return bb 71 | 72 | @staticmethod 73 | def layout(value: Union[List[str], str], width: int, height: int, spacing: int) -> str: 74 | """ 75 | Lay out data in a grid with specific lengths, heights, and spacing. 76 | 77 | Example: 78 | >>> f = CustomFormats() 79 | >>> f.layout(['1', '2', '3', '4'], width=2, height=2, spacing=0) 80 | 12 81 | 34 82 | >>> f.layout(['1', '2', '3', '4'], width=2, height=2, spacing=1) 83 | 1 2 84 | 85 | 3 4 86 | """ 87 | if not value: 88 | return "" 89 | if not isinstance(value, list): 90 | value = [value] 91 | if len(value) != width * height: 92 | # TODO: How about just ignore and try fill as much as it can? 93 | raise ValueError("Layout invalid, not enough images...") 94 | grid = [ 95 | (value[i:i + width]) 96 | for i in range(0, len(value), width) 97 | ] 98 | grid_indented = [(" " * spacing).join(x) for x in grid] 99 | grid_str = ("\n" * (spacing + 1)).join(grid_indented) 100 | return grid_str 101 | 102 | def wrap(self, value: Union[List[str], str], indent: int, width: int) -> str: 103 | """Text-wrap data at a specific width and indent amount.""" 104 | if isinstance(value, list): 105 | return self.list_to_indented_strings(value, indent) 106 | return "\n".join(textwrap.wrap(value or "", width, subsequent_indent=" " * indent)) 107 | 108 | @staticmethod 109 | def center(value: str, center_width: int, wrap_width: int) -> str: 110 | """Center data at a specific width, while also text-wrapping at a specific width.""" 111 | return "\n".join([x.center(center_width) for x in textwrap.wrap(value or "", wrap_width)]) 112 | 113 | def format_field(self, value: Any, format_spec: str) -> str: 114 | """Apply both standard formatters along with custom formatters to value.""" 115 | if ":" in format_spec: 116 | return self.chain(value, format_spec) 117 | for func, match_str, group_casts, return_cast in self.custom_specs: 118 | match = re.match(match_str, format_spec) 119 | if not match: 120 | continue 121 | groups = match.groupdict() # type: dict[str, Any] 122 | if group_casts: 123 | groups = {k: group_casts[i](v) for i, (k, v) in enumerate(groups.items())} 124 | new_value = func(value, **groups) 125 | if return_cast: 126 | new_value = return_cast(new_value) 127 | return new_value 128 | return super().format_field(value, format_spec) 129 | 130 | def list_to_indented_strings(self, value: list, indent: int = 0) -> str: 131 | """Recursively convert a list to an indented \n separated string.""" 132 | if isinstance(value[0], list): 133 | return self.list_to_indented_strings(value[0], indent) 134 | return f"\n{' ' * indent}".join(value) 135 | -------------------------------------------------------------------------------- /pynfogen/cli/generate.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Any, Optional, Union 3 | 4 | import click 5 | from pymediainfo import MediaInfo 6 | 7 | from pynfogen.config import Files, config 8 | from pynfogen.nfo import NFO 9 | 10 | 11 | @click.group(context_settings=dict(default_map=config.get("generate", {}))) 12 | @click.argument("file", type=Path) 13 | @click.argument("imdb", type=str) 14 | @click.option("-tmdb", type=str, default=None, help="TMDB ID (including 'tv/' or 'movie/').") 15 | @click.option("-tvdb", type=int, default=None, help="TVDB ID ('73244' not 'the-office-us').") 16 | @click.option("-a", "--artwork", type=str, default=None, help="Artwork to use.") 17 | @click.option("-s", "--source", type=str, default=None, help="Source information.") 18 | @click.option("-n", "--note", type=str, default=None, help="Notes/special information.") 19 | @click.option("-p", "--preview", type=str, default=None, help="Preview information, typically an URL.") 20 | @click.option("-e", "--encoding", type=str, default="utf8", help="Text-encoding for output, input is always UTF-8.") 21 | def generate(**__: Any) -> None: 22 | """ 23 | Generate an NFO and Description for a release. 24 | 25 | \b 26 | IMDb IDs are required and should include the `tt`. 27 | TMDB and TVDB IDs may optionally be provided. 28 | """ 29 | 30 | 31 | @generate.command(name="season") 32 | @click.argument("season", type=str) 33 | def season_(season: Union[int, str]) -> dict: 34 | """ 35 | Generate an NFO and Description for a season release. 36 | 37 | \b 38 | It's best-practice to provide the first-most file that best represents the majority of the release. 39 | E.g., If Episode 1 and 2 has a fault not found on Episodes 3 onwards, then provide Episode 3. 40 | 41 | The season argument can be a Season Name or a Season Number, it's up to you. You may even provide 42 | a season name that also contains the Season Number e.g. "1: The Beginning" if you prefer. Just 43 | remember that it's up to the template on whether or not the result looks good or not. 44 | """ 45 | if isinstance(season, str) and season.isdigit(): 46 | season = int(season) 47 | return {"season": season} 48 | 49 | 50 | @generate.command(name="episode") 51 | @click.argument("episode", type=int) 52 | @click.argument("title", type=str, default=None) 53 | @click.argument("season", type=str, default=None) 54 | def episode_(episode: int, title: str, season: Union[int, str]) -> dict: 55 | """ 56 | Generate an NFO and Description for a single-episode release. 57 | 58 | The episode title is optional but highly recommended. If there is no episode name like cases with 59 | Daily TV Shows and such, you may want to put the original Air Date as the Episode Name. It's 60 | recommended in such case to use ISO 8601 format; YYYY-MM-DD format. 61 | 62 | The season argument can be a Season Name or a Season Number, it's up to you. You may even provide 63 | a season name that also contains the Season Number e.g. "1: The Beginning" if you prefer. Just 64 | remember that it's up to the template on whether or not the result looks good or not. 65 | """ 66 | if isinstance(season, str) and season.isdigit(): 67 | season = int(season) 68 | return { 69 | "season": season, 70 | "episode": (episode, title or None) 71 | } 72 | 73 | 74 | @generate.command() 75 | def movie() -> dict: 76 | """Generate an NFO and Description for a movie release.""" 77 | return {} 78 | 79 | 80 | @generate.result_callback() 81 | @click.pass_context 82 | def generator(ctx: click.Context, args: dict, file: Path, imdb: str, artwork: Optional[str], 83 | tmdb: Optional[str], tvdb: Optional[int], source: Optional[str], note: Optional[str], 84 | preview: Optional[str], encoding: str, *_: Any, **__: Any) -> None: 85 | if not isinstance(ctx, click.Context) or not ctx.invoked_subcommand: 86 | raise ValueError("Generator called directly, or not used as part of the generate command group.") 87 | if not file.is_file(): 88 | raise click.ClickException("The provided file path is to a folder, not a file.") 89 | if not file.exists(): 90 | raise click.ClickException("The provided file path does not exist.") 91 | 92 | if imdb == "-": 93 | imdb = MediaInfo.parse(file).general_tracks[0].to_data().get("imdb") 94 | if not imdb: 95 | raise ValueError("No IMDB ID was found within the file's metadata.") 96 | 97 | nfo = NFO( 98 | file, 99 | imdb, 100 | **dict( 101 | tmdb=tmdb, 102 | tvdb=tvdb, 103 | source=source, 104 | note=note, 105 | preview=preview, 106 | fanart_api_key=config.get("fanart_api_key"), 107 | **args 108 | ) 109 | ) 110 | 111 | template_vars = { 112 | "videos_pretty": nfo.get_video_print(nfo.videos), 113 | "audio_pretty": nfo.get_audio_print(nfo.audio), 114 | "subtitles_pretty": nfo.get_subtitle_print(nfo.subtitles), 115 | "chapters_yes_no": nfo.get_chapter_print_short(nfo.chapters), 116 | "chapters_named": nfo.chapters and not nfo.chapters_numbered, 117 | "chapter_entries": nfo.get_chapter_print(nfo.chapters) 118 | } 119 | 120 | template = ctx.invoked_subcommand 121 | 122 | artwork_text = None 123 | if artwork: 124 | artwork_path = Path(str(Files.artwork).format(name=artwork)) 125 | if not artwork_path.exists(): 126 | raise click.ClickException(f"No artwork named {artwork} exists.") 127 | artwork_text = artwork_path.read_text(encoding="utf8") 128 | 129 | template_path = Path(str(Files.template).format(name=template)) 130 | if not template_path.exists(): 131 | raise click.ClickException(f"No template named {template} exists.") 132 | template_text = template_path.read_text(encoding="utf8") 133 | 134 | file_name = { 135 | "season": file.parent.name, 136 | "episode": file.stem, 137 | "movie": file.stem 138 | }[ctx.invoked_subcommand] 139 | 140 | nfo_txt = nfo.run(template_text, art=artwork_text, **template_vars) 141 | nfo_out = Path(nfo.file).parent / f"{file_name}.nfo" 142 | nfo_out.write_text(nfo_txt, encoding=encoding, errors="unidecode") 143 | print(f"Generated NFO for {file_name}") 144 | print(f" + Saved to: {nfo_out}") 145 | 146 | description_path = Path(str(Files.description).format(name=template)) 147 | if description_path.exists(): 148 | description_text = description_path.read_text(encoding="utf8") 149 | description_txt = nfo.run(description_text, art=None, **template_vars) 150 | description_out = Path(nfo.file).parent / f"{file_name}.desc.txt" 151 | description_out.write_text(description_txt, encoding=encoding, errors="unidecode") 152 | print(f"Generated Description for {file_name}") 153 | print(f" + Saved to: {description_out}") 154 | -------------------------------------------------------------------------------- /pynfogen/nfo.py: -------------------------------------------------------------------------------- 1 | import re 2 | from pathlib import Path 3 | from typing import Any, Dict, List, Optional, Union 4 | 5 | import langcodes 6 | import requests 7 | from imdb import IMDb 8 | from pymediainfo import MediaInfo 9 | from tldextract import tldextract 10 | 11 | from pynfogen.formatter import CustomFormats 12 | from pynfogen.tracks import Audio, Subtitle, Video 13 | 14 | 15 | class NFO: 16 | IMDB_ID_T = re.compile(r"^tt\d{7,8}$") 17 | TMDB_ID_T = re.compile(r"^(tv|movie)/\d+$") 18 | TVDB_ID_T = re.compile(r"^\d+$") 19 | 20 | def __init__(self, file: Path, imdb: str, **config: Any) -> None: 21 | self.session = self.get_session() 22 | 23 | self.file = file 24 | self.media_info = MediaInfo.parse(self.file) 25 | 26 | self.fanart_api_key: str = config.get("fanart_api_key") 27 | self.source: str = config.get("source") 28 | self.note: str = config.get("note") 29 | self.preview: str = config.get("preview") 30 | 31 | self.season: Union[int, str] = config.get("season") 32 | self.episode, self.episode_name = config.get("episode") or (None, None) 33 | self.episodes: int = self.get_episode_count() 34 | 35 | self.videos = [Video(x, self.file) for x in self.media_info.video_tracks] 36 | self.audio = [Audio(x, self.file) for x in self.media_info.audio_tracks] 37 | self.subtitles = [Subtitle(x, self.file) for x in self.media_info.text_tracks] 38 | self.language = next(( 39 | lang.language 40 | for lang in sorted(self.audio + self.subtitles, key=lambda x: x.streamorder) # type: ignore 41 | if lang.language 42 | ), "en") # defaults to English 43 | 44 | chapters = next(iter(self.media_info.menu_tracks), None) 45 | if chapters: 46 | self.chapters = { 47 | ".".join([k.replace("_", ".")[:-3], k[-3:]]): v.strip(":") 48 | for k, v in chapters.to_data().items() 49 | if f"1{k.replace('_', '')}".isdigit() 50 | } 51 | self.chapters_numbered = all( 52 | x.split(":", 1)[-1].lower() in [f"chapter {i + 1}", f"chapter {str(i + 1).zfill(2)}"] 53 | for i, x in enumerate(self.chapters.values()) 54 | ) 55 | else: 56 | self.chapters = {} 57 | self.chapters_numbered = False 58 | 59 | if not imdb: 60 | raise ValueError("An IMDB ID is required, but none were provided.") 61 | if not self.IMDB_ID_T.match(imdb): 62 | raise ValueError( 63 | f"The provided IMDB ID `{imdb!r}` is not valid. " 64 | f"Expected e.g., 'tt0487831', 'tt10810424', (i.e., include the 'tt')." 65 | ) 66 | self.imdb = IMDb().get_movie(imdb.strip("tt")) 67 | 68 | self.tmdb = config.get("tmdb") 69 | if not self.tmdb: 70 | self.tmdb = self.media_info.general_tracks[0].to_data().get("tmdb") 71 | if self.tmdb and not self.TMDB_ID_T.match(self.tmdb): 72 | raise ValueError( 73 | f"The provided TMDB ID {self.tmdb!r} is not valid. " 74 | f"Expected e.g., 'tv/2490', 'movie/14836', (i.e., include the 'tv/' or 'movie/')." 75 | ) 76 | 77 | self.tvdb = config.get("tvdb") 78 | if not self.tvdb: 79 | self.tvdb = self.media_info.general_tracks[0].to_data().get("tvdb") 80 | if self.tvdb and not self.TVDB_ID_T.match(str(self.tvdb)): 81 | raise ValueError( 82 | f"The provided TVDB ID {self.tvdb!r} is not valid. " 83 | f"Expected e.g., '79216', '1395', (not the url slug e.g., 'the-office-us')." 84 | ) 85 | 86 | if self.tvdb and self.fanart_api_key: 87 | self.banner_image = self.get_banner_image(self.tvdb) 88 | else: 89 | self.banner_image = None 90 | 91 | if self.preview: 92 | self.preview_images = self.get_preview_images(self.preview) 93 | else: 94 | self.preview_images = [] 95 | 96 | def __repr__(self) -> str: 97 | return "<{c} {attrs}>".format( 98 | c=self.__class__.__name__, 99 | attrs=" ".join("{}={!r}".format(k, v) for k, v in self.__dict__.items()), 100 | ) 101 | 102 | def run(self, template: str, art: Optional[str] = None, **kwargs: Any) -> str: 103 | """ 104 | Evaluate and apply formatting on template, apply any art if provided. 105 | Any additional parameters are passed as extra variables to the template. 106 | The extra variables have priority when there's conflicting variable names. 107 | """ 108 | variables = self.__dict__ 109 | variables.update(kwargs) 110 | 111 | template = CustomFormats().format(template, **variables) 112 | if art: 113 | art = art.format(nfo=template) 114 | template = art 115 | 116 | for m in re.finditer(r"<\?([01])\?([\D\d]*?)\?>", template): 117 | # TODO: This if check is quite yucky, look into alternative options. 118 | # Ideally a custom format spec would be great. 119 | template = template.replace( 120 | m.group(0), 121 | m.group(2) if int(m.group(1)) else "" 122 | ) 123 | 124 | template = "\n".join(map(str.rstrip, template.splitlines(keepends=False))) 125 | 126 | return template 127 | 128 | def get_episode_count(self) -> int: 129 | """Count episodes based on neighbouring same-extension files.""" 130 | return sum(1 for _ in self.file.parent.glob(f"*{self.file.suffix}")) 131 | 132 | def get_banner_image(self, tvdb_id: int) -> Optional[str]: 133 | """ 134 | Get a wide banner image from fanart.tv. 135 | It will only return banners in the same language as the first audio track. 136 | """ 137 | if not tvdb_id: 138 | return None 139 | if not self.fanart_api_key: 140 | raise ValueError("Need Fanart.tv api key for TV titles!") 141 | 142 | r = self.session.get(f"http://webservice.fanart.tv/v3/tv/{tvdb_id}?api_key={self.fanart_api_key}") 143 | if r.status_code == 404: 144 | return None 145 | res = r.json() 146 | 147 | error = res.get("error message") 148 | if error: 149 | if error == "Not found": 150 | return None 151 | raise ValueError(f"An unexpected error occurred while calling Fanart.tv, {res}") 152 | 153 | url = next(( 154 | x["url"] 155 | for x in res.get("tvbanner") or [] 156 | if langcodes.closest_supported_match(x["lang"], [self.language], 5) 157 | ), None) 158 | 159 | return url 160 | 161 | def get_preview_images(self, url: str) -> List[Dict[str, str]]: 162 | if not url: 163 | return [] 164 | 165 | domain = tldextract.extract(url).registered_domain 166 | supported_domains = ["imgbox.com", "beyondhd.co"] 167 | if domain not in supported_domains: 168 | return [] 169 | 170 | images = [] 171 | page = self.session.get(url).text 172 | if domain == "imgbox.com": 173 | for m in re.finditer('src="(https://thumbs2.imgbox.com.+/)(\\w+)_b.([^"]+)', page): 174 | images.append({ 175 | "url": f"https://imgbox.com/{m.group(2)}", 176 | "src": f"{m.group(1)}{m.group(2)}_t.{m.group(3)}" 177 | }) 178 | if domain == "beyondhd.co": 179 | for m in re.finditer('/image/([^"]+)"\\D+src="(https://.*beyondhd.co/images.+/(\\w+).md.[^"]+)', page): 180 | images.append({ 181 | "url": f"https://beyondhd.co/image/{m.group(1)}", 182 | "src": m.group(2) 183 | }) 184 | 185 | return images 186 | 187 | @staticmethod 188 | def get_video_print(videos: List[Video]) -> List[List[str]]: 189 | """Get Video track's as string representations.""" 190 | if not videos: 191 | return [["--"]] 192 | 193 | data = [] 194 | for v in videos: 195 | data.append([ 196 | CustomFormats().vformat( 197 | "- {codec} ({profile}) " 198 | "{width}x{height} ({dar}) @ {bitrate}", 199 | args=[], 200 | kwargs=v.all_properties 201 | ), 202 | CustomFormats().vformat( 203 | " {fps} FPS ({frame_rate_mode}), {color_space} {chroma_subsampling} {bit_depth}bps, " 204 | "{range}, {scan}", 205 | args=[], 206 | kwargs=v.all_properties 207 | ) 208 | ]) 209 | 210 | return data 211 | 212 | @staticmethod 213 | def get_audio_print(audio: List[Audio]) -> List[str]: 214 | """Get Audio track's as string representations.""" 215 | if not audio: 216 | return ["--"] 217 | 218 | data = [] 219 | for a in audio: 220 | data.append(CustomFormats().vformat( 221 | "- , {codec} {channels} @ " 222 | "{bitrate}", 223 | args=[], 224 | kwargs=a.all_properties 225 | )) 226 | 227 | return data 228 | 229 | @staticmethod 230 | def get_subtitle_print(subtitles: List[Subtitle]) -> List[str]: 231 | """ 232 | Return a list of a brief subtitle overview per-subtitle. 233 | 234 | e.g. 235 | - English, Forced, SubRip (SRT) 236 | - English, SubRip (SRT) 237 | - English, SDH, SubRip (SRT) 238 | - Spanish, Latin American (SDH), SubRip (SRT) 239 | 240 | The bit of text between the Language and the Subtitle format is the Track Title. 241 | It can be of any format, but it is recommended to be used as shown above. 242 | 243 | It will be returned as a list of strings with the `- ` already pre-pended to each entry. 244 | """ 245 | if not subtitles: 246 | return ["--"] 247 | 248 | data = [] 249 | for s in subtitles: 250 | data.append(CustomFormats().vformat( 251 | "- {title}, {codec}", 252 | args=[], 253 | kwargs=s.all_properties 254 | )) 255 | 256 | return data 257 | 258 | @staticmethod 259 | def get_chapter_print(chapters: Dict[str, str]) -> List[str]: 260 | """Get Chapter's as string representations.""" 261 | if not chapters: 262 | return ["--"] 263 | 264 | return [ 265 | f"- {k}: {v}" 266 | for k, v in chapters.items() 267 | ] 268 | 269 | def get_chapter_print_short(self, chapters: Dict[str, str]) -> str: 270 | """Get string stating if there's Chapters, and if so, if named or numbered.""" 271 | if not chapters: 272 | return "No" 273 | if self.chapters_numbered: 274 | return f"Yes (Numbered 01-{str(len(chapters)).zfill(2)})" 275 | return "Yes (Named)" 276 | 277 | @staticmethod 278 | def get_session() -> requests.Session: 279 | session = requests.Session() 280 | session.headers.update({ 281 | "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0", 282 | "Accept-Language": "en-US,en;q=0.5" 283 | }) 284 | return session 285 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pynfogen 2 | 3 | > [!IMPORTANT] 4 | > This has been functionally replaced with 5 | > 6 | > This original version of pynfogen had upstream packages break that were quite critical, 7 | > but even then, a vast majority of users struggled to grasp the templating and CLI commands. 8 | > And to > be honest I can't blame anyone since really too many things were going on for a simple task. 9 | > 10 | > The new Web App version is a lot nicer to use with a lot of automation built into it 11 | > as well as an on-the-fly previewing system where you can see the variables available. 12 | 13 | [![License](https://img.shields.io/github/license/rlaphoenix/pynfogen)](https://github.com/rlaphoenix/pynfogen/blob/master/LICENSE) 14 | [![Build status](https://github.com/rlaphoenix/pynfogen/actions/workflows/ci.yml/badge.svg)](https://github.com/rlaphoenix/pynfogen/actions/workflows/ci.yml) 15 | [![Python version](https://img.shields.io/pypi/pyversions/pynfogen)](https://pypi.python.org/pypi/pynfogen) 16 | [![PyPI version](https://img.shields.io/pypi/v/pynfogen)](https://pypi.python.org/pypi/pynfogen) 17 | [![DeepSource issues](https://deepsource.io/gh/rlaphoenix/pynfogen.svg/?label=active+issues)](https://deepsource.io/gh/rlaphoenix/pynfogen) 18 | 19 | Scriptable MediaInfo-fed NFO Generator for Movies and TV. 20 | 21 | ## Installation 22 | 23 | pip install --user pynfogen 24 | 25 | ## F.A.Q 26 | 27 | Using pynfogen is fairly simple, it's a multi-command CLI program. You can see up-to-date help information by 28 | running `nfo --help`, or reading the readme file. 29 | 30 | On initial installation, you won't have any [Artwork](#artwork) or [Templates](#templates), which are needed. You can 31 | take a look at some examples at [/examples/art](/examples/art) and [/examples/templates](/examples/templates) in the 32 | git. You may also want to take a look at [Configuration](#configuration). 33 | 34 | Once you have everything set up as much as you want, simply take a look at the available commands with `nfo --help`. 35 | The main command you want to take a look at would be `nfo generate -h`, which will create NFO and Description files 36 | based on *one* provided video file. 37 | 38 | What is an NFO and Description file, you may ask? tl-dr; Think of an NFO as a Receipt with information about your 39 | release to be shared alongside it, and a Description file as the body for your post, thread, topic, message, or 40 | such. You could also think of a Description file as an alternative output you could use. 41 | 42 | More information can be found in the sections below. 43 | 44 | ### What is an NFO? 45 | 46 | An NFO (aka .nfo, .NFO, a contraction of "Info" or "Information") is a commonly used filename extension for text files 47 | that accompany various releases with information about them. 48 | 49 | They are for delivering release information about the media, such as the title, release date, authorship, etc. They 50 | also commonly contain elaborate [ANSI art](https://en.wikipedia.org/wiki/ANSI_art). 51 | 52 | An NFO is generally archaic, do not think otherwise. Originally, NFO files would be shared through IRC, Usenet, 53 | Email, etc. However, these days, most platforms do not allow NFO files to be shared with the release itself, but 54 | rather within a forum thread or post. NFOs are becoming more and more phased out in P2P sharing, but still remains 55 | in use in some cases. 56 | 57 | While there isn't any hard-rules, If you plan to create a modern-style NFO then the following is recommended: 58 | 59 | - Max line-length of 70 characters. 60 | - Text should not reach the edge of the file (col. 1 and 70), instead it should be padded by spacing or ANSI art. 61 | - Text-encoding should be UTF-8 and not CP437. CP437 is far too restricted compared to UTF-8. 62 | 63 | Note that, elaborate ANSI art is no longer really used or wanted. Modern NFO files tend to be verbose with minimal 64 | ANSI art, rather than concise with elaborate ANSI art. 65 | 66 | ### What is a Description file? 67 | 68 | It's like an NFO, without any rules whatsoever. You can use a description template as an alternative output to the NFO 69 | output. This alternative output can be used for any purpose you like, but most commonly used for forum posts, IRC 70 | messages, and such. 71 | 72 | ### What file should I pass to `nfo generate`? 73 | 74 | It's best-practice to provide the first-most file that best represents the majority of the release. E.g., If Episode 1 75 | and 2 has a fault not found on Episodes 3 onwards, then provide Episode 3. 76 | 77 | ### What Text-encoding is supported? 78 | 79 | The input file templates and artwork must be UTF-8. The output generated files' text-encoding can be chosen by you, 80 | but defaults to UTF-8. 81 | 82 | To choose a text-encoding for the output, see `-e/--encoding` of `nfo generate`. This can also be set in the config 83 | with `nfo config generate.encoding cp437` (sets to CP437). 84 | 85 | NFOs with elaborate ANSI art may need to use CP437 text-encoding, where-as any other NFO can get away with just about 86 | any encoding. The default UTF-8 will work fine for most scenarios. However, some applications or websites may require 87 | your NFO to be a specific text-encoding, which is usually either CP437 or UTF-8. 88 | 89 | ### How is it detecting or getting ...? 90 | 91 | #### Database IDs (IMDB, TMDB, TVDB) 92 | 93 | CLI options (`-imdb`, `-tmdb`, `-tvdb`), the Config, or the provided file's global tags (in that order). 94 | 95 | #### Title Name and Year 96 | 97 | The IMDB page's `` tag for the provided IMDB ID. 98 | 99 | #### Release Name 100 | 101 | Season releases get it from the parent folder name of the provided file. 102 | Movie and Episode releases get it from the filename. 103 | 104 | #### Preview Images 105 | 106 | It scrapes the provided Preview URL (`-P`) for thumbnail and full image URLs. 107 | The Preview URL must be for a Gallery or Album. 108 | Supported hosts: 109 | 110 | - <https://imgbox.com> 111 | - <https://beyondhd.co> 112 | 113 | #### Banner Image 114 | 115 | The Fanart.tv API if a TVDB ID has been provided, and a Fanart.tv API key has been set. 116 | It only returns banners that match the language of the primary Audio tracks language. 117 | 118 | #### Season Episode Count 119 | 120 | It counts the amount of neighbouring files of the same file-extension as the provided file. Make sure all files 121 | matching this check is going to be part of the release as an episode file, or the episode count will be inaccurate. 122 | 123 | ## Templates 124 | 125 | | Type | Description | File Extension | 126 | | -------------------- | ---------------------------------------------------------------------------------- | -------------- | 127 | | NFO Template | Primary Scriptable, structural data, like the Title, Year, Media Information, etc. | .nfo | 128 | | Description Template | Similar to NFO templates, but for the content of a forum post, IRC message, etc. | .desc.txt | 129 | 130 | You can add, delete, edit, and list templates with `nfo template -h`. 131 | 132 | Tip: If you notice you are copying and pasting something between templates that is not structural or media information, 133 | then you should probably put it into an [Artwork](#artwork) instead. 134 | 135 | ## Artwork 136 | 137 | Artworks are for surrounding an NFO templates generated output with artwork or common text. Artwork templates aren't 138 | currently applied to Description templates. 139 | 140 | You can add, delete, edit, and list artwork with `nfo artwork -h`. 141 | 142 | **Important:** 143 | The provided example Artwork files are for viewing and studying only, for more information see their 144 | [LICENSE](/examples/art/LICENSE). 145 | 146 | ## Configuration 147 | 148 | All configuration values is entirely optional, but may require to be set to enable some features. 149 | 150 | Configuration values can be set with `nfo config`, e.g., `nfo config generate.artwork phoenix`. 151 | See `nfo config -h` for more information. 152 | 153 | | Config Key | Description | 154 | | -------------- | ----------------------------------------------------------------------------- | 155 | | fanart_api_key | A Fanart.tv API Key to use for the fanart banner image (if available) | 156 | | generate.* | Allows you to set a default for any of the arguments in use by `nfo generate` | 157 | 158 | ## Scripting 159 | 160 | The scripting system used by pynfogen is by no means ideal. It is however, consistent. 161 | It's mostly a mix of python's normal new-style string formatting, with custom formatters. 162 | It also uses a PHP-like `<?{x:y}..?>` custom syntax for if statements. 163 | 164 | Scripting is generally not recommended to be used within Artwork templates. 165 | 166 | ### If statement 167 | 168 | For example the following will check if the `{note}` variable (python new-style formatting) is a truthy value, 169 | and only if so, print it: 170 | 171 | # note = "Hello World!" 172 | <?{note:true}?Has note: {note}?> 173 | # returns: `Has note: Hello World!` 174 | 175 | # note = "" # or None, 0, False, 1==2, e.t.c 176 | <?{note:true}?Has note: {note}?> 177 | # returns: `` 178 | 179 | It's obvious this is in no way good syntax for `if` statements (no `else` or `elif` support either), but it works. 180 | 181 | It uses `1` and `0` in the `<?{here}?...>` section to determine if it should print or not. 182 | Essentially speaking any time the If statement is used, you should be using the [Boolean custom formatter](#boolean). 183 | 184 | ### Custom Formatting 185 | 186 | The following custom additional formatting to pythons new-style formatting is available: 187 | 188 | #### Chaining 189 | 190 | Example: `{var:bbimg:layout,2x2x0}` 191 | 192 | Using `:` you can chain formatter results from left to right, passing previous value as it goes on. 193 | The previous value does not necessarily need to be used. 194 | 195 | For less confusion, since `:` is already used as standard in new-string formatting, look at the above example as 196 | `{(var:bbimg):(layout,2x2x0)}` 197 | 198 | #### Boolean 199 | 200 | Example: `{var:true}` or `{var:!false}`. 201 | Type-hint: func(var: Any) -> Fixed\[1, 0] 202 | 203 | Returns `1` if `var` is a truthy value, otherwise `0`. 204 | 205 | There's also `{var:false}` and `{var:!true}` which is the flip-reverse of the above result. 206 | 207 | #### BBCode Image Links 208 | 209 | Example: `{var:bbimg}` 210 | Type-hint: bbimg(var: Union\[List\[dict], dict]) -> Union\[List\[str], str] 211 | Each dictionary: e.g. `{url: 'https://url/to/image/page', src: 'https://url/to/image/src.png'}` 212 | 213 | Every dictionary is converted to BBCode `[IMG]` wrapped in `[URL]`. For example: 214 | `[URL=https://url/to/image/page][IMG]https://url/to/image/src.png[/IMG][/URL]` 215 | 216 | Returns a list of converted bbcode strings, or a single string if only one dictionary was provided. 217 | 218 | #### Layout 219 | 220 | Example: `{var:layout,3x2x1}` 221 | Type-hint: layout(var: Union\[List\[Any], Any], width: int, height: int, spacing: int) -> str 222 | 223 | Lays out items in a grid-like layout, spacing out items using spaces (or new lines) as specified. 224 | New-lines are used when spacing vertically. 225 | 226 | #### Wrapping 227 | 228 | Example: `{var:>>2x68}` 229 | Type-hint: wrap(var: Any, indent: int, wrap: int) 230 | 231 | Text-wrap to a specific length. Each subsequent new-line caused by the wrapping can be intended (or not if 0). 232 | 233 | #### Centering 234 | 235 | Example: `{var:^>70x68}` 236 | Type-hint: center(var: Any, centering: int, wrap: int) 237 | 238 | Centers and also Text-wraps (while also centering wraps) to a specific width. 239 | 240 | ## License 241 | 242 | [MIT License](LICENSE) 243 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0), and this project adheres 6 | to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [1.1.2] - 2022-01-31 9 | 10 | ### Added 11 | 12 | - Improved language equality check in `get_banner_image` via `langcodes.closest_supported_match`. 13 | - Created Video, Audio, Subtitle, and BaseTrack classes to override and tailor properties of `MediaInfo.Track` 14 | objects for NFO usage. 15 | - Added CustomFormatter() usage to `get_video_print`, `get_audio_print`, and `get_subtitle_print` to allow for 16 | the use of Templating system's custom formatter system, if chains, and so on. 17 | - Added language equality check against subtitles in `get_banner_image` if needed. 18 | - Set the default command to `generate`. Now `nfo ...` is identical to `nfo generate ...`. 19 | 20 | ### Changed 21 | 22 | - Replaced `pytomlpp` with `toml` for more reliability on importing. Project won't lose out on the speed. 23 | - No tracks are checked for a specified language anymore. Languages on Video tracks may not be necessary 24 | in every situation, and code has been updated to check language is available prior to use. 25 | - Merged `NFO.set_config` into `NFO`'s constructor for a simplified instantiation. 26 | - Merged `get_imdb_id`, `get_tmdb_id`, and `get_tvdb_id` into the `NFO` constructor. 27 | - Renamed `get_tv_episodes` to `get_episode_count`. 28 | - Audio track's title will no longer be returned if detected to just be the Language, Codec, or Channels. 29 | - Moved the default language code to `NFO` constructor as `language` property. 30 | 31 | ### Fixed 32 | 33 | - Fixed information in the doc-string of `NFO.get_banner_image`. 34 | - Corrected the value of `__version__` value. Fixes `nfo version`'s value outside of git repositories. 35 | 36 | ## [1.1.1] - 2022-01-27 37 | 38 | ### Fixed 39 | 40 | - Add missing `langcodes-data` dependency. 41 | 42 | ## [1.1.0] - 2022-01-27 43 | 44 | ### Added 45 | 46 | - Added export() and import(), which is the ability to backup and restore configuration, artwork, 47 | and templates to and from one gzip compressed file. 48 | - Video Range is now shown in Video Track print line 2. Includes fallback range. 49 | - Added a LICENSE for the /examples/art that is separate to main license. 50 | - Added a `.gitattributes` file to enforce lf line-endings on all text files. 51 | - Added `jsonpickle` to dependencies. 52 | - Added `isort` test to CI workflow, added tests on 3.10. 53 | - Added `pre-commit` to dev dependencies and a config with tests for isort, flake8, and more. 54 | 55 | ### Changed 56 | 57 | - Advanced Development Status PyPI trove classifier to 4 (Beta). 58 | - Replaced all uses of `pycountry` with `langcodes`. 59 | - Renamed GitHub Workflows from Build and Release to CI, CD. 60 | - Refactored CD workflow to auto-create releases when version tags are made. 61 | - Updated pytomlpp to 1.0.10 for Python 3.10 fixes. 62 | 63 | ### Removed 64 | 65 | - Dropped support for Python versions below 3.7. 66 | - Dropped CI workflow tests on 3.6. 67 | 68 | ### Fixed 69 | 70 | - Editorconfig will no longer trim trailing whitespace on Markdown files. 71 | - Updated the README for v1.0.0 changes. 72 | - Fixed the custom `if` formatter example which had a syntax error. 73 | - LICENSE and README.md is no longer explicitly included. 74 | 75 | ## [1.0.0] - 2021-08-28 76 | 77 | ### Added 78 | 79 | - (nfo generate) It now prints the location of the generated NFO and Description files. 80 | - (nfo generate) Added new option -e/--encoding to control the output text-encoding. It defaults to UTF-8. It's 81 | recommended to use `cp437` for ANSI art, or `utf8` for generally anything else. Ensure the software, page, forum, 82 | and so on actually supports the output text-encoding you choose. Any characters that do not map to the wanted 83 | text-encoding will use unidecode to map to a best-match character. Any characters unidecode also cannot map to 84 | a suitable character, will simply be ignored (i.e., deleted). 85 | - (nfo) It now ensures that all tracks have language information associated with them on the provided file path. 86 | If not, it will tell you which tracks did not have any language information and ask you to add it. Language 87 | checks happen for various reasons across the codebase on multiple track types, so it's important to have them. 88 | Yes, even Video tracks should have language information! 89 | 90 | ### Changed 91 | 92 | - (nfo generate) The template argument has been replaced with sub-commands named `season`, `episode`, and `movie`. 93 | These sub-commands directly use templates named respectively, and directly relate to the type of content for the 94 | generate command to expect. This makes it possible to move -s and -e to only the ones it needs to be used in. 95 | - (nfo) No longer prints its own NFO object instance representation at the end of set_config calls. 96 | - (cli) Replaced `os.path` calls with `Path` across the nfo CLI codebase. 97 | - (formatter) The multiple if checks in format_field has been replaced with a list of tuples for easier glances at 98 | what custom specs are available, and how they are to be used. 99 | - (examples) The release_name variable on the NFO templates have been changed from centered-wrap, to left-wrap with 100 | a 2-space indent. Centered wrapping never looked good due to typical release names not containing spaces or any 101 | characters that could be cleanly wrapped at. 102 | - (examples) The `- - - ...` dashed line and `\n Video : ` (and so on) lines have been replaced with a hard line 103 | separator with two windows, one for the section name, and the other for the section number/count. This reduces the 104 | height footprint and helps distinguish between sections. 105 | 106 | ### Removed 107 | 108 | - The term BBCode in relation to Description Templates has been removed. Description templates can use any kind of 109 | format, or no particular format at all. 110 | - (examples) The `Greetz` text at the end of the example Description templates. 111 | 112 | ### Fixed 113 | 114 | - (nfo generate) Artwork and template files are now explicitly UTF-8. This was a problem on systems that did not use 115 | UTF-8 by default (e.g., Windows using cp1257 instead). 116 | - (nfo) Only try to get a banner image if a fanart API key is available. 117 | 118 | ### Security 119 | 120 | - (nfo generate) You can no longer get values from the users' config using the config key as a variable name. This 121 | allowed malicious template files to leak possibly sensitive information. 122 | 123 | ## [0.5.1] - 2021-08-26 124 | 125 | ### Added 126 | 127 | - (dependencies) Added `pytomlpp` for TOML parsing. 128 | - (config) Created new config file to manage declaration of important directories and filenames. All code that used 129 | files or directories have been updated to use these new importable config objects. 130 | - (nfo generate) Load settings from the config under the `generate` key. This means the defaults for any of the 131 | arguments for nfo generate is now able to set by the config. 132 | 133 | ### Changed 134 | 135 | - (config) All uses of YAML for the config file has been replaced with TOML. Any existing `config.yml` files are now 136 | invalid and should be re-written entirely due to other config changes. 137 | - (git) Update `.gitignore` to latest from <https://github.com/github/gitignore>. 138 | 139 | ### Deprecated 140 | 141 | - (config) `art` option is no longer used. It has been renamed to `artwork` and moved under the `generate` key. 142 | The config `art` option hasn't actually been used since the move to CLI, by mistake. 143 | 144 | ### Removed 145 | 146 | - (dependencies) `PyYAML` as it's been replaced in favor of `pytomlpp` to switch from YAML to TOML, see above. 147 | - (git) Unnecessary `config.yml` exclusion has been removed. 148 | 149 | ### Fixed 150 | 151 | - (nfo) Fix Runtime type-error in get_tvdb_id when click converts the provided TVDB ID string from CLI to an integer. 152 | 153 | ## [0.5.0] - 2021-08-22 154 | 155 | ### Added 156 | 157 | - (dependencies) Added `flake8`, `mypy`, `types-requests` and `isort` to dev-dependencies. 158 | - (nfo) The get_banner_image method now returns banners in the same language of the primary audio track. 159 | - (nfo) The chapter_entries scripting variable now shows the timecode to the left of the Chapter Name/Title. 160 | - (examples) Added example output NFO and Description TXT generated by `nfo generate`. 161 | 162 | ### Changed 163 | 164 | - (pycharm) Disabled the AttributeOutsideInit Inspection. This is to follow a more modern approach of type-hinting 165 | in the init function, instead of out-right initializing it with a value it probably doesn't need nor want. 166 | - (nfo) get_database_ids method has been replaced with separate methods for each database ID. The new methods and 167 | code are more optimized too, with tightened checks on the ID values provided either by the user or the file metadata. 168 | - (nfo) Replaced the uses of the scrape function from helpers with a new `requests` session which is stored in the NFO 169 | class directly. 170 | - (nfo) The chapters attribute value is now a Dict with the key being the chapter timecode and the value being the 171 | chapter name/title. 172 | 173 | ### Fixed 174 | 175 | - (nfo) The chapters_numbered check now checks case-insensitively. 176 | - (nfo) The get_banner_image method now correctly returns banner URLs in expected languages. 177 | - (examples) Removed typo `%` from the end of the if check syntax in templates near the chapter_entries variable. 178 | - (examples) Fixed the list->indented newline-separated paragraph conversion on the chapters_entries variable. 179 | - (examples) Removed unnecessary trailing whitespace from some artwork and templates. 180 | 181 | ### Removed 182 | 183 | - (dependencies) Dropped support for Python 3.6.0 due to bugs in it. Python 3.6.1 and above is still supported. 184 | 185 | ## [0.4.4] - 2021-08-21 186 | 187 | ### Added 188 | 189 | - Created `.markdownlint.yaml` and `.editorconfig` files for cross-editor configuration. 190 | - Added the Contributor Covenant as `CODE_OF_CONDUCT.md`. 191 | - (readme) Mention that scripting is not recommended on artwork templates. 192 | 193 | ### Changed 194 | 195 | - (changelog) Updated the changelog to use Keep a Changelog. This makes the changelog a lot easier to read and write. 196 | - (readme) Update the usage, installation, general layout, fixes. It was previously stating instructions for the old 197 | non-cli versions. 198 | - (examples) Moved the artwork, nfo, and description templates from within the python-module directory to the new 199 | `/examples` directory. 200 | - (license) Update year and username of the copyright line near the bottom of the file. The license is otherwise the 201 | exact same. 202 | 203 | ### Fixed 204 | 205 | - (nfo) Subtitle print list no longer skips printing the subtitle language if it isn't included in the subtitle track 206 | title. It now supports scenarios in which the language is or isn't in the subtitle track title for compatibility. 207 | 208 | ### Security 209 | 210 | - All YAML loads now use `yaml.safe_load` for extra security protection. 211 | 212 | ## [0.4.3] - 2021-07-12 213 | 214 | ### Added 215 | 216 | - (github) Added GitHub actions CI build and release workflows. 217 | - (readme) Add some Badges including new CI build status. 218 | - (nfo artwork) Add nfo artwork explore with the same functionality as nfo template explore. 219 | 220 | ### Removed 221 | 222 | - Unnecessary `/__init__.py` file at root. 223 | 224 | ### Fixed 225 | 226 | - (nfo artwork) Fix possibility of a no-print result if the directory exists but is empty. 227 | - (nfo template) Fix possibility of a no-print result if the directory exists but is empty. 228 | - (nfo config) Ensure the config directory exists before attempting to write to it. 229 | 230 | ## [0.4.2] - 2021-06-26 231 | 232 | ### Changed 233 | 234 | - (dependencies) Updated `pyd2v` to v1.3.0 to use the `_get_d2v()` which was originally part of `pvsfunc`, hence why 235 | it's removal was possible. 236 | 237 | ### Removed 238 | 239 | - (dependencies) `pvsfunc` as it's highly integrated with VapourSynth causing unnecessary VapourSynth requirements. 240 | I only ever used it for one small function which has been moved to `pyd2v`. 241 | - (dependencies) `poetry-dynamic-versioning` as it's caused some problems in some pip related nonsense, and this 242 | project isn't often updated enough to justify its PROs vs. CONs. 243 | 244 | ## [0.4.1] - 2021-06-24 245 | 246 | ### Added 247 | 248 | - (nfo) Support for IMDb `Mini Series` titles. Previously I only knew of `Mini-Series` titles (note the `-`). 249 | 250 | ### Changed 251 | 252 | - (dependencies) Updated `click`, `poetry-dynamic-versioning`, `pvsfunc`, and `pyd2v`. 253 | 254 | ### Removed 255 | 256 | - config.example.yml file remnant that isn't needed due to breaking changes in [0.4.0]. 257 | 258 | ### Fixed 259 | 260 | - (nfo generate) Run `nfo.set_config` even if there's no config or no data in the config. 261 | - (nfo generate) Ensure sure the input file or folder path exists before running generate code. 262 | 263 | ## [0.4.0] - 2021-06-05 264 | 265 | ### ⚠️ Breaking Changes 266 | 267 | - Deleted `pynfogen.py` to move away from a split use-case project to a unified CLI-only project. 268 | 269 | ### Fixed 270 | 271 | - (nfo generate) Fix extra template variable name conflicts with base NFO class variable names. Requires template file 272 | updates, see commit ID [15c66d8] for more information, and see it's changes to the example template files to know 273 | what to change. 274 | - (nfo template) Add missing `--bbcode` option to `delete` subcommand. 275 | - (readme) Fix the syntax and semantics of the custom formatter `If statement` examples. 276 | 277 | [15c66d8]: <https://github.com/rlaphoenix/pynfogen/commit/15c66d8d6767abb04fc26a354aec3bac09f1b542> 278 | 279 | ### Added 280 | 281 | - (nfo) Support for IMDb `Short` titles in `get_title_name_year`. 282 | - (readme) Advertise that PyPI/PIP is a valid installation method. 283 | - (readme) Clarify `poetry config virtualenvs.in-project true` as being recommended, yet optional. 284 | 285 | ## [0.3.3] - 2021-05-27 286 | 287 | ### Fixed 288 | 289 | - (nfo generate) Ensure `-s` is optional by checking `-s` has a value before using it for the int cast check. 290 | 291 | ## [0.3.2] - 2021-05-11 292 | 293 | ### Fixed 294 | 295 | - (nfo generate) Ensure file path is absolute so `release_name` can be correctly retrieved in some scenarios. 296 | 297 | ## [0.3.1] - 2021-05-07 298 | 299 | ### Fixed 300 | 301 | - (nfo config) Fix crash when no valid commands and/or options are used. Return help information in those cases. 302 | - (nfo generate) Fix/Update the doc-string information for `nfo generate`. 303 | - (nfo generate) Use `-N/--note` not `-N/--notes` as to match the already defined `note` variable used by NFO object. 304 | 305 | ## [0.3.0] - 2021-05-06 306 | 307 | ### Added 308 | 309 | - (changelog) This HISTORY.md document. 310 | - (nfo template) `--bbcode` switch for specifying creation/editing of a BBCode Description template. 311 | - (nfo template) `template explore` command to open template directory in file explorer. 312 | - (init) `__version__` string is now available for external use by other scripts and `version` command. 313 | 314 | ### Changed 315 | 316 | - (formatter) Custom formatter specs `boolean` and `length` now returns as ints, but gets cast to str when needed. 317 | 318 | ### Fixed 319 | 320 | - (version) `version` command no longer errors out if it's installed outside a git repository. 321 | - (formatter) Custom formatter spec `layout` no longer panics if it receives a null/length of 0 items. 322 | 323 | ## [0.2.0] - 2021-05-05 324 | 325 | ### Added 326 | 327 | - (dependencies) Added `poetry-dynamic-versioning` and `click`. 328 | - (cli) Created CLI Interface using click. 329 | - (versioning) Added `poetry-dynamic-versioning` for git tag based automated versioning. 330 | - (readme) README now has information on requirements, setup, installation, etc. 331 | - (formatter) New `len` formatter. It returns the `len()` of an object. 332 | 333 | ## Changed 334 | 335 | - (packaging) Replaced setuptools with Poetry (<https://python-poetry.org>). 336 | - (packaging) Project has been reworked as a python module. 337 | - (nfo generate) Artwork is now optional. 338 | 339 | ## [0.1.0] - 2021-05-05 340 | 341 | - Initial release. 342 | 343 | [Unreleased]: https://github.com/rlaphoenix/pynfogen/compare/v1.1.1...HEAD 344 | [1.1.1]: https://github.com/rlaphoenix/pynfogen/releases/tag/v1.1.1 345 | [1.1.0]: https://github.com/rlaphoenix/pynfogen/releases/tag/v1.1.0 346 | [1.0.0]: https://github.com/rlaphoenix/pynfogen/releases/tag/v1.0.0 347 | [0.5.1]: https://github.com/rlaphoenix/pynfogen/releases/tag/v0.5.1 348 | [0.5.0]: https://github.com/rlaphoenix/pynfogen/releases/tag/v0.5.0 349 | [0.4.4]: https://github.com/rlaphoenix/pynfogen/releases/tag/v0.4.4 350 | [0.4.3]: https://github.com/rlaphoenix/pynfogen/releases/tag/v0.4.3 351 | [0.4.2]: https://github.com/rlaphoenix/pynfogen/releases/tag/v0.4.2 352 | [0.4.1]: https://github.com/rlaphoenix/pynfogen/releases/tag/v0.4.1 353 | [0.4.0]: https://github.com/rlaphoenix/pynfogen/releases/tag/v0.4.0 354 | [0.3.3]: https://github.com/rlaphoenix/pynfogen/releases/tag/v0.3.3 355 | [0.3.2]: https://github.com/rlaphoenix/pynfogen/releases/tag/v0.3.2 356 | [0.3.1]: https://github.com/rlaphoenix/pynfogen/releases/tag/v0.3.1 357 | [0.3.0]: https://github.com/rlaphoenix/pynfogen/releases/tag/v0.3.0 358 | [0.2.0]: https://github.com/rlaphoenix/pynfogen/releases/tag/v0.2.0 359 | [0.1.0]: https://github.com/rlaphoenix/pynfogen/releases/tag/v0.1.0 360 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "appdirs" 5 | version = "1.4.4" 6 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 7 | category = "main" 8 | optional = false 9 | python-versions = "*" 10 | files = [ 11 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 12 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 13 | ] 14 | 15 | [[package]] 16 | name = "certifi" 17 | version = "2023.7.22" 18 | description = "Python package for providing Mozilla's CA Bundle." 19 | category = "main" 20 | optional = false 21 | python-versions = ">=3.6" 22 | files = [ 23 | {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, 24 | {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, 25 | ] 26 | 27 | [[package]] 28 | name = "cfgv" 29 | version = "3.3.1" 30 | description = "Validate configuration and produce human readable error messages." 31 | category = "dev" 32 | optional = false 33 | python-versions = ">=3.6.1" 34 | files = [ 35 | {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, 36 | {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, 37 | ] 38 | 39 | [[package]] 40 | name = "charset-normalizer" 41 | version = "3.3.0" 42 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 43 | category = "main" 44 | optional = false 45 | python-versions = ">=3.7.0" 46 | files = [ 47 | {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, 48 | {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, 49 | {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, 50 | {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, 51 | {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, 52 | {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, 53 | {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, 54 | {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, 55 | {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, 56 | {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, 57 | {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, 58 | {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, 59 | {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, 60 | {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, 61 | {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, 62 | {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, 63 | {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, 64 | {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, 65 | {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, 66 | {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, 67 | {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, 68 | {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, 69 | {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, 70 | {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, 71 | {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, 72 | {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, 73 | {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, 74 | {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, 75 | {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, 76 | {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, 77 | {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, 78 | {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, 79 | {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, 80 | {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, 81 | {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, 82 | {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, 83 | {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, 84 | {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, 85 | {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, 86 | {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, 87 | {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, 88 | {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, 89 | {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, 90 | {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, 91 | {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, 92 | {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, 93 | {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, 94 | {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, 95 | {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, 96 | {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, 97 | {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, 98 | {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, 99 | {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, 100 | {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, 101 | {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, 102 | {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, 103 | {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, 104 | {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, 105 | {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, 106 | {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, 107 | {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, 108 | {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, 109 | {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, 110 | {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, 111 | {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, 112 | {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, 113 | {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, 114 | {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, 115 | {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, 116 | {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, 117 | {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, 118 | {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, 119 | {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, 120 | {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, 121 | {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, 122 | {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, 123 | {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, 124 | {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, 125 | {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, 126 | {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, 127 | {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, 128 | {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, 129 | {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, 130 | {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, 131 | {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, 132 | {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, 133 | {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, 134 | {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, 135 | {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, 136 | {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, 137 | ] 138 | 139 | [[package]] 140 | name = "cinemagoer" 141 | version = "2023.5.1" 142 | description = "Python package to access the IMDb's database" 143 | category = "main" 144 | optional = false 145 | python-versions = "*" 146 | files = [ 147 | {file = "cinemagoer-2023.5.1-py3-none-any.whl", hash = "sha256:0c6bc00fbc56cbdd58bc3dbf00cf858770fc127929408460fb28ffe2ca99f83a"}, 148 | {file = "cinemagoer-2023.5.1.tar.gz", hash = "sha256:5ce1d77ae6546701618f11e5b1556a19d18edecad1b6d7d96973ec34941b18f2"}, 149 | ] 150 | 151 | [package.dependencies] 152 | lxml = "*" 153 | SQLAlchemy = "*" 154 | 155 | [package.extras] 156 | dev = ["flake8", "flake8-isort", "pytest", "pytest-cov", "tox"] 157 | doc = ["sphinx", "sphinx-rtd-theme"] 158 | 159 | [[package]] 160 | name = "click" 161 | version = "8.1.7" 162 | description = "Composable command line interface toolkit" 163 | category = "main" 164 | optional = false 165 | python-versions = ">=3.7" 166 | files = [ 167 | {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, 168 | {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, 169 | ] 170 | 171 | [package.dependencies] 172 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 173 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 174 | 175 | [[package]] 176 | name = "click-default-group" 177 | version = "1.2.4" 178 | description = "click_default_group" 179 | category = "main" 180 | optional = false 181 | python-versions = ">=2.7" 182 | files = [ 183 | {file = "click_default_group-1.2.4-py2.py3-none-any.whl", hash = "sha256:9b60486923720e7fc61731bdb32b617039aba820e22e1c88766b1125592eaa5f"}, 184 | {file = "click_default_group-1.2.4.tar.gz", hash = "sha256:eb3f3c99ec0d456ca6cd2a7f08f7d4e91771bef51b01bdd9580cc6450fe1251e"}, 185 | ] 186 | 187 | [package.dependencies] 188 | click = "*" 189 | 190 | [package.extras] 191 | test = ["pytest"] 192 | 193 | [[package]] 194 | name = "colorama" 195 | version = "0.4.6" 196 | description = "Cross-platform colored terminal text." 197 | category = "main" 198 | optional = false 199 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 200 | files = [ 201 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 202 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 203 | ] 204 | 205 | [[package]] 206 | name = "distlib" 207 | version = "0.3.7" 208 | description = "Distribution utilities" 209 | category = "dev" 210 | optional = false 211 | python-versions = "*" 212 | files = [ 213 | {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, 214 | {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, 215 | ] 216 | 217 | [[package]] 218 | name = "dunamai" 219 | version = "1.19.0" 220 | description = "Dynamic version generation" 221 | category = "main" 222 | optional = false 223 | python-versions = ">=3.5,<4.0" 224 | files = [ 225 | {file = "dunamai-1.19.0-py3-none-any.whl", hash = "sha256:1ed948676bbf0812bfaafe315a134634f8d6eb67138513c75aa66e747404b9c6"}, 226 | {file = "dunamai-1.19.0.tar.gz", hash = "sha256:6ad99ae34f7cd290550a2ef1305d2e0292e6e6b5b1b830dfc07ceb7fd35fec09"}, 227 | ] 228 | 229 | [package.dependencies] 230 | importlib-metadata = {version = ">=1.6.0", markers = "python_version < \"3.8\""} 231 | packaging = ">=20.9" 232 | 233 | [[package]] 234 | name = "filelock" 235 | version = "3.12.2" 236 | description = "A platform independent file lock." 237 | category = "main" 238 | optional = false 239 | python-versions = ">=3.7" 240 | files = [ 241 | {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, 242 | {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, 243 | ] 244 | 245 | [package.extras] 246 | docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] 247 | testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] 248 | 249 | [[package]] 250 | name = "flake8" 251 | version = "5.0.4" 252 | description = "the modular source code checker: pep8 pyflakes and co" 253 | category = "dev" 254 | optional = false 255 | python-versions = ">=3.6.1" 256 | files = [ 257 | {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, 258 | {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, 259 | ] 260 | 261 | [package.dependencies] 262 | importlib-metadata = {version = ">=1.1.0,<4.3", markers = "python_version < \"3.8\""} 263 | mccabe = ">=0.7.0,<0.8.0" 264 | pycodestyle = ">=2.9.0,<2.10.0" 265 | pyflakes = ">=2.5.0,<2.6.0" 266 | 267 | [[package]] 268 | name = "greenlet" 269 | version = "3.0.0" 270 | description = "Lightweight in-process concurrent programming" 271 | category = "main" 272 | optional = false 273 | python-versions = ">=3.7" 274 | files = [ 275 | {file = "greenlet-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e09dea87cc91aea5500262993cbd484b41edf8af74f976719dd83fe724644cd6"}, 276 | {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47932c434a3c8d3c86d865443fadc1fbf574e9b11d6650b656e602b1797908a"}, 277 | {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdfaeecf8cc705d35d8e6de324bf58427d7eafb55f67050d8f28053a3d57118c"}, 278 | {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a68d670c8f89ff65c82b936275369e532772eebc027c3be68c6b87ad05ca695"}, 279 | {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ad562a104cd41e9d4644f46ea37167b93190c6d5e4048fcc4b80d34ecb278f"}, 280 | {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02a807b2a58d5cdebb07050efe3d7deaf915468d112dfcf5e426d0564aa3aa4a"}, 281 | {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b1660a15a446206c8545edc292ab5c48b91ff732f91b3d3b30d9a915d5ec4779"}, 282 | {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:813720bd57e193391dfe26f4871186cf460848b83df7e23e6bef698a7624b4c9"}, 283 | {file = "greenlet-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:aa15a2ec737cb609ed48902b45c5e4ff6044feb5dcdfcf6fa8482379190330d7"}, 284 | {file = "greenlet-3.0.0-cp310-universal2-macosx_11_0_x86_64.whl", hash = "sha256:7709fd7bb02b31908dc8fd35bfd0a29fc24681d5cc9ac1d64ad07f8d2b7db62f"}, 285 | {file = "greenlet-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:211ef8d174601b80e01436f4e6905aca341b15a566f35a10dd8d1e93f5dbb3b7"}, 286 | {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6512592cc49b2c6d9b19fbaa0312124cd4c4c8a90d28473f86f92685cc5fef8e"}, 287 | {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:871b0a8835f9e9d461b7fdaa1b57e3492dd45398e87324c047469ce2fc9f516c"}, 288 | {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b505fcfc26f4148551826a96f7317e02c400665fa0883fe505d4fcaab1dabfdd"}, 289 | {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123910c58234a8d40eaab595bc56a5ae49bdd90122dde5bdc012c20595a94c14"}, 290 | {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96d9ea57292f636ec851a9bb961a5cc0f9976900e16e5d5647f19aa36ba6366b"}, 291 | {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b72b802496cccbd9b31acea72b6f87e7771ccfd7f7927437d592e5c92ed703c"}, 292 | {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:527cd90ba3d8d7ae7dceb06fda619895768a46a1b4e423bdb24c1969823b8362"}, 293 | {file = "greenlet-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:37f60b3a42d8b5499be910d1267b24355c495064f271cfe74bf28b17b099133c"}, 294 | {file = "greenlet-3.0.0-cp311-universal2-macosx_10_9_universal2.whl", hash = "sha256:c3692ecf3fe754c8c0f2c95ff19626584459eab110eaab66413b1e7425cd84e9"}, 295 | {file = "greenlet-3.0.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:be557119bf467d37a8099d91fbf11b2de5eb1fd5fc5b91598407574848dc910f"}, 296 | {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b2f1922a39d5d59cc0e597987300df3396b148a9bd10b76a058a2f2772fc04"}, 297 | {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1e22c22f7826096ad503e9bb681b05b8c1f5a8138469b255eb91f26a76634f2"}, 298 | {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d363666acc21d2c204dd8705c0e0457d7b2ee7a76cb16ffc099d6799744ac99"}, 299 | {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:334ef6ed8337bd0b58bb0ae4f7f2dcc84c9f116e474bb4ec250a8bb9bd797a66"}, 300 | {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6672fdde0fd1a60b44fb1751a7779c6db487e42b0cc65e7caa6aa686874e79fb"}, 301 | {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:952256c2bc5b4ee8df8dfc54fc4de330970bf5d79253c863fb5e6761f00dda35"}, 302 | {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:269d06fa0f9624455ce08ae0179430eea61085e3cf6457f05982b37fd2cefe17"}, 303 | {file = "greenlet-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9adbd8ecf097e34ada8efde9b6fec4dd2a903b1e98037adf72d12993a1c80b51"}, 304 | {file = "greenlet-3.0.0-cp312-universal2-macosx_10_9_universal2.whl", hash = "sha256:553d6fb2324e7f4f0899e5ad2c427a4579ed4873f42124beba763f16032959af"}, 305 | {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b5ce7f40f0e2f8b88c28e6691ca6806814157ff05e794cdd161be928550f4c"}, 306 | {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf94aa539e97a8411b5ea52fc6ccd8371be9550c4041011a091eb8b3ca1d810"}, 307 | {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80dcd3c938cbcac986c5c92779db8e8ce51a89a849c135172c88ecbdc8c056b7"}, 308 | {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e52a712c38e5fb4fd68e00dc3caf00b60cb65634d50e32281a9d6431b33b4af1"}, 309 | {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5539f6da3418c3dc002739cb2bb8d169056aa66e0c83f6bacae0cd3ac26b423"}, 310 | {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:343675e0da2f3c69d3fb1e894ba0a1acf58f481f3b9372ce1eb465ef93cf6fed"}, 311 | {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:abe1ef3d780de56defd0c77c5ba95e152f4e4c4e12d7e11dd8447d338b85a625"}, 312 | {file = "greenlet-3.0.0-cp37-cp37m-win32.whl", hash = "sha256:e693e759e172fa1c2c90d35dea4acbdd1d609b6936115d3739148d5e4cd11947"}, 313 | {file = "greenlet-3.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bdd696947cd695924aecb3870660b7545a19851f93b9d327ef8236bfc49be705"}, 314 | {file = "greenlet-3.0.0-cp37-universal2-macosx_11_0_x86_64.whl", hash = "sha256:cc3e2679ea13b4de79bdc44b25a0c4fcd5e94e21b8f290791744ac42d34a0353"}, 315 | {file = "greenlet-3.0.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:63acdc34c9cde42a6534518e32ce55c30f932b473c62c235a466469a710bfbf9"}, 316 | {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a1a6244ff96343e9994e37e5b4839f09a0207d35ef6134dce5c20d260d0302c"}, 317 | {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b822fab253ac0f330ee807e7485769e3ac85d5eef827ca224feaaefa462dc0d0"}, 318 | {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8060b32d8586e912a7b7dac2d15b28dbbd63a174ab32f5bc6d107a1c4143f40b"}, 319 | {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:621fcb346141ae08cb95424ebfc5b014361621b8132c48e538e34c3c93ac7365"}, 320 | {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb36985f606a7c49916eff74ab99399cdfd09241c375d5a820bb855dfb4af9f"}, 321 | {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10b5582744abd9858947d163843d323d0b67be9432db50f8bf83031032bc218d"}, 322 | {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f351479a6914fd81a55c8e68963609f792d9b067fb8a60a042c585a621e0de4f"}, 323 | {file = "greenlet-3.0.0-cp38-cp38-win32.whl", hash = "sha256:9de687479faec7db5b198cc365bc34addd256b0028956501f4d4d5e9ca2e240a"}, 324 | {file = "greenlet-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:3fd2b18432e7298fcbec3d39e1a0aa91ae9ea1c93356ec089421fabc3651572b"}, 325 | {file = "greenlet-3.0.0-cp38-universal2-macosx_11_0_x86_64.whl", hash = "sha256:3c0d36f5adc6e6100aedbc976d7428a9f7194ea79911aa4bf471f44ee13a9464"}, 326 | {file = "greenlet-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4cd83fb8d8e17633ad534d9ac93719ef8937568d730ef07ac3a98cb520fd93e4"}, 327 | {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a5b2d4cdaf1c71057ff823a19d850ed5c6c2d3686cb71f73ae4d6382aaa7a06"}, 328 | {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e7dcdfad252f2ca83c685b0fa9fba00e4d8f243b73839229d56ee3d9d219314"}, 329 | {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c94e4e924d09b5a3e37b853fe5924a95eac058cb6f6fb437ebb588b7eda79870"}, 330 | {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad6fb737e46b8bd63156b8f59ba6cdef46fe2b7db0c5804388a2d0519b8ddb99"}, 331 | {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d55db1db455c59b46f794346efce896e754b8942817f46a1bada2d29446e305a"}, 332 | {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:56867a3b3cf26dc8a0beecdb4459c59f4c47cdd5424618c08515f682e1d46692"}, 333 | {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a812224a5fb17a538207e8cf8e86f517df2080c8ee0f8c1ed2bdaccd18f38f4"}, 334 | {file = "greenlet-3.0.0-cp39-cp39-win32.whl", hash = "sha256:0d3f83ffb18dc57243e0151331e3c383b05e5b6c5029ac29f754745c800f8ed9"}, 335 | {file = "greenlet-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:831d6f35037cf18ca5e80a737a27d822d87cd922521d18ed3dbc8a6967be50ce"}, 336 | {file = "greenlet-3.0.0-cp39-universal2-macosx_11_0_x86_64.whl", hash = "sha256:a048293392d4e058298710a54dfaefcefdf49d287cd33fb1f7d63d55426e4355"}, 337 | ] 338 | 339 | [package.extras] 340 | docs = ["Sphinx"] 341 | test = ["objgraph", "psutil"] 342 | 343 | [[package]] 344 | name = "identify" 345 | version = "2.5.24" 346 | description = "File identification library for Python" 347 | category = "dev" 348 | optional = false 349 | python-versions = ">=3.7" 350 | files = [ 351 | {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, 352 | {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, 353 | ] 354 | 355 | [package.extras] 356 | license = ["ukkonen"] 357 | 358 | [[package]] 359 | name = "idna" 360 | version = "3.4" 361 | description = "Internationalized Domain Names in Applications (IDNA)" 362 | category = "main" 363 | optional = false 364 | python-versions = ">=3.5" 365 | files = [ 366 | {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, 367 | {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, 368 | ] 369 | 370 | [[package]] 371 | name = "importlib-metadata" 372 | version = "4.2.0" 373 | description = "Read metadata from Python packages" 374 | category = "main" 375 | optional = false 376 | python-versions = ">=3.6" 377 | files = [ 378 | {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, 379 | {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, 380 | ] 381 | 382 | [package.dependencies] 383 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 384 | zipp = ">=0.5" 385 | 386 | [package.extras] 387 | docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] 388 | testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", "pyfakefs", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] 389 | 390 | [[package]] 391 | name = "isort" 392 | version = "5.11.5" 393 | description = "A Python utility / library to sort Python imports." 394 | category = "dev" 395 | optional = false 396 | python-versions = ">=3.7.0" 397 | files = [ 398 | {file = "isort-5.11.5-py3-none-any.whl", hash = "sha256:ba1d72fb2595a01c7895a5128f9585a5cc4b6d395f1c8d514989b9a7eb2a8746"}, 399 | {file = "isort-5.11.5.tar.gz", hash = "sha256:6be1f76a507cb2ecf16c7cf14a37e41609ca082330be4e3436a18ef74add55db"}, 400 | ] 401 | 402 | [package.extras] 403 | colors = ["colorama (>=0.4.3,<0.5.0)"] 404 | pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] 405 | plugins = ["setuptools"] 406 | requirements-deprecated-finder = ["pip-api", "pipreqs"] 407 | 408 | [[package]] 409 | name = "jsonpickle" 410 | version = "3.0.2" 411 | description = "Python library for serializing any arbitrary object graph into JSON" 412 | category = "main" 413 | optional = false 414 | python-versions = ">=3.7" 415 | files = [ 416 | {file = "jsonpickle-3.0.2-py3-none-any.whl", hash = "sha256:4a8442d97ca3f77978afa58068768dba7bff2dbabe79a9647bc3cdafd4ef019f"}, 417 | {file = "jsonpickle-3.0.2.tar.gz", hash = "sha256:e37abba4bfb3ca4a4647d28bb9f4706436f7b46c8a8333b4a718abafa8e46b37"}, 418 | ] 419 | 420 | [package.dependencies] 421 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 422 | 423 | [package.extras] 424 | docs = ["jaraco.packaging (>=3.2)", "rst.linker (>=1.9)", "sphinx"] 425 | testing = ["ecdsa", "feedparser", "gmpy2", "numpy", "pandas", "pymongo", "pytest (>=3.5,!=3.7.3)", "pytest-black-multipy", "pytest-checkdocs (>=1.2.3)", "pytest-cov", "pytest-flake8 (>=1.1.1)", "scikit-learn", "sqlalchemy"] 426 | testing-libs = ["simplejson", "ujson"] 427 | 428 | [[package]] 429 | name = "langcodes" 430 | version = "3.3.0" 431 | description = "Tools for labeling human languages with IETF language tags" 432 | category = "main" 433 | optional = false 434 | python-versions = ">=3.6" 435 | files = [ 436 | {file = "langcodes-3.3.0-py3-none-any.whl", hash = "sha256:4d89fc9acb6e9c8fdef70bcdf376113a3db09b67285d9e1d534de6d8818e7e69"}, 437 | {file = "langcodes-3.3.0.tar.gz", hash = "sha256:794d07d5a28781231ac335a1561b8442f8648ca07cd518310aeb45d6f0807ef6"}, 438 | ] 439 | 440 | [package.dependencies] 441 | language-data = {version = ">=1.1,<2.0", optional = true, markers = "extra == \"data\""} 442 | 443 | [package.extras] 444 | data = ["language-data (>=1.1,<2.0)"] 445 | 446 | [[package]] 447 | name = "language-data" 448 | version = "1.1" 449 | description = "Supplementary data about languages used by the langcodes module" 450 | category = "main" 451 | optional = false 452 | python-versions = ">=3.6" 453 | files = [ 454 | {file = "language_data-1.1-py3-none-any.whl", hash = "sha256:f7ba86fafe099ef213ef597eda483d5227b12446604a61f617122d6c925847d5"}, 455 | {file = "language_data-1.1.tar.gz", hash = "sha256:c1f5283c46bba68befa37505857a3f672497aba0c522b37d99367e911232455b"}, 456 | ] 457 | 458 | [package.dependencies] 459 | marisa-trie = ">=0.7.7,<0.8.0" 460 | 461 | [[package]] 462 | name = "lxml" 463 | version = "4.9.3" 464 | description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." 465 | category = "main" 466 | optional = false 467 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" 468 | files = [ 469 | {file = "lxml-4.9.3-cp27-cp27m-macosx_11_0_x86_64.whl", hash = "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c"}, 470 | {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:075b731ddd9e7f68ad24c635374211376aa05a281673ede86cbe1d1b3455279d"}, 471 | {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1e224d5755dba2f4a9498e150c43792392ac9b5380aa1b845f98a1618c94eeef"}, 472 | {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0781a98ff5e6586926293e59480b64ddd46282953203c76ae15dbbbf302e8bb"}, 473 | {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cef2502e7e8a96fe5ad686d60b49e1ab03e438bd9123987994528febd569868e"}, 474 | {file = "lxml-4.9.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991"}, 475 | {file = "lxml-4.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:42871176e7896d5d45138f6d28751053c711ed4d48d8e30b498da155af39aebd"}, 476 | {file = "lxml-4.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae8b9c6deb1e634ba4f1930eb67ef6e6bf6a44b6eb5ad605642b2d6d5ed9ce3c"}, 477 | {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:411007c0d88188d9f621b11d252cce90c4a2d1a49db6c068e3c16422f306eab8"}, 478 | {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:cd47b4a0d41d2afa3e58e5bf1f62069255aa2fd6ff5ee41604418ca925911d76"}, 479 | {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e2cb47860da1f7e9a5256254b74ae331687b9672dfa780eed355c4c9c3dbd23"}, 480 | {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1247694b26342a7bf47c02e513d32225ededd18045264d40758abeb3c838a51f"}, 481 | {file = "lxml-4.9.3-cp310-cp310-win32.whl", hash = "sha256:cdb650fc86227eba20de1a29d4b2c1bfe139dc75a0669270033cb2ea3d391b85"}, 482 | {file = "lxml-4.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:97047f0d25cd4bcae81f9ec9dc290ca3e15927c192df17331b53bebe0e3ff96d"}, 483 | {file = "lxml-4.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5"}, 484 | {file = "lxml-4.9.3-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf"}, 485 | {file = "lxml-4.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a"}, 486 | {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f"}, 487 | {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b"}, 488 | {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120"}, 489 | {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6"}, 490 | {file = "lxml-4.9.3-cp311-cp311-win32.whl", hash = "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305"}, 491 | {file = "lxml-4.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc"}, 492 | {file = "lxml-4.9.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4"}, 493 | {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be"}, 494 | {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13"}, 495 | {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9"}, 496 | {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5"}, 497 | {file = "lxml-4.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8"}, 498 | {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56dc1f1ebccc656d1b3ed288f11e27172a01503fc016bcabdcbc0978b19352b7"}, 499 | {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:578695735c5a3f51569810dfebd05dd6f888147a34f0f98d4bb27e92b76e05c2"}, 500 | {file = "lxml-4.9.3-cp35-cp35m-win32.whl", hash = "sha256:704f61ba8c1283c71b16135caf697557f5ecf3e74d9e453233e4771d68a1f42d"}, 501 | {file = "lxml-4.9.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c41bfca0bd3532d53d16fd34d20806d5c2b1ace22a2f2e4c0008570bf2c58833"}, 502 | {file = "lxml-4.9.3-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:64f479d719dc9f4c813ad9bb6b28f8390360660b73b2e4beb4cb0ae7104f1c12"}, 503 | {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:dd708cf4ee4408cf46a48b108fb9427bfa00b9b85812a9262b5c668af2533ea5"}, 504 | {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c31c7462abdf8f2ac0577d9f05279727e698f97ecbb02f17939ea99ae8daa98"}, 505 | {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e3cd95e10c2610c360154afdc2f1480aea394f4a4f1ea0a5eacce49640c9b190"}, 506 | {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:4930be26af26ac545c3dffb662521d4e6268352866956672231887d18f0eaab2"}, 507 | {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4aec80cde9197340bc353d2768e2a75f5f60bacda2bab72ab1dc499589b3878c"}, 508 | {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:14e019fd83b831b2e61baed40cab76222139926b1fb5ed0e79225bc0cae14584"}, 509 | {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0c0850c8b02c298d3c7006b23e98249515ac57430e16a166873fc47a5d549287"}, 510 | {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aca086dc5f9ef98c512bac8efea4483eb84abbf926eaeedf7b91479feb092458"}, 511 | {file = "lxml-4.9.3-cp36-cp36m-win32.whl", hash = "sha256:50baa9c1c47efcaef189f31e3d00d697c6d4afda5c3cde0302d063492ff9b477"}, 512 | {file = "lxml-4.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bef4e656f7d98aaa3486d2627e7d2df1157d7e88e7efd43a65aa5dd4714916cf"}, 513 | {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:46f409a2d60f634fe550f7133ed30ad5321ae2e6630f13657fb9479506b00601"}, 514 | {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4c28a9144688aef80d6ea666c809b4b0e50010a2aca784c97f5e6bf143d9f129"}, 515 | {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:141f1d1a9b663c679dc524af3ea1773e618907e96075262726c7612c02b149a4"}, 516 | {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:53ace1c1fd5a74ef662f844a0413446c0629d151055340e9893da958a374f70d"}, 517 | {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17a753023436a18e27dd7769e798ce302963c236bc4114ceee5b25c18c52c693"}, 518 | {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7d298a1bd60c067ea75d9f684f5f3992c9d6766fadbc0bcedd39750bf344c2f4"}, 519 | {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:081d32421db5df44c41b7f08a334a090a545c54ba977e47fd7cc2deece78809a"}, 520 | {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:23eed6d7b1a3336ad92d8e39d4bfe09073c31bfe502f20ca5116b2a334f8ec02"}, 521 | {file = "lxml-4.9.3-cp37-cp37m-win32.whl", hash = "sha256:1509dd12b773c02acd154582088820893109f6ca27ef7291b003d0e81666109f"}, 522 | {file = "lxml-4.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:120fa9349a24c7043854c53cae8cec227e1f79195a7493e09e0c12e29f918e52"}, 523 | {file = "lxml-4.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4d2d1edbca80b510443f51afd8496be95529db04a509bc8faee49c7b0fb6d2cc"}, 524 | {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d7e43bd40f65f7d97ad8ef5c9b1778943d02f04febef12def25f7583d19baac"}, 525 | {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:71d66ee82e7417828af6ecd7db817913cb0cf9d4e61aa0ac1fde0583d84358db"}, 526 | {file = "lxml-4.9.3-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:6fc3c450eaa0b56f815c7b62f2b7fba7266c4779adcf1cece9e6deb1de7305ce"}, 527 | {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65299ea57d82fb91c7f019300d24050c4ddeb7c5a190e076b5f48a2b43d19c42"}, 528 | {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eadfbbbfb41b44034a4c757fd5d70baccd43296fb894dba0295606a7cf3124aa"}, 529 | {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3e9bdd30efde2b9ccfa9cb5768ba04fe71b018a25ea093379c857c9dad262c40"}, 530 | {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fcdd00edfd0a3001e0181eab3e63bd5c74ad3e67152c84f93f13769a40e073a7"}, 531 | {file = "lxml-4.9.3-cp38-cp38-win32.whl", hash = "sha256:57aba1bbdf450b726d58b2aea5fe47c7875f5afb2c4a23784ed78f19a0462574"}, 532 | {file = "lxml-4.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:92af161ecbdb2883c4593d5ed4815ea71b31fafd7fd05789b23100d081ecac96"}, 533 | {file = "lxml-4.9.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:9bb6ad405121241e99a86efff22d3ef469024ce22875a7ae045896ad23ba2340"}, 534 | {file = "lxml-4.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8ed74706b26ad100433da4b9d807eae371efaa266ffc3e9191ea436087a9d6a7"}, 535 | {file = "lxml-4.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b"}, 536 | {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:303bf1edce6ced16bf67a18a1cf8339d0db79577eec5d9a6d4a80f0fb10aa2da"}, 537 | {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:5515edd2a6d1a5a70bfcdee23b42ec33425e405c5b351478ab7dc9347228f96e"}, 538 | {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:690dafd0b187ed38583a648076865d8c229661ed20e48f2335d68e2cf7dc829d"}, 539 | {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6420a005548ad52154c8ceab4a1290ff78d757f9e5cbc68f8c77089acd3c432"}, 540 | {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bb3bb49c7a6ad9d981d734ef7c7193bc349ac338776a0360cc671eaee89bcf69"}, 541 | {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d27be7405547d1f958b60837dc4c1007da90b8b23f54ba1f8b728c78fdb19d50"}, 542 | {file = "lxml-4.9.3-cp39-cp39-win32.whl", hash = "sha256:8df133a2ea5e74eef5e8fc6f19b9e085f758768a16e9877a60aec455ed2609b2"}, 543 | {file = "lxml-4.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:4dd9a263e845a72eacb60d12401e37c616438ea2e5442885f65082c276dfb2b2"}, 544 | {file = "lxml-4.9.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35"}, 545 | {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0"}, 546 | {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3"}, 547 | {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b"}, 548 | {file = "lxml-4.9.3-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b"}, 549 | {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7"}, 550 | {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d"}, 551 | {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b"}, 552 | {file = "lxml-4.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a"}, 553 | {file = "lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0"}, 554 | {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694"}, 555 | {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7"}, 556 | {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4"}, 557 | {file = "lxml-4.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9"}, 558 | {file = "lxml-4.9.3.tar.gz", hash = "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"}, 559 | ] 560 | 561 | [package.extras] 562 | cssselect = ["cssselect (>=0.7)"] 563 | html5 = ["html5lib"] 564 | htmlsoup = ["BeautifulSoup4"] 565 | source = ["Cython (>=0.29.35)"] 566 | 567 | [[package]] 568 | name = "marisa-trie" 569 | version = "0.7.8" 570 | description = "Static memory-efficient and fast Trie-like structures for Python." 571 | category = "main" 572 | optional = false 573 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 574 | files = [ 575 | {file = "marisa-trie-0.7.8.tar.gz", hash = "sha256:aee3de5f2836074cfd803f1caf16f68390f262ef09cd7dc7d0e8aee9b6878643"}, 576 | {file = "marisa_trie-0.7.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f1cf9d5ead4471b149fdb93a1c84eddaa941d23e67b0782091adc222d198a87"}, 577 | {file = "marisa_trie-0.7.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:73296b4d6d8ce2f6bc3898fe84348756beddb10cb56442391d050bff135e9c4c"}, 578 | {file = "marisa_trie-0.7.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:782c1515caa603656e15779bc61d5db3b079fa4270ad77f464908796e0d940aa"}, 579 | {file = "marisa_trie-0.7.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49131e51aad530e4d47c716cef1bbef15a4e5b8f75bddfcdd7903f5043ef2331"}, 580 | {file = "marisa_trie-0.7.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45b0a38e015d0149141f028b8892ab518946b828c7931685199549294f5893ca"}, 581 | {file = "marisa_trie-0.7.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a537e0efff1ec880bc212390e97f1d35832a44bd78c96807ddb685d538875096"}, 582 | {file = "marisa_trie-0.7.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5c2a33ede2655f1a6fb840729128cb4bc48829108711f79b7a645b6c0c54b5c2"}, 583 | {file = "marisa_trie-0.7.8-cp310-cp310-win32.whl", hash = "sha256:7200cde8e2040811e98661a60463b296b76a6b224411f8899aa0850085e6af40"}, 584 | {file = "marisa_trie-0.7.8-cp310-cp310-win_amd64.whl", hash = "sha256:a432607bae139183c7251da7eb22f761440bc07d92eacc9e9f7dc0d87f70c495"}, 585 | {file = "marisa_trie-0.7.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a891d2841da153b98c6c7fbe0a89ea8edbc164bdc96a001f360bdcdd54e2070d"}, 586 | {file = "marisa_trie-0.7.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c9ab632c5caef23a59cd43c76ab59e325f9eadd1e9c8b1c34005b9756ae716ee"}, 587 | {file = "marisa_trie-0.7.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:68087942e95acb5801f2a5e9a874aa57af27a4afb52aca81fe1cbe22b2a2fd38"}, 588 | {file = "marisa_trie-0.7.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ef2c4a5023bb6ddbaf1803187b7fb3108e9955aa9c60564504e5f622517c9e7"}, 589 | {file = "marisa_trie-0.7.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24e873619f61bef6a87c669ae459b79d98822270e8a10b21fc52dddf2acc9a46"}, 590 | {file = "marisa_trie-0.7.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:34189c321f30cefb76a6b20c7f055b3f6cd0bc8378c16ba8b7283fd898bf4ac2"}, 591 | {file = "marisa_trie-0.7.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:396555d5f52dc86c65717052573fa2875e10f9e5dd014f825677beadcaec8248"}, 592 | {file = "marisa_trie-0.7.8-cp311-cp311-win32.whl", hash = "sha256:bfe649b02b6318bac572b86d9ddd8276c594411311f8e5ef2edc4bcd7285a06f"}, 593 | {file = "marisa_trie-0.7.8-cp311-cp311-win_amd64.whl", hash = "sha256:84991b52a187d09b269c4caefc8b857a81156c44997eec7eac0e2862d108cc20"}, 594 | {file = "marisa_trie-0.7.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0555104fe9f414abb12e967322a13df778b21958d1727470f4c8dedfde76a8f2"}, 595 | {file = "marisa_trie-0.7.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f96531013252bca14f7665f67aa642be113b6c348ada5e167ebf8db27b1551b5"}, 596 | {file = "marisa_trie-0.7.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ed76391b132c6261cfb402c1a08679e635d09a0a142dae2c1744d816f103c7f"}, 597 | {file = "marisa_trie-0.7.8-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:e6232506b4d66da932f70cf359a4c5ba9e086228ccd97b602159e90c6ea53dab"}, 598 | {file = "marisa_trie-0.7.8-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:34f927f2738d0b402b76821895254e6a164d5020042559f7d910f6632829cdfa"}, 599 | {file = "marisa_trie-0.7.8-cp36-cp36m-win32.whl", hash = "sha256:645908879ae8fcadfb51650fc176902b9e68eee9a8c4d4d8c682cf99ce3ff029"}, 600 | {file = "marisa_trie-0.7.8-cp36-cp36m-win_amd64.whl", hash = "sha256:a5bf2912810e135ce1e60a9b56a179ed62258306103bf5dd3186307f5c51b28f"}, 601 | {file = "marisa_trie-0.7.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bd86212d5037973deda057fc29d60e83dca05e68fa1e7ceaf014c513975c7a0d"}, 602 | {file = "marisa_trie-0.7.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f280f059be417cff81ac030db6a002f8a93093c7ca4555e570d43a24ed45514"}, 603 | {file = "marisa_trie-0.7.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ae35c696f3c5b57c5fe4f73725102f3fe884bc658b854d484dfe6d7e72c86f5"}, 604 | {file = "marisa_trie-0.7.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:524c02f398d361aaf85d8f7709b5ac6de68d020c588fb6c087fb171137643c13"}, 605 | {file = "marisa_trie-0.7.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:353113e811ccfa176fbb611b83671f0b3b40f46b3896b096c10e43f65d35916d"}, 606 | {file = "marisa_trie-0.7.8-cp37-cp37m-win32.whl", hash = "sha256:93172a7314d4d5993970dbafb746f23140d3abfa0d93cc174e766a302d125f7d"}, 607 | {file = "marisa_trie-0.7.8-cp37-cp37m-win_amd64.whl", hash = "sha256:579d69981b18f427bd8e540199c4de400a2bd4ae98e96c814a12cbf766e7029b"}, 608 | {file = "marisa_trie-0.7.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:08858920d0e09ca07d239252884fd72db2abb56c35ff463145ffc9c1277a4f34"}, 609 | {file = "marisa_trie-0.7.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a1b4d07158a3f9b4e84ee709a1fa86b9e11f3dd3b1e6fc45493195105a029545"}, 610 | {file = "marisa_trie-0.7.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f0359f392679774d1ff014f12efdf48da5d661e6241531ff55a3ae5a72a1137e"}, 611 | {file = "marisa_trie-0.7.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c1daaa8c38423fbd119db6654f92740d5ee40d1185a2bbc47afae6712b9ebfc"}, 612 | {file = "marisa_trie-0.7.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:266bf4b6e00b4cff2b8618533919d38b883127f4e5c0af0e0bd78a042093dd99"}, 613 | {file = "marisa_trie-0.7.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:fd7e71d8d85d04d2a5d23611663b2d322b60c98c2edab7e9ef9a2019f7435c5b"}, 614 | {file = "marisa_trie-0.7.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:66b13382be3c277f32143e6c814344118721c7954b2bfb57f5cfe93d17e63c9e"}, 615 | {file = "marisa_trie-0.7.8-cp38-cp38-win32.whl", hash = "sha256:d75b5d642b3d1e47a0ab649fb5eb6bf3681a5e1d3793c8ea7546586ab72731fd"}, 616 | {file = "marisa_trie-0.7.8-cp38-cp38-win_amd64.whl", hash = "sha256:07c14c88fde8a0ac55139f9fe763dc0deabc4b7950047719ae986ca62135e1fb"}, 617 | {file = "marisa_trie-0.7.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c8df5238c7b29498f4ee24fd3ee25e0129b3c56beaed1dd1628bce0ebac8ec8c"}, 618 | {file = "marisa_trie-0.7.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db2bdc480d83a1a566b3a64027f9fb34eae98bfe45788c41a45e99d430cbf48a"}, 619 | {file = "marisa_trie-0.7.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:80b22bdbebc3e6677e83db1352e4f6d478364107874c031a34a961437ead4e93"}, 620 | {file = "marisa_trie-0.7.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6412c816be723a0f11dd41225a30a08182cf2b3b7b3c882c44335003bde47003"}, 621 | {file = "marisa_trie-0.7.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fcdb7f802db43857df3825c4c11acd14bb380deb961ff91e260950886531400"}, 622 | {file = "marisa_trie-0.7.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5cf04156f38dc46f0f14423f98559c5def7d83f3a30f8a580c27ad3b0311ce76"}, 623 | {file = "marisa_trie-0.7.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c53b1d02f4974ecb52c6e8c6f4f1dbf3a15e79bc3861f4ad48b14e4e77c82342"}, 624 | {file = "marisa_trie-0.7.8-cp39-cp39-win32.whl", hash = "sha256:75317347f20bf05ab2ce5537a90989b1439b5e1752f558aad7b5d6b43194429b"}, 625 | {file = "marisa_trie-0.7.8-cp39-cp39-win_amd64.whl", hash = "sha256:82ba3caed5acfdff6a23d6881cc1927776b7320415261b6b24f48d0a190ab890"}, 626 | {file = "marisa_trie-0.7.8-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:43abd082a21295b04859705b088d15acac8956587557680850e3149a79e36789"}, 627 | {file = "marisa_trie-0.7.8-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0d891f0138e5aecc9c5afb7b0a57c758e22c5b5c7c0edb0a1f21ae933259815"}, 628 | {file = "marisa_trie-0.7.8-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9031184fe2215b591a6cdefe5d6d4901806fd7359e813c485a7ff25ea69d603c"}, 629 | {file = "marisa_trie-0.7.8-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8ccb3ba8a2a589b8a7aed693d564f20a6d3bbbb552975f904ba311cea6b85706"}, 630 | {file = "marisa_trie-0.7.8-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f49a2cba047e643e5cd295d75de59f1df710c5e919cd376ac06ead513439881b"}, 631 | {file = "marisa_trie-0.7.8-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d37ea556bb99d9b0dfbe8fd6bdb17e91b91d04531be9e3b8b1b7b7f76ea55637"}, 632 | {file = "marisa_trie-0.7.8-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55a5aea422a4c0c9ef143d3703323f2a43b4a5315fc90bbb6e9ff18544b8d931"}, 633 | {file = "marisa_trie-0.7.8-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d19f363b981fe9b4a302060a8088fd1f00906bc315db24f5d6726b5c309cc47e"}, 634 | {file = "marisa_trie-0.7.8-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e0d51c31fb41b6bc76c1abb7cf2d63a6e0ba7feffc96ea3d92b4d5084d71721a"}, 635 | {file = "marisa_trie-0.7.8-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71ed6286e9d593dac035b8516e7ec35a1b54a7d9c6451a9319e918a8ef722714"}, 636 | {file = "marisa_trie-0.7.8-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc1c1dca06c0fdcca5bb261a09eca2b3bcf41eaeb467caf600ac68e77d3ed2c0"}, 637 | {file = "marisa_trie-0.7.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:891be5569cd6e3a059c2de53d63251aaaef513d68e8d2181f71378f9cb69e1ab"}, 638 | ] 639 | 640 | [package.dependencies] 641 | setuptools = "*" 642 | 643 | [package.extras] 644 | test = ["hypothesis", "pytest", "readme-renderer"] 645 | 646 | [[package]] 647 | name = "mccabe" 648 | version = "0.7.0" 649 | description = "McCabe checker, plugin for flake8" 650 | category = "dev" 651 | optional = false 652 | python-versions = ">=3.6" 653 | files = [ 654 | {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, 655 | {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, 656 | ] 657 | 658 | [[package]] 659 | name = "mypy" 660 | version = "1.4.1" 661 | description = "Optional static typing for Python" 662 | category = "dev" 663 | optional = false 664 | python-versions = ">=3.7" 665 | files = [ 666 | {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, 667 | {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, 668 | {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, 669 | {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, 670 | {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, 671 | {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, 672 | {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, 673 | {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, 674 | {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, 675 | {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, 676 | {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, 677 | {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, 678 | {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, 679 | {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, 680 | {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, 681 | {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, 682 | {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, 683 | {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, 684 | {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, 685 | {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, 686 | {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, 687 | {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, 688 | {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, 689 | {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, 690 | {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, 691 | {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, 692 | ] 693 | 694 | [package.dependencies] 695 | mypy-extensions = ">=1.0.0" 696 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 697 | typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} 698 | typing-extensions = ">=4.1.0" 699 | 700 | [package.extras] 701 | dmypy = ["psutil (>=4.0)"] 702 | install-types = ["pip"] 703 | python2 = ["typed-ast (>=1.4.0,<2)"] 704 | reports = ["lxml"] 705 | 706 | [[package]] 707 | name = "mypy-extensions" 708 | version = "1.0.0" 709 | description = "Type system extensions for programs checked with the mypy type checker." 710 | category = "dev" 711 | optional = false 712 | python-versions = ">=3.5" 713 | files = [ 714 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 715 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 716 | ] 717 | 718 | [[package]] 719 | name = "nodeenv" 720 | version = "1.8.0" 721 | description = "Node.js virtual environment builder" 722 | category = "dev" 723 | optional = false 724 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" 725 | files = [ 726 | {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, 727 | {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, 728 | ] 729 | 730 | [package.dependencies] 731 | setuptools = "*" 732 | 733 | [[package]] 734 | name = "packaging" 735 | version = "23.2" 736 | description = "Core utilities for Python packages" 737 | category = "main" 738 | optional = false 739 | python-versions = ">=3.7" 740 | files = [ 741 | {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, 742 | {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, 743 | ] 744 | 745 | [[package]] 746 | name = "platformdirs" 747 | version = "2.6.2" 748 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 749 | category = "dev" 750 | optional = false 751 | python-versions = ">=3.7" 752 | files = [ 753 | {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, 754 | {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, 755 | ] 756 | 757 | [package.dependencies] 758 | typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""} 759 | 760 | [package.extras] 761 | docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] 762 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] 763 | 764 | [[package]] 765 | name = "pre-commit" 766 | version = "2.21.0" 767 | description = "A framework for managing and maintaining multi-language pre-commit hooks." 768 | category = "dev" 769 | optional = false 770 | python-versions = ">=3.7" 771 | files = [ 772 | {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, 773 | {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, 774 | ] 775 | 776 | [package.dependencies] 777 | cfgv = ">=2.0.0" 778 | identify = ">=1.0.0" 779 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 780 | nodeenv = ">=0.11.1" 781 | pyyaml = ">=5.1" 782 | virtualenv = ">=20.10.0" 783 | 784 | [[package]] 785 | name = "pycodestyle" 786 | version = "2.9.1" 787 | description = "Python style guide checker" 788 | category = "dev" 789 | optional = false 790 | python-versions = ">=3.6" 791 | files = [ 792 | {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, 793 | {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, 794 | ] 795 | 796 | [[package]] 797 | name = "pyd2v" 798 | version = "1.3.1" 799 | description = "A Python Parser for DGMPGDec's D2V Project Files." 800 | category = "main" 801 | optional = false 802 | python-versions = ">=3.7,<4.0" 803 | files = [ 804 | {file = "pyd2v-1.3.1-py3-none-any.whl", hash = "sha256:fab2416872f78100ebacb50bc921ff08f6416d0dee41c4c7f857af3e76364293"}, 805 | {file = "pyd2v-1.3.1.tar.gz", hash = "sha256:f7cd9b4edfa478e1de196530982265bdc4f90d86a2b90c1e1384834469f65a28"}, 806 | ] 807 | 808 | [package.dependencies] 809 | click = ">=8.1.7,<9.0.0" 810 | jsonpickle = ">=3.0.2,<4.0.0" 811 | 812 | [[package]] 813 | name = "pyflakes" 814 | version = "2.5.0" 815 | description = "passive checker of Python programs" 816 | category = "dev" 817 | optional = false 818 | python-versions = ">=3.6" 819 | files = [ 820 | {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, 821 | {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, 822 | ] 823 | 824 | [[package]] 825 | name = "pymediainfo" 826 | version = "6.0.1" 827 | description = "A Python wrapper for the mediainfo library." 828 | category = "main" 829 | optional = false 830 | python-versions = ">=3.7" 831 | files = [ 832 | {file = "pymediainfo-6.0.1-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:81165e895e1e362fa11c128ce2bc976cb8a74224f96f309a88ee047106041b0a"}, 833 | {file = "pymediainfo-6.0.1-py3-none-win32.whl", hash = "sha256:bb3a48ac9706626fd2fa7881f4271728459a1c9a082917deb0c7dd343d8a1be5"}, 834 | {file = "pymediainfo-6.0.1-py3-none-win_amd64.whl", hash = "sha256:c38e79d4d2062732ae555b564c3cac18a6de4f36e033066c617f386cf5e77564"}, 835 | {file = "pymediainfo-6.0.1.tar.gz", hash = "sha256:96e04bac0dfcb726bed70c314b1219121c4b9344c66a98f426ce27d7f9abffb0"}, 836 | ] 837 | 838 | [package.dependencies] 839 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 840 | 841 | [[package]] 842 | name = "pyyaml" 843 | version = "6.0.1" 844 | description = "YAML parser and emitter for Python" 845 | category = "dev" 846 | optional = false 847 | python-versions = ">=3.6" 848 | files = [ 849 | {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, 850 | {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, 851 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, 852 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, 853 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, 854 | {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, 855 | {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, 856 | {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, 857 | {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, 858 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, 859 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, 860 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, 861 | {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, 862 | {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, 863 | {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, 864 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, 865 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, 866 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, 867 | {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, 868 | {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, 869 | {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, 870 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, 871 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, 872 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, 873 | {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, 874 | {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, 875 | {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, 876 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, 877 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, 878 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, 879 | {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, 880 | {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, 881 | {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, 882 | {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, 883 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, 884 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, 885 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, 886 | {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, 887 | {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, 888 | {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, 889 | ] 890 | 891 | [[package]] 892 | name = "requests" 893 | version = "2.31.0" 894 | description = "Python HTTP for Humans." 895 | category = "main" 896 | optional = false 897 | python-versions = ">=3.7" 898 | files = [ 899 | {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, 900 | {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, 901 | ] 902 | 903 | [package.dependencies] 904 | certifi = ">=2017.4.17" 905 | charset-normalizer = ">=2,<4" 906 | idna = ">=2.5,<4" 907 | urllib3 = ">=1.21.1,<3" 908 | 909 | [package.extras] 910 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 911 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 912 | 913 | [[package]] 914 | name = "requests-file" 915 | version = "1.5.1" 916 | description = "File transport adapter for Requests" 917 | category = "main" 918 | optional = false 919 | python-versions = "*" 920 | files = [ 921 | {file = "requests-file-1.5.1.tar.gz", hash = "sha256:07d74208d3389d01c38ab89ef403af0cfec63957d53a0081d8eca738d0247d8e"}, 922 | {file = "requests_file-1.5.1-py2.py3-none-any.whl", hash = "sha256:dfe5dae75c12481f68ba353183c53a65e6044c923e64c24b2209f6c7570ca953"}, 923 | ] 924 | 925 | [package.dependencies] 926 | requests = ">=1.0.0" 927 | six = "*" 928 | 929 | [[package]] 930 | name = "setuptools" 931 | version = "68.0.0" 932 | description = "Easily download, build, install, upgrade, and uninstall Python packages" 933 | category = "main" 934 | optional = false 935 | python-versions = ">=3.7" 936 | files = [ 937 | {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, 938 | {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, 939 | ] 940 | 941 | [package.extras] 942 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] 943 | testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] 944 | testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] 945 | 946 | [[package]] 947 | name = "six" 948 | version = "1.16.0" 949 | description = "Python 2 and 3 compatibility utilities" 950 | category = "main" 951 | optional = false 952 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 953 | files = [ 954 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 955 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 956 | ] 957 | 958 | [[package]] 959 | name = "sqlalchemy" 960 | version = "2.0.21" 961 | description = "Database Abstraction Library" 962 | category = "main" 963 | optional = false 964 | python-versions = ">=3.7" 965 | files = [ 966 | {file = "SQLAlchemy-2.0.21-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1e7dc99b23e33c71d720c4ae37ebb095bebebbd31a24b7d99dfc4753d2803ede"}, 967 | {file = "SQLAlchemy-2.0.21-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7f0c4ee579acfe6c994637527c386d1c22eb60bc1c1d36d940d8477e482095d4"}, 968 | {file = "SQLAlchemy-2.0.21-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f7d57a7e140efe69ce2d7b057c3f9a595f98d0bbdfc23fd055efdfbaa46e3a5"}, 969 | {file = "SQLAlchemy-2.0.21-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca38746eac23dd7c20bec9278d2058c7ad662b2f1576e4c3dbfcd7c00cc48fa"}, 970 | {file = "SQLAlchemy-2.0.21-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3cf229704074bce31f7f47d12883afee3b0a02bb233a0ba45ddbfe542939cca4"}, 971 | {file = "SQLAlchemy-2.0.21-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fb87f763b5d04a82ae84ccff25554ffd903baafba6698e18ebaf32561f2fe4aa"}, 972 | {file = "SQLAlchemy-2.0.21-cp310-cp310-win32.whl", hash = "sha256:89e274604abb1a7fd5c14867a412c9d49c08ccf6ce3e1e04fffc068b5b6499d4"}, 973 | {file = "SQLAlchemy-2.0.21-cp310-cp310-win_amd64.whl", hash = "sha256:e36339a68126ffb708dc6d1948161cea2a9e85d7d7b0c54f6999853d70d44430"}, 974 | {file = "SQLAlchemy-2.0.21-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bf8eebccc66829010f06fbd2b80095d7872991bfe8415098b9fe47deaaa58063"}, 975 | {file = "SQLAlchemy-2.0.21-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b977bfce15afa53d9cf6a632482d7968477625f030d86a109f7bdfe8ce3c064a"}, 976 | {file = "SQLAlchemy-2.0.21-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ff3dc2f60dbf82c9e599c2915db1526d65415be323464f84de8db3e361ba5b9"}, 977 | {file = "SQLAlchemy-2.0.21-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44ac5c89b6896f4740e7091f4a0ff2e62881da80c239dd9408f84f75a293dae9"}, 978 | {file = "SQLAlchemy-2.0.21-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:87bf91ebf15258c4701d71dcdd9c4ba39521fb6a37379ea68088ce8cd869b446"}, 979 | {file = "SQLAlchemy-2.0.21-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b69f1f754d92eb1cc6b50938359dead36b96a1dcf11a8670bff65fd9b21a4b09"}, 980 | {file = "SQLAlchemy-2.0.21-cp311-cp311-win32.whl", hash = "sha256:af520a730d523eab77d754f5cf44cc7dd7ad2d54907adeb3233177eeb22f271b"}, 981 | {file = "SQLAlchemy-2.0.21-cp311-cp311-win_amd64.whl", hash = "sha256:141675dae56522126986fa4ca713739d00ed3a6f08f3c2eb92c39c6dfec463ce"}, 982 | {file = "SQLAlchemy-2.0.21-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7614f1eab4336df7dd6bee05bc974f2b02c38d3d0c78060c5faa4cd1ca2af3b8"}, 983 | {file = "SQLAlchemy-2.0.21-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d59cb9e20d79686aa473e0302e4a82882d7118744d30bb1dfb62d3c47141b3ec"}, 984 | {file = "SQLAlchemy-2.0.21-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a95aa0672e3065d43c8aa80080cdd5cc40fe92dc873749e6c1cf23914c4b83af"}, 985 | {file = "SQLAlchemy-2.0.21-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8c323813963b2503e54d0944813cd479c10c636e3ee223bcbd7bd478bf53c178"}, 986 | {file = "SQLAlchemy-2.0.21-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:419b1276b55925b5ac9b4c7044e999f1787c69761a3c9756dec6e5c225ceca01"}, 987 | {file = "SQLAlchemy-2.0.21-cp37-cp37m-win32.whl", hash = "sha256:4615623a490e46be85fbaa6335f35cf80e61df0783240afe7d4f544778c315a9"}, 988 | {file = "SQLAlchemy-2.0.21-cp37-cp37m-win_amd64.whl", hash = "sha256:cca720d05389ab1a5877ff05af96551e58ba65e8dc65582d849ac83ddde3e231"}, 989 | {file = "SQLAlchemy-2.0.21-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b4eae01faee9f2b17f08885e3f047153ae0416648f8e8c8bd9bc677c5ce64be9"}, 990 | {file = "SQLAlchemy-2.0.21-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3eb7c03fe1cd3255811cd4e74db1ab8dca22074d50cd8937edf4ef62d758cdf4"}, 991 | {file = "SQLAlchemy-2.0.21-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2d494b6a2a2d05fb99f01b84cc9af9f5f93bf3e1e5dbdafe4bed0c2823584c1"}, 992 | {file = "SQLAlchemy-2.0.21-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b19ae41ef26c01a987e49e37c77b9ad060c59f94d3b3efdfdbf4f3daaca7b5fe"}, 993 | {file = "SQLAlchemy-2.0.21-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fc6b15465fabccc94bf7e38777d665b6a4f95efd1725049d6184b3a39fd54880"}, 994 | {file = "SQLAlchemy-2.0.21-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:014794b60d2021cc8ae0f91d4d0331fe92691ae5467a00841f7130fe877b678e"}, 995 | {file = "SQLAlchemy-2.0.21-cp38-cp38-win32.whl", hash = "sha256:0268256a34806e5d1c8f7ee93277d7ea8cc8ae391f487213139018b6805aeaf6"}, 996 | {file = "SQLAlchemy-2.0.21-cp38-cp38-win_amd64.whl", hash = "sha256:73c079e21d10ff2be54a4699f55865d4b275fd6c8bd5d90c5b1ef78ae0197301"}, 997 | {file = "SQLAlchemy-2.0.21-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:785e2f2c1cb50d0a44e2cdeea5fd36b5bf2d79c481c10f3a88a8be4cfa2c4615"}, 998 | {file = "SQLAlchemy-2.0.21-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c111cd40910ffcb615b33605fc8f8e22146aeb7933d06569ac90f219818345ef"}, 999 | {file = "SQLAlchemy-2.0.21-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9cba4e7369de663611ce7460a34be48e999e0bbb1feb9130070f0685e9a6b66"}, 1000 | {file = "SQLAlchemy-2.0.21-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50a69067af86ec7f11a8e50ba85544657b1477aabf64fa447fd3736b5a0a4f67"}, 1001 | {file = "SQLAlchemy-2.0.21-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ccb99c3138c9bde118b51a289d90096a3791658da9aea1754667302ed6564f6e"}, 1002 | {file = "SQLAlchemy-2.0.21-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:513fd5b6513d37e985eb5b7ed89da5fd9e72354e3523980ef00d439bc549c9e9"}, 1003 | {file = "SQLAlchemy-2.0.21-cp39-cp39-win32.whl", hash = "sha256:f9fefd6298433b6e9188252f3bff53b9ff0443c8fde27298b8a2b19f6617eeb9"}, 1004 | {file = "SQLAlchemy-2.0.21-cp39-cp39-win_amd64.whl", hash = "sha256:2e617727fe4091cedb3e4409b39368f424934c7faa78171749f704b49b4bb4ce"}, 1005 | {file = "SQLAlchemy-2.0.21-py3-none-any.whl", hash = "sha256:ea7da25ee458d8f404b93eb073116156fd7d8c2a776d8311534851f28277b4ce"}, 1006 | {file = "SQLAlchemy-2.0.21.tar.gz", hash = "sha256:05b971ab1ac2994a14c56b35eaaa91f86ba080e9ad481b20d99d77f381bb6258"}, 1007 | ] 1008 | 1009 | [package.dependencies] 1010 | greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} 1011 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 1012 | typing-extensions = ">=4.2.0" 1013 | 1014 | [package.extras] 1015 | aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] 1016 | aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] 1017 | asyncio = ["greenlet (!=0.4.17)"] 1018 | asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] 1019 | mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] 1020 | mssql = ["pyodbc"] 1021 | mssql-pymssql = ["pymssql"] 1022 | mssql-pyodbc = ["pyodbc"] 1023 | mypy = ["mypy (>=0.910)"] 1024 | mysql = ["mysqlclient (>=1.4.0)"] 1025 | mysql-connector = ["mysql-connector-python"] 1026 | oracle = ["cx-oracle (>=7)"] 1027 | oracle-oracledb = ["oracledb (>=1.0.1)"] 1028 | postgresql = ["psycopg2 (>=2.7)"] 1029 | postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] 1030 | postgresql-pg8000 = ["pg8000 (>=1.29.1)"] 1031 | postgresql-psycopg = ["psycopg (>=3.0.7)"] 1032 | postgresql-psycopg2binary = ["psycopg2-binary"] 1033 | postgresql-psycopg2cffi = ["psycopg2cffi"] 1034 | postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] 1035 | pymysql = ["pymysql"] 1036 | sqlcipher = ["sqlcipher3-binary"] 1037 | 1038 | [[package]] 1039 | name = "tldextract" 1040 | version = "3.6.0" 1041 | description = "Accurately separates a URL's subdomain, domain, and public suffix, using the Public Suffix List (PSL). By default, this includes the public ICANN TLDs and their exceptions. You can optionally support the Public Suffix List's private domains as well." 1042 | category = "main" 1043 | optional = false 1044 | python-versions = ">=3.7" 1045 | files = [ 1046 | {file = "tldextract-3.6.0-py3-none-any.whl", hash = "sha256:30a492de80f4de215aa998588ba5c2e625ee74ace3a2705cfb52b0021053bcbe"}, 1047 | {file = "tldextract-3.6.0.tar.gz", hash = "sha256:a5d8b6583791daca268a7592ebcf764152fa49617983c49916ee9de99b366222"}, 1048 | ] 1049 | 1050 | [package.dependencies] 1051 | filelock = ">=3.0.8" 1052 | idna = "*" 1053 | requests = ">=2.1.0" 1054 | requests-file = ">=1.4" 1055 | 1056 | [[package]] 1057 | name = "tomli" 1058 | version = "2.0.1" 1059 | description = "A lil' TOML parser" 1060 | category = "dev" 1061 | optional = false 1062 | python-versions = ">=3.7" 1063 | files = [ 1064 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 1065 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 1066 | ] 1067 | 1068 | [[package]] 1069 | name = "typed-ast" 1070 | version = "1.5.5" 1071 | description = "a fork of Python 2 and 3 ast modules with type comment support" 1072 | category = "dev" 1073 | optional = false 1074 | python-versions = ">=3.6" 1075 | files = [ 1076 | {file = "typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b"}, 1077 | {file = "typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686"}, 1078 | {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769"}, 1079 | {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04"}, 1080 | {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d"}, 1081 | {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d"}, 1082 | {file = "typed_ast-1.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02"}, 1083 | {file = "typed_ast-1.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee"}, 1084 | {file = "typed_ast-1.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18"}, 1085 | {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88"}, 1086 | {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2"}, 1087 | {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9"}, 1088 | {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8"}, 1089 | {file = "typed_ast-1.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b"}, 1090 | {file = "typed_ast-1.5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44f214394fc1af23ca6d4e9e744804d890045d1643dd7e8229951e0ef39429b5"}, 1091 | {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:118c1ce46ce58fda78503eae14b7664163aa735b620b64b5b725453696f2a35c"}, 1092 | {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4919b808efa61101456e87f2d4c75b228f4e52618621c77f1ddcaae15904fa"}, 1093 | {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fc2b8c4e1bc5cd96c1a823a885e6b158f8451cf6f5530e1829390b4d27d0807f"}, 1094 | {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:16f7313e0a08c7de57f2998c85e2a69a642e97cb32f87eb65fbfe88381a5e44d"}, 1095 | {file = "typed_ast-1.5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:2b946ef8c04f77230489f75b4b5a4a6f24c078be4aed241cfabe9cbf4156e7e5"}, 1096 | {file = "typed_ast-1.5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e"}, 1097 | {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e"}, 1098 | {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311"}, 1099 | {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2"}, 1100 | {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4"}, 1101 | {file = "typed_ast-1.5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431"}, 1102 | {file = "typed_ast-1.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a"}, 1103 | {file = "typed_ast-1.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437"}, 1104 | {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede"}, 1105 | {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4"}, 1106 | {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6"}, 1107 | {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4"}, 1108 | {file = "typed_ast-1.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b"}, 1109 | {file = "typed_ast-1.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10"}, 1110 | {file = "typed_ast-1.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814"}, 1111 | {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8"}, 1112 | {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274"}, 1113 | {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a"}, 1114 | {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba"}, 1115 | {file = "typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155"}, 1116 | {file = "typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd"}, 1117 | ] 1118 | 1119 | [[package]] 1120 | name = "types-requests" 1121 | version = "2.31.0.7" 1122 | description = "Typing stubs for requests" 1123 | category = "dev" 1124 | optional = false 1125 | python-versions = ">=3.7" 1126 | files = [ 1127 | {file = "types-requests-2.31.0.7.tar.gz", hash = "sha256:4d930dcabbc2452e3d70728e581ac4ac8c2d13f62509ad9114673f542af8cb4e"}, 1128 | {file = "types_requests-2.31.0.7-py3-none-any.whl", hash = "sha256:39844effefca88f4f824dcdc4127b813d3b86a56b2248d3d1afa58832040d979"}, 1129 | ] 1130 | 1131 | [package.dependencies] 1132 | urllib3 = ">=2" 1133 | 1134 | [[package]] 1135 | name = "typing-extensions" 1136 | version = "4.7.1" 1137 | description = "Backported and Experimental Type Hints for Python 3.7+" 1138 | category = "main" 1139 | optional = false 1140 | python-versions = ">=3.7" 1141 | files = [ 1142 | {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, 1143 | {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, 1144 | ] 1145 | 1146 | [[package]] 1147 | name = "unidecode" 1148 | version = "1.3.7" 1149 | description = "ASCII transliterations of Unicode text" 1150 | category = "main" 1151 | optional = false 1152 | python-versions = ">=3.5" 1153 | files = [ 1154 | {file = "Unidecode-1.3.7-py3-none-any.whl", hash = "sha256:663a537f506834ed836af26a81b210d90cbde044c47bfbdc0fbbc9f94c86a6e4"}, 1155 | {file = "Unidecode-1.3.7.tar.gz", hash = "sha256:3c90b4662aa0de0cb591884b934ead8d2225f1800d8da675a7750cbc3bd94610"}, 1156 | ] 1157 | 1158 | [[package]] 1159 | name = "urllib3" 1160 | version = "2.0.6" 1161 | description = "HTTP library with thread-safe connection pooling, file post, and more." 1162 | category = "main" 1163 | optional = false 1164 | python-versions = ">=3.7" 1165 | files = [ 1166 | {file = "urllib3-2.0.6-py3-none-any.whl", hash = "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2"}, 1167 | {file = "urllib3-2.0.6.tar.gz", hash = "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"}, 1168 | ] 1169 | 1170 | [package.extras] 1171 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 1172 | secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] 1173 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 1174 | zstd = ["zstandard (>=0.18.0)"] 1175 | 1176 | [[package]] 1177 | name = "virtualenv" 1178 | version = "20.16.2" 1179 | description = "Virtual Python Environment builder" 1180 | category = "dev" 1181 | optional = false 1182 | python-versions = ">=3.6" 1183 | files = [ 1184 | {file = "virtualenv-20.16.2-py2.py3-none-any.whl", hash = "sha256:635b272a8e2f77cb051946f46c60a54ace3cb5e25568228bd6b57fc70eca9ff3"}, 1185 | {file = "virtualenv-20.16.2.tar.gz", hash = "sha256:0ef5be6d07181946891f5abc8047fda8bc2f0b4b9bf222c64e6e8963baee76db"}, 1186 | ] 1187 | 1188 | [package.dependencies] 1189 | distlib = ">=0.3.1,<1" 1190 | filelock = ">=3.2,<4" 1191 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 1192 | platformdirs = ">=2,<3" 1193 | 1194 | [package.extras] 1195 | docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] 1196 | testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "packaging (>=20.0)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)"] 1197 | 1198 | [[package]] 1199 | name = "zipp" 1200 | version = "3.15.0" 1201 | description = "Backport of pathlib-compatible object wrapper for zip files" 1202 | category = "main" 1203 | optional = false 1204 | python-versions = ">=3.7" 1205 | files = [ 1206 | {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, 1207 | {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, 1208 | ] 1209 | 1210 | [package.extras] 1211 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 1212 | testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] 1213 | 1214 | [metadata] 1215 | lock-version = "2.0" 1216 | python-versions = ">=3.7,<4.0" 1217 | content-hash = "dd718e601b99c69d726b761901d4d1fe862efe931c56791f47c2671b2a2a9975" 1218 | --------------------------------------------------------------------------------