Updated readme, fixed linting and type annotations
This commit is contained in:
parent
acc05a99bb
commit
b701a91083
12 changed files with 84 additions and 33 deletions
44
README.md
44
README.md
|
@ -60,10 +60,48 @@ 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
|
||||
|
||||
Example on Windows:
|
||||
Windows (PowerShell):
|
||||
|
||||
```shell
|
||||
```powershell
|
||||
$env:SKYEWEAVE_STDOUT_LEVEL="debug"
|
||||
|
||||
skyeweave --output out --id 70 > log.log
|
||||
skyeweave --output out --id 70 2>&1 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.
|
||||
|
|
|
@ -4,13 +4,14 @@ version = "1.0.0-c.4"
|
|||
requires-python = ">= 3.10"
|
||||
authors = [{name = "Firq", email = "firelp42@gmail.com"}]
|
||||
maintainers = [{name = "Firq", email = "firelp42@gmail.com"}]
|
||||
description = "Easily generate any FGO expression sheets from an id"
|
||||
description = "Helper script to easily generate experssions from FGO expression sheets"
|
||||
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 = [
|
||||
|
@ -57,6 +58,7 @@ ignore-paths="test/*"
|
|||
python_version = "3.11"
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
strict = true
|
||||
exclude = [ "test" ]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
|
|
|
@ -3,3 +3,5 @@ import importlib.metadata
|
|||
__version__ = importlib.metadata.version(__package__ or "skyeweave")
|
||||
|
||||
from .service import SkyeWeave
|
||||
|
||||
__all__ = [ "SkyeWeave" ]
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
from .cli import run
|
||||
|
||||
__all__ = [ "run" ]
|
||||
|
|
|
@ -2,7 +2,7 @@ import argparse
|
|||
import logging
|
||||
import pathlib
|
||||
import sys
|
||||
from typing import List
|
||||
from typing import List, Optional, Sequence, Tuple
|
||||
|
||||
from .. import __version__
|
||||
from ..service import SkyeWeave
|
||||
|
@ -16,15 +16,15 @@ class CLIArguments(argparse.Namespace):
|
|||
"""
|
||||
Default Arguments when calling the CLI
|
||||
"""
|
||||
output: str
|
||||
id: int
|
||||
reset: bool
|
||||
nocache: bool
|
||||
filter: List[str]
|
||||
timeout: int
|
||||
quiet: bool
|
||||
output: Optional[str]
|
||||
id: Optional[int]
|
||||
reset: Optional[bool]
|
||||
nocache: Optional[bool]
|
||||
filter: Optional[List[str]]
|
||||
timeout: Optional[int]
|
||||
quiet: Optional[bool]
|
||||
|
||||
def parse_arguments(arguments):
|
||||
def parse_arguments(arguments: Sequence[str]) -> Tuple[CLIArguments, list[str]]:
|
||||
"""
|
||||
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):
|
|||
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():
|
||||
def __welcome() -> None:
|
||||
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():
|
||||
def run() -> None:
|
||||
args, _ = parse_arguments(sys.argv[1:])
|
||||
if args.output:
|
||||
PathConfig.OUTPUT = pathlib.Path(args.output)
|
||||
|
@ -75,7 +75,7 @@ def run():
|
|||
else:
|
||||
__welcome()
|
||||
|
||||
input_id = validate_id(args.id)
|
||||
input_id = validate_id(str(args.id))
|
||||
|
||||
if args.nocache:
|
||||
cachepath = PathConfig.IMAGES / str(input_id)
|
||||
|
@ -91,6 +91,7 @@ def run():
|
|||
rmdir(PathConfig.IMAGES)
|
||||
LOGGER.info("Successfully reset local storage")
|
||||
|
||||
weaver = SkyeWeave(input_id, args.filter)
|
||||
filters = [int(f) for f in args.filter] if args.filter is not None else None
|
||||
weaver = SkyeWeave(input_id, filters=filters)
|
||||
weaver.download()
|
||||
weaver.compose()
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
from .config import AtlasAPIConfig, ExpressionDefaults, LoggingConfig, PathConfig
|
||||
|
||||
__all__ = [ "AtlasAPIConfig", "ExpressionDefaults", "LoggingConfig", "PathConfig" ]
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
from .compose import SkyeWeave
|
||||
|
||||
__all__ = [ "SkyeWeave" ]
|
||||
|
|
|
@ -45,7 +45,7 @@ def fetch_config(chara_id: int) -> SpritesheetData:
|
|||
LOGGER.debug(returndata)
|
||||
return returndata
|
||||
|
||||
def fetch_mstsvtjson():
|
||||
def fetch_mstsvtjson() -> None:
|
||||
url = AtlasAPIConfig.MST_SVT_JSON
|
||||
filelocation = PathConfig.IMAGES / "mstsvt.json"
|
||||
|
||||
|
@ -65,7 +65,7 @@ def fetch_mstsvtjson():
|
|||
break
|
||||
handle.write(block)
|
||||
|
||||
def fetch_expression_sheets(tempfolder: pathlib.Path, imageid: int):
|
||||
def fetch_expression_sheets(tempfolder: pathlib.Path, imageid: int) -> pathlib.Path:
|
||||
atlasurl_base = f"https://static.atlasacademy.io/{AtlasAPIConfig.REGION}/CharaFigure/{imageid}"
|
||||
|
||||
savefolder = tempfolder / str(imageid)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import logging
|
||||
import pathlib
|
||||
from typing import Dict, List, Optional, TypedDict
|
||||
from typing import Dict, List, Optional, Tuple, 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[str, CharaInfos]
|
||||
chara_infos: Dict[int, 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):
|
||||
def download(self) -> None:
|
||||
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):
|
||||
def compose(self) -> None:
|
||||
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):
|
||||
def process_sprite(self, images_folder: pathlib.Path, configdata: SpritesheetData, outputfolder: pathlib.Path) -> None:
|
||||
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]):
|
||||
def _calculate_counts(width: int, height: int, facesize: tuple[int, int]) -> 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):
|
||||
def _gen_main_sprite(imagepath: pathlib.Path) -> Image.Image:
|
||||
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):
|
||||
def _is_empty(img: Image.Image) -> bool:
|
||||
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())
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
from .filesystem import rmdir
|
||||
from .logger import LOGGER, disable_tqdm
|
||||
|
||||
__all__ = [ "rmdir", "LOGGER", "disable_tqdm" ]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import pathlib
|
||||
|
||||
def rmdir(directory: pathlib.Path):
|
||||
def rmdir(directory: pathlib.Path) -> None:
|
||||
"""
|
||||
Recursively deletes all files and folders in a given directory
|
||||
|
||||
|
|
|
@ -4,17 +4,17 @@ import sys
|
|||
|
||||
from ..config import LoggingConfig
|
||||
|
||||
def disable_tqdm():
|
||||
def disable_tqdm() -> None:
|
||||
from tqdm import tqdm
|
||||
from functools import partialmethod
|
||||
tqdm.__init__ = partialmethod(tqdm.__init__, disable=True)
|
||||
tqdm.__init__ = partialmethod(tqdm.__init__, disable=True) # type: ignore[method-assign,assignment]
|
||||
|
||||
def __init_logger():
|
||||
def __init_logger() -> logging.Logger:
|
||||
if LoggingConfig.LEVEL == "DEBUG":
|
||||
disable_tqdm()
|
||||
|
||||
default_logger = logging.getLogger(LoggingConfig.NAME)
|
||||
default_logger.setLevel(LoggingConfig.LEVEL)
|
||||
default_logger.setLevel(LoggingConfig.LEVEL) # type: ignore[arg-type]
|
||||
|
||||
default_handler = logging.StreamHandler(stream=sys.stdout)
|
||||
default_formatter = logging.Formatter('[%(levelname)s] [%(name)s] %(message)s')
|
||||
|
|
Loading…
Add table
Reference in a new issue