diff --git a/.forgejo/workflows/build-release.yaml b/.forgejo/workflows/build-release.yaml index c4a545c..dcacbec 100644 --- a/.forgejo/workflows/build-release.yaml +++ b/.forgejo/workflows/build-release.yaml @@ -11,7 +11,7 @@ jobs: container: forgejo.neshweb.net/ci-docker-images/python-neshweb:3.11 steps: - name: Checkout source code - uses: https://code.forgejo.org/actions/checkout@v4 + uses: https://code.forgejo.org/actions/checkout@v3 - name: Install packages run: | pip install -e .[lint,typing] -q --disable-pip-version-check -q --no-cache-dir -q @@ -31,7 +31,7 @@ jobs: container: forgejo.neshweb.net/ci-docker-images/python-neshweb:3.11 steps: - name: Checkout source code - uses: https://code.forgejo.org/actions/checkout@v4 + uses: https://code.forgejo.org/actions/checkout@v3 - name: Install packages run: pip install build --no-cache-dir -q - name: Build package diff --git a/.forgejo/workflows/check.yaml b/.forgejo/workflows/check.yaml index 470917f..ffa1e64 100644 --- a/.forgejo/workflows/check.yaml +++ b/.forgejo/workflows/check.yaml @@ -8,7 +8,7 @@ jobs: container: forgejo.neshweb.net/ci-docker-images/python-neshweb:3.11 steps: - name: Checkout source code - uses: actions/checkout@v4 + uses: https://code.forgejo.org/actions/checkout@v3 - name: Install packages run: | pip install -e .[lint] --disable-pip-version-check --no-cache-dir -q @@ -23,7 +23,7 @@ jobs: container: forgejo.neshweb.net/ci-docker-images/python-neshweb:3.11 steps: - name: Checkout source code - uses: actions/checkout@v4 + uses: https://code.forgejo.org/actions/checkout@v3 - name: Install packages run: | pip install -e .[typing] --disable-pip-version-check --no-cache-dir -q @@ -35,22 +35,14 @@ jobs: tests: runs-on: docker - strategy: - matrix: - container: [ "3.10", "3.11", "3.12", "3.13" ] - container: forgejo.neshweb.net/ci-docker-images/python-neshweb:${{ matrix.container }} + container: forgejo.neshweb.net/ci-docker-images/python-neshweb:3.11 steps: - name: Checkout source code - uses: actions/checkout@v4 + uses: https://code.forgejo.org/actions/checkout@v3 - name: Install packages run: | - python --version - python -m venv .venv - source .venv/bin/activate - echo $VIRTUAL_ENV pip install -e .[testing] --disable-pip-version-check --no-cache-dir -q python -m pip list --format=columns --disable-pip-version-check - name: Run pytest run: | - source .venv/bin/activate - python -m pytest + pytest diff --git a/README.md b/README.md index b492eb3..0b20ed7 100644 --- a/README.md +++ b/README.md @@ -60,48 +60,10 @@ If there are any issues with `skyeweave`, feel free to reach out to me using the If you want to help me debug when an issue occurs, set the environment variable `SKYEWEAVE_STDOUT_LEVEL` to `debug` and send me a copy of the log -Windows (PowerShell): +Example on Windows: -```powershell +```shell $env:SKYEWEAVE_STDOUT_LEVEL="debug" -skyeweave --output out --id 70 2>&1 log.log +skyeweave --output out --id 70 > log.log ``` - -Linux (Bash): - -```bash -SKYEWEAVE_STDOUT_LEVEL="debug" - -skyeweave --output out --id 70 2>&1 log.log -``` - -## Contributing - -Feel free to reach out if you want to help to improve skyeweave. I really appreachiate it. - -## FAQ - -> Q: Why Python - -A: Because it is the language I am the most familiar with, and I know my stuff. I also like not having to deal with too many restrictions when developing or the mess that is JS/TS. - -> Q: Why a CLI script? - -A: Because it felt like the appropriate solution for this problem. Writing a whole GUI application felt overkill, and for the most part having the tool just spittingt out the necessary files is more than enough. - -> Q: Where is the executable? Why do I have to install Python? - -A: ~~Because I said so!~~ In all honesty, I felt that it's too much work for too little return building executables for multiple systems, expecially with `pyinstaller` resulting in a large executable for what is actually happening. Installing Python is pretty straightforward, and I have attached guidance for setting up from scratch below. Also: See [this gem](https://www.reddit.com/r/github/comments/1at9br4) for another reason. - -> Q: How did this come to be? - -A: I am usually editing thumbnails for my videos with the expression sheets, and at some point I was just annoyed as the process of editing those for EVERY. SINGLE. SERVANT. IN. EVERY. THUMBNAIL. - -## How to install Python? - -1. Go to [the download page](https://www.python.org/downloads/) and download a current version of Python (must be version 3.10 or later for this package to work). -2. Install Python as you would any other program, make sure it gets added to PATH -3. Open a Terminal (cmd, poweshell on Windows; bash, sh, ... on Linux) and run `python --version` once to verify it installed correctly. There should be some output like `Python 3.11.9` - -After this, you can continue with the instructions above. I highly recommend you check out of virtual environments (venv) work beofre installing, so that you don't pollute the global package installation directory. diff --git a/pyproject.toml b/pyproject.toml index da942cb..fb0f77f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,24 +1,22 @@ [project] name = "skyeweave" -version = "1.0.0-c.5" +version = "1.0.0-c.4" requires-python = ">= 3.10" -authors = [{name = "Firq", email = "me@firq.dev"}] -maintainers = [{name = "Firq", email = "me@firq.dev"}] -description = "Helper script to easily generate experssions from FGO expression sheets" +authors = [{name = "Firq", email = "firelp42@gmail.com"}] +maintainers = [{name = "Firq", email = "firelp42@gmail.com"}] +description = "Easily generate any FGO expression sheets from an id" classifiers = [ "Development Status :: 5 - Production/Stable", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", ] dependencies = [ "pillow~=10.4.0", "requests~=2.32.3", "tqdm~=4.66.5", - "typing_extensions>=4.0; python_version < '3.11'", ] [project.optional-dependencies] @@ -59,7 +57,6 @@ ignore-paths="test/*" python_version = "3.11" warn_return_any = true warn_unused_configs = true -strict = true exclude = [ "test" ] [tool.pytest.ini_options] diff --git a/skyeweave/__init__.py b/skyeweave/__init__.py index a381fc2..07a550a 100644 --- a/skyeweave/__init__.py +++ b/skyeweave/__init__.py @@ -3,5 +3,3 @@ import importlib.metadata __version__ = importlib.metadata.version(__package__ or "skyeweave") from .service import SkyeWeave - -__all__ = [ "SkyeWeave" ] diff --git a/skyeweave/cli/__init__.py b/skyeweave/cli/__init__.py index f2cfb08..af3c5ad 100644 --- a/skyeweave/cli/__init__.py +++ b/skyeweave/cli/__init__.py @@ -1,3 +1 @@ from .cli import run - -__all__ = [ "run" ] diff --git a/skyeweave/cli/cli.py b/skyeweave/cli/cli.py index 7c9b96d..4a18431 100644 --- a/skyeweave/cli/cli.py +++ b/skyeweave/cli/cli.py @@ -2,7 +2,7 @@ import argparse import logging import pathlib import sys -from typing import List, Optional, Sequence, Tuple +from typing import List from .. import __version__ from ..service import SkyeWeave @@ -16,15 +16,15 @@ class CLIArguments(argparse.Namespace): """ Default Arguments when calling the CLI """ - output: Optional[str] - id: Optional[int] - reset: Optional[bool] - nocache: Optional[bool] - filter: Optional[List[str]] - timeout: Optional[int] - quiet: Optional[bool] + output: str + id: int + reset: bool + nocache: bool + filter: List[str] + timeout: int + quiet: bool -def parse_arguments(arguments: Sequence[str]) -> Tuple[CLIArguments, list[str]]: +def parse_arguments(arguments): """ Create a parser and parse the arguments of sys.argv based on the Arguments Namespace Returns arguments and extra arguments separately @@ -43,9 +43,9 @@ def parse_arguments(arguments: Sequence[str]) -> Tuple[CLIArguments, list[str]]: parser.add_argument("--reset", action="store_true", default=False, dest="reset", help="Delete any already downloaded assets") parser.add_argument("--quiet", "-q", action="store_true", default=False, dest="quiet", help="Mute output and hide progress bars") - return parser.parse_known_args(arguments, namespace=CLIArguments()) + return parser.parse_known_args(arguments, namespace=CLIArguments) -def __welcome() -> None: +def __welcome(): print("-------------------------------------------") print(" Welcome to skyeweave, an expression sheet ") print(" composer developed by Firq ") @@ -63,7 +63,7 @@ def validate_id(input_id: None | str) -> int: return int(input_id) -def run() -> None: +def run(): args, _ = parse_arguments(sys.argv[1:]) if args.output: PathConfig.OUTPUT = pathlib.Path(args.output) @@ -75,7 +75,7 @@ def run() -> None: else: __welcome() - input_id = validate_id(str(args.id)) + input_id = validate_id(args.id) if args.nocache: cachepath = PathConfig.IMAGES / str(input_id) @@ -91,7 +91,6 @@ def run() -> None: rmdir(PathConfig.IMAGES) LOGGER.info("Successfully reset local storage") - filters = [int(f) for f in args.filter] if args.filter is not None else None - weaver = SkyeWeave(input_id, filters=filters) + weaver = SkyeWeave(input_id, args.filter) weaver.download() weaver.compose() diff --git a/skyeweave/config/__init__.py b/skyeweave/config/__init__.py index ec28630..48e9947 100644 --- a/skyeweave/config/__init__.py +++ b/skyeweave/config/__init__.py @@ -1,3 +1 @@ from .config import AtlasAPIConfig, ExpressionDefaults, LoggingConfig, PathConfig - -__all__ = [ "AtlasAPIConfig", "ExpressionDefaults", "LoggingConfig", "PathConfig" ] diff --git a/skyeweave/service/__init__.py b/skyeweave/service/__init__.py index 3f15b8a..2746888 100644 --- a/skyeweave/service/__init__.py +++ b/skyeweave/service/__init__.py @@ -1,3 +1 @@ from .compose import SkyeWeave - -__all__ = [ "SkyeWeave" ] diff --git a/skyeweave/service/atlas.py b/skyeweave/service/atlas.py index ea4d1ca..5038c7e 100644 --- a/skyeweave/service/atlas.py +++ b/skyeweave/service/atlas.py @@ -1,14 +1,8 @@ import logging import pathlib -from typing import Annotated, List, Tuple, TypedDict +from typing import Annotated, List, NotRequired, Tuple, TypedDict import requests -# Backport of NotRequired for python 3.10 and older -try: - from typing import NotRequired -except ImportError: - from typing_extensions import NotRequired - from ..config import AtlasAPIConfig, PathConfig, ExpressionDefaults, LoggingConfig LOGGER = logging.getLogger(LoggingConfig.NAME) @@ -51,7 +45,7 @@ def fetch_config(chara_id: int) -> SpritesheetData: LOGGER.debug(returndata) return returndata -def fetch_mstsvtjson() -> None: +def fetch_mstsvtjson(): url = AtlasAPIConfig.MST_SVT_JSON filelocation = PathConfig.IMAGES / "mstsvt.json" @@ -71,7 +65,7 @@ def fetch_mstsvtjson() -> None: break handle.write(block) -def fetch_expression_sheets(tempfolder: pathlib.Path, imageid: int) -> pathlib.Path: +def fetch_expression_sheets(tempfolder: pathlib.Path, imageid: int): atlasurl_base = f"https://static.atlasacademy.io/{AtlasAPIConfig.REGION}/CharaFigure/{imageid}" savefolder = tempfolder / str(imageid) diff --git a/skyeweave/service/compose.py b/skyeweave/service/compose.py index e5fe74c..7e5259f 100644 --- a/skyeweave/service/compose.py +++ b/skyeweave/service/compose.py @@ -1,6 +1,6 @@ import logging import pathlib -from typing import Dict, List, Optional, Tuple, TypedDict +from typing import Dict, List, Optional, TypedDict from collections import Counter from PIL import Image @@ -19,7 +19,7 @@ class SkyeWeave: output_folder: pathlib.Path image_folder: pathlib.Path chara_ids: List[int] - chara_infos: Dict[int, CharaInfos] + chara_infos: Dict[str, CharaInfos] def __init__(self, input_id: int, filters: Optional[List[int]] = None, output: Optional[pathlib.Path] = None, assets: Optional[pathlib.Path] = None): _output_folder = output or PathConfig.OUTPUT @@ -42,20 +42,20 @@ class SkyeWeave: self.output_folder.mkdir(parents=True, exist_ok=True) self.image_folder.mkdir(parents=True, exist_ok=True) - def download(self) -> None: + def download(self): for char_id in self.chara_ids: expfolder = fetch_expression_sheets(self.image_folder, char_id) config = fetch_config(char_id) self.chara_infos.update({char_id : { "folder": expfolder, "config": config}}) LOGGER.debug(self.chara_infos) - def compose(self) -> None: + def compose(self): for key, val in self.chara_infos.items(): LOGGER.info(f"Processing sheet for {key}") self.process_sprite(val["folder"], val["config"], self.output_folder) LOGGER.info(f"Files have been saved at: {self.output_folder.absolute()}") - def process_sprite(self, images_folder: pathlib.Path, configdata: SpritesheetData, outputfolder: pathlib.Path) -> None: + def process_sprite(self, images_folder: pathlib.Path, configdata: SpritesheetData, outputfolder: pathlib.Path): main_sprite = self._gen_main_sprite(images_folder / "0.png") image_idx = self._save_sprite(main_sprite, outputfolder, f"{images_folder.stem}") @@ -79,14 +79,14 @@ class SkyeWeave: LOGGER.debug(f"{x}/{y} - {'Invalid' if img is None else 'Valid'} image") @staticmethod - def _calculate_counts(width: int, height: int, facesize: tuple[int, int]) -> Tuple[int, int]: + def _calculate_counts(width: int, height: int, facesize: tuple[int, int]): rowcount, colcount = height // facesize[1], width // facesize[0] LOGGER.debug(f"{height} | {facesize[1]} --> {rowcount}") LOGGER.debug(f"{width} | {facesize[0]} --> {colcount}") return rowcount, colcount @staticmethod - def _gen_main_sprite(imagepath: pathlib.Path) -> Image.Image: + def _gen_main_sprite(imagepath: pathlib.Path): image = Image.open(imagepath) width, height = image.size LOGGER.debug(f"Main sprite ({imagepath}): {width}:{height}") @@ -126,7 +126,7 @@ class SkyeWeave: return idx + 1 @staticmethod - def _is_empty(img: Image.Image) -> bool: + def _is_empty(img: Image.Image): w, h = img.size croparea = (w * 0.375, h * 0.375, w * 0.625, h * 0.625 ) data = Counter(img.crop(croparea).convert('LA').getdata()) diff --git a/skyeweave/utils/__init__.py b/skyeweave/utils/__init__.py index e5d3dcd..d6ba198 100644 --- a/skyeweave/utils/__init__.py +++ b/skyeweave/utils/__init__.py @@ -1,4 +1,2 @@ from .filesystem import rmdir from .logger import LOGGER, disable_tqdm - -__all__ = [ "rmdir", "LOGGER", "disable_tqdm" ] diff --git a/skyeweave/utils/filesystem.py b/skyeweave/utils/filesystem.py index 1d3d0c5..752ef02 100644 --- a/skyeweave/utils/filesystem.py +++ b/skyeweave/utils/filesystem.py @@ -1,6 +1,6 @@ import pathlib -def rmdir(directory: pathlib.Path) -> None: +def rmdir(directory: pathlib.Path): """ Recursively deletes all files and folders in a given directory diff --git a/skyeweave/utils/logger.py b/skyeweave/utils/logger.py index dedf0bc..11bec2e 100644 --- a/skyeweave/utils/logger.py +++ b/skyeweave/utils/logger.py @@ -4,17 +4,17 @@ import sys from ..config import LoggingConfig -def disable_tqdm() -> None: +def disable_tqdm(): from tqdm import tqdm from functools import partialmethod - tqdm.__init__ = partialmethod(tqdm.__init__, disable=True) # type: ignore[method-assign,assignment] + tqdm.__init__ = partialmethod(tqdm.__init__, disable=True) -def __init_logger() -> logging.Logger: +def __init_logger(): if LoggingConfig.LEVEL == "DEBUG": disable_tqdm() default_logger = logging.getLogger(LoggingConfig.NAME) - default_logger.setLevel(LoggingConfig.LEVEL) # type: ignore[arg-type] + default_logger.setLevel(LoggingConfig.LEVEL) default_handler = logging.StreamHandler(stream=sys.stdout) default_formatter = logging.Formatter('[%(levelname)s] [%(name)s] %(message)s')