Compare commits

...

15 Commits

Author SHA1 Message Date
8602978a0a refactor: update hook to support latest versions of VapourSynth 2024-08-28 14:17:51 -04:00
4c4a12d1dc refactor: update readme 2024-08-25 14:24:58 -04:00
a32b7586d8 feat: update build script to use poetry's venv 2024-08-25 14:11:31 -04:00
d27d3d0653 feat: v1.3.3
fix: an edge case bug that could potentially cause data loss when generating the final folder
fix: a bug where the encode would attempt to use the source index file, generating a new one in it's place incorrectly (this was present in ffms2)
2024-08-25 14:00:27 -04:00
69f76e6164 refactor: update required python version 2024-08-12 12:12:02 -04:00
jlw4049
ee38e3fbd9 feat: v1.3.2 2024-07-09 14:45:53 -04:00
jlw4049
1d88a0b929 feat: v1.3.1 2024-06-21 14:59:52 -04:00
jlw4049
d77f708dc2 fix: bug since VapourSynth v65 where we not byte strings was trying to be decoded for FFMS2 2024-06-21 14:59:46 -04:00
jlw4049
358ae81525 feat: add latest version of awsmfunc 2024-06-21 14:57:26 -04:00
jlw4049
930e6b4e70 feat: update readme 2024-06-20 12:17:09 -04:00
jlw4049
84908723d9 feat: v1.3.0
feat: add arg `--img-lib`. This allows the user to select one of the two image libs, defaulting to fpng.
refactor: improved error handling
feat: added temp directory clean up even if the program crashes
2024-06-20 12:14:06 -04:00
jlw4049
d4f0c46ae3 refactor: re worked how image output folders worked in order to prevent errors from dependency libraries not being able to handle unicode paths in some cases
refactor: removed un-needed comments
feat: add support to select image generation library from the CLI
refactor: only import what we need from awsmfunc instead of the whole module
2024-06-20 12:12:59 -04:00
jlw4049
af5695c647 feat: upgrade vapoursynth from v64 to v65 2024-06-20 12:10:51 -04:00
jlw4049
0324c22d9b feat: v1.2.2
fix: unicode characters could cause improper paths during folder creation
2024-06-17 14:19:14 -04:00
jlw4049
50976c6f19 refactor: remove unidecode 2024-06-17 14:18:34 -04:00
7 changed files with 176 additions and 100 deletions

@ -1,12 +1,12 @@
# Image-Generator
A CLI to generate comparison image sets with
A CLI to generate comparison image sets utilizing the power of VapourSynth
## Usage
```
usage: FrameForge [-h] [-v] [--source SOURCE] [--encode ENCODE] [--frames FRAMES] [--image-dir IMAGE_DIR]
[--indexer {lsmash,ffms2}] [--source-index-path SOURCE_INDEX_PATH]
[--indexer {lsmash,ffms2}] [--img-lib {imwri,fpng}] [--source-index-path SOURCE_INDEX_PATH]
[--encode-index-path ENCODE_INDEX_PATH] [--sub-size SUB_SIZE] [--sub-alignment SUB_ALIGNMENT]
[--left-crop LEFT_CROP] [--right-crop RIGHT_CROP] [--top-crop TOP_CROP] [--bottom-crop BOTTOM_CROP]
[--adv-resize-left ADV_RESIZE_LEFT] [--adv-resize-right ADV_RESIZE_RIGHT]
@ -24,6 +24,8 @@ options:
Path to base image folder
--indexer {lsmash,ffms2}
Indexer choice
--img-lib {imwri,fpng}
Image library to use
--source-index-path SOURCE_INDEX_PATH
Path to look/create indexes for source
--encode-index-path ENCODE_INDEX_PATH

@ -1,5 +1,5 @@
from pathlib import Path
from subprocess import run
from subprocess import run, PIPE
import os
import shutil
import sys
@ -31,11 +31,23 @@ def build_app():
icon_path = project_root / "images" / "icon.ico"
additional_hooks_path = Path(Path.cwd() / "hooks")
# paths to needed vapoursynth files
vapoursynth_64 = project_root / ".venv" / "Lib" / "site-packages" / "vapoursynth64"
vapoursynth_64_portable = (
project_root / ".venv" / "Lib" / "site-packages" / "portable.vs"
# get poetry venv path
poetry_venv_path = run(
["cmd", "/c", "poetry", "env", "info", "--path"],
stdout=PIPE,
stderr=PIPE,
text=True,
check=True,
)
if poetry_venv_path.returncode == 0 and poetry_venv_path.stdout:
poetry_venv_path = Path(poetry_venv_path.stdout.strip())
site_packages = poetry_venv_path / "Lib" / "site-packages"
# get paths to needed vapoursynth files in poetry venv
vapoursynth_64 = site_packages / "vapoursynth64"
vapoursynth_64_portable = site_packages / "portable.vs"
else:
raise FileNotFoundError("Cannot find path to poetry venv")
# Change directory so PyInstaller outputs all of its files in its own folder
os.chdir(pyinstaller_folder)

@ -7,7 +7,7 @@ from frame_forge.cli_utils import frame_list
program_name = "FrameForge"
__version__ = "1.2.1"
__version__ = "1.3.3"
if __name__ == "__main__":
@ -33,6 +33,13 @@ if __name__ == "__main__":
default="lsmash",
help="Indexer choice",
)
parser.add_argument(
"--img-lib",
type=str,
choices=["imwri", "fpng"],
default="fpng",
help="Image library to use",
)
parser.add_argument(
"--source-index-path", type=str, help="Path to look/create indexes for source"
)
@ -114,6 +121,7 @@ if __name__ == "__main__":
frames=args.frames,
image_dir=image_dir,
indexer=args.indexer,
img_lib=args.img_lib,
source_index_path=args.source_index_path,
encode_index_path=args.encode_index_path,
sub_size=args.sub_size,
@ -134,8 +142,18 @@ if __name__ == "__main__":
subtitle_color=args.subtitle_color,
release_sub_title=args.release_sub_title,
)
except Exception as init_error:
exit_application(f"Initiation Error: {init_error}", 1)
try:
img_gen = img_generator.process_images()
if img_gen:
exit_application(f"Output: {img_gen}", 0)
except FrameForgeError as error:
exit_application(error, 1)
exit_application(f"\nOutput: {img_gen}", 0)
except FrameForgeError as ff_error:
img_generator.clean_temp(False)
exit_application(str(ff_error), 1)
except Exception as except_error:
img_generator.clean_temp(False)
exit_application(f"Unhandled Exception: {except_error}", 1)
finally:
img_generator.clean_temp(False)

@ -1,12 +1,13 @@
import re
import shutil
import tempfile
from random import choice
from pathlib import Path
from typing import Tuple
from numpy import linspace
from unidecode import unidecode
import awsmfunc
import vapoursynth as vs
from awsmfunc import ScreenGenEncoder, ScreenGen, FrameInfo, DynamicTonemap
from frame_forge.exceptions import FrameForgeError
from frame_forge.utils import get_working_dir, hex_to_bgr
@ -19,6 +20,7 @@ class GenerateImages:
frames: str,
image_dir: Path,
indexer: str,
img_lib: str,
source_index_path: None | str,
encode_index_path: None | str,
sub_size: int,
@ -45,6 +47,7 @@ class GenerateImages:
self.encode_node = None
self.image_dir = image_dir
self.indexer = indexer
self.img_lib = ScreenGenEncoder(img_lib)
self.source_index_path = source_index_path
self.encode_index_path = encode_index_path
self.sub_size = sub_size
@ -66,7 +69,9 @@ class GenerateImages:
self.core = vs.core
self.load_plugins()
def process_images(self):
self.temp_dir: Path = None
def process_images(self) -> Path:
self.check_index_paths()
if self.indexer == "lsmash":
@ -109,7 +114,11 @@ class GenerateImages:
if not self.frames:
b_frames = self.get_b_frames(num_source_frames)
screenshot_comparison_dir, screenshot_sync_dir = self.generate_folders()
(
temp_screenshot_comparison_dir,
temp_selected_dir,
temp_screenshot_sync_dir,
) = self.generate_temp_folders()
self.handle_crop()
@ -120,23 +129,27 @@ class GenerateImages:
vs_source_info, vs_encode_info = self.handle_subtitles(selected_sub_style)
if not self.frames:
img_job = self.generate_screens(
self.generate_screens(
b_frames,
vs_source_info,
vs_encode_info,
screenshot_comparison_dir,
screenshot_sync_dir,
temp_screenshot_comparison_dir,
temp_screenshot_sync_dir,
selected_sub_style_ref,
selected_sub_style_sync,
)
else:
img_job = self.generate_exact_screens(
self.generate_exact_screens(
vs_source_info,
vs_encode_info,
screenshot_comparison_dir,
temp_screenshot_comparison_dir,
)
return img_job
final_folder = self.generate_final_folder()
self.move_images(temp_screenshot_comparison_dir.parent, final_folder)
self.clean_temp()
return final_folder
@staticmethod
def screen_gen_callback(sg_call_back):
@ -155,13 +168,14 @@ class GenerateImages:
text=f"Reference\nFrame: {ref_frame}",
style=selected_sub_style_ref,
)
awsmfunc.ScreenGen(
ScreenGen(
vs_encode_ref_info,
frame_numbers=[ref_frame],
fpng_compression=1,
folder=screenshot_sync_dir,
suffix="b_encode__%d",
callback=self.screen_gen_callback,
encoder=self.img_lib,
)
def generate_sync_screens(
@ -174,13 +188,14 @@ class GenerateImages:
text=f"Sync\nFrame: {sync_frame}",
style=selected_sub_style_sync,
)
awsmfunc.ScreenGen(
ScreenGen(
vs_sync_info,
frame_numbers=[sync_frame],
fpng_compression=1,
folder=Path(screenshot_sync_dir),
suffix="a_source__%d",
callback=self.screen_gen_callback,
encoder=self.img_lib,
)
def generate_exact_screens(
@ -188,31 +203,33 @@ class GenerateImages:
vs_source_info,
vs_encode_info,
screenshot_comparison_dir,
) -> str:
) -> Path:
print("\nGenerating screenshots, please wait", flush=True)
# generate source images
awsmfunc.ScreenGen(
ScreenGen(
vs_source_info,
frame_numbers=self.frames,
fpng_compression=1,
folder=screenshot_comparison_dir,
suffix="a_source__%d",
callback=self.screen_gen_callback,
encoder=self.img_lib,
)
# generate encode images
awsmfunc.ScreenGen(
ScreenGen(
vs_encode_info,
frame_numbers=self.frames,
fpng_compression=1,
folder=screenshot_comparison_dir,
suffix="b_encode__%d",
callback=self.screen_gen_callback,
encoder=self.img_lib,
)
print("Screen generation completed", flush=True)
return str(screenshot_comparison_dir)
return screenshot_comparison_dir
def generate_screens(
self,
@ -223,7 +240,7 @@ class GenerateImages:
screenshot_sync_dir,
selected_sub_style_ref,
selected_sub_style_sync,
) -> str:
) -> Path:
print("\nGenerating screenshots, please wait", flush=True)
# handle re_sync if needed
@ -240,23 +257,25 @@ class GenerateImages:
sync_frames = b_frames
# generate source images
awsmfunc.ScreenGen(
ScreenGen(
vs_source_info,
frame_numbers=sync_frames,
fpng_compression=1,
folder=screenshot_comparison_dir,
suffix="a_source__%d",
callback=self.screen_gen_callback,
encoder=self.img_lib,
)
# generate encode images
awsmfunc.ScreenGen(
ScreenGen(
vs_encode_info,
frame_numbers=b_frames,
fpng_compression=1,
folder=screenshot_comparison_dir,
suffix="b_encode__%d",
callback=self.screen_gen_callback,
encoder=self.img_lib,
)
# generate some sync frames
@ -295,13 +314,13 @@ class GenerateImages:
)
print("Screen generation completed", flush=True)
return str(screenshot_comparison_dir)
return screenshot_comparison_dir
def handle_subtitles(self, selected_sub_style):
vs_source_info = self.core.sub.Subtitle(
clip=self.source_node, text="Source", style=selected_sub_style
)
vs_encode_info = awsmfunc.FrameInfo(
vs_encode_info = FrameInfo(
clip=self.encode_node,
title=self.release_sub_title if self.release_sub_title else "",
style=selected_sub_style,
@ -311,10 +330,8 @@ class GenerateImages:
def handle_hdr(self):
if self.tone_map:
self.source_node = awsmfunc.DynamicTonemap(
clip=self.source_node, libplacebo=False
)
self.encode_node = awsmfunc.DynamicTonemap(
self.source_node = DynamicTonemap(clip=self.source_node, libplacebo=False)
self.encode_node = DynamicTonemap(
clip=self.encode_node,
reference=self.reference_source_file,
libplacebo=False,
@ -371,8 +388,8 @@ class GenerateImages:
bottom=self.bottom_crop if self.bottom_crop else 0,
)
def generate_folders(self):
print("\nCreating folders for images", flush=True)
def generate_final_folder(self) -> Path:
print("\nCreating final output folder", flush=True)
if self.image_dir:
image_output_dir = Path(self.image_dir)
else:
@ -380,34 +397,61 @@ class GenerateImages:
Path(self.encode_file).parent / f"{Path(self.encode_file).stem}_images"
)
# remove any accent characters from path
image_output_dir = Path(unidecode(str(image_output_dir)))
# check if temp image dir exists, if so delete it!
if image_output_dir.exists():
shutil.rmtree(image_output_dir, ignore_errors=True)
for folder in ("img_comparison", "img_selected", "img_sync"):
rm_path = image_output_dir / folder
if rm_path.is_dir() and rm_path.exists():
shutil.rmtree(rm_path, ignore_errors=True)
# create main image dir
image_output_dir.mkdir(exist_ok=True, parents=True)
# create comparison image directory and define it as variable
Path(Path(image_output_dir) / "img_comparison").mkdir(exist_ok=True)
screenshot_comparison_dir = str(Path(Path(image_output_dir) / "img_comparison"))
# create selected image directory and define it as variable
Path(Path(image_output_dir) / "img_selected").mkdir(exist_ok=True)
# create sync image directory and define it as variable
Path(Path(image_output_dir) / "img_sync").mkdir(exist_ok=True)
screenshot_sync_dir = str(Path(Path(image_output_dir) / "img_sync"))
# create sub directories
Path(Path(image_output_dir) / "img_sync/sync1").mkdir(exist_ok=True)
Path(Path(image_output_dir) / "img_sync/sync2").mkdir(exist_ok=True)
print("Folder creation completed", flush=True)
return screenshot_comparison_dir, screenshot_sync_dir
return image_output_dir
def generate_temp_folders(self) -> Tuple[Path, Path, Path]:
print("\nCreating temporary folders for images", flush=True)
self.temp_dir = Path(tempfile.mkdtemp(prefix="ff_"))
screenshot_comparison_dir = Path(Path(self.temp_dir) / "img_comparison")
screenshot_comparison_dir.mkdir(exist_ok=True)
selected_dir = Path(Path(self.temp_dir) / "img_selected")
selected_dir.mkdir(exist_ok=True)
screenshot_sync_dir = Path(Path(self.temp_dir) / "img_sync")
screenshot_sync_dir.mkdir(exist_ok=True)
Path(screenshot_sync_dir / "sync1").mkdir(exist_ok=True)
Path(screenshot_sync_dir / "sync2").mkdir(exist_ok=True)
print("Folder creation completed", flush=True)
return screenshot_comparison_dir, selected_dir, screenshot_sync_dir
def move_images(self, temp_folder: Path, output_folder: Path) -> None:
print("\nMoving generated images")
for sub_folder in temp_folder.iterdir():
if sub_folder.is_dir():
target_sub_folder = output_folder / sub_folder.name
target_sub_folder.mkdir(parents=True, exist_ok=True)
for item in sub_folder.iterdir():
target_item = target_sub_folder / item.name
if item.is_dir():
shutil.move(item, target_item)
else:
shutil.move(item, target_sub_folder)
print("Image move completed", flush=True)
def clean_temp(self, status: bool = True) -> None:
if status:
print("\nRemoving temp folder")
shutil.rmtree(self.temp_dir, ignore_errors=True)
if status:
print("Temp folder removal completed")
def get_b_frames(self, num_source_frames):
print(
@ -424,9 +468,11 @@ class GenerateImages:
)
try:
pict_types = ("B", b"B")
for i, frame in enumerate(b_frames):
while (
self.encode_node.get_frame(frame).props["_PictType"].decode() != "B"
self.encode_node.get_frame(frame).props["_PictType"]
not in pict_types
):
frame += 1
b_frames[i] = frame
@ -645,7 +691,7 @@ class GenerateImages:
self.source_index_path = Path(self.source_index_path)
if not self.encode_index_path or not Path(self.encode_index_path).exists():
encode_path_obj = Path(self.source_file)
encode_path_obj = Path(self.encode_file)
self.encode_index_path = encode_path_obj.parent / Path(
f"{encode_path_obj.stem}{indexer_ext}"
)

@ -22,6 +22,7 @@ hiddenimports = [
"collections.namedtuple",
"collections.abc.Iterable",
"collections.abc.Mapping",
"concurrent.futures",
"concurrent.futures.Future",
"fractions",
]

63
poetry.lock generated

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
[[package]]
name = "altgraph"
@ -17,10 +17,8 @@ version = "1.3.4"
description = "awesome VapourSynth functions"
optional = false
python-versions = ">=3.9"
files = [
{file = "awsmfunc-1.3.4-py3-none-any.whl", hash = "sha256:d9ce9cf90dfdb66b4561a5d3b011232e663ad0d879e2a276827bff9b8b3b37e1"},
{file = "awsmfunc-1.3.4.tar.gz", hash = "sha256:8330332f5c4818322b4090b24499b1dc4e4e371460de70c4bd62a112f4157255"},
]
files = []
develop = false
[package.dependencies]
numpy = "*"
@ -29,7 +27,13 @@ vs-rekt = ">=1.0.0"
vsutil = ">=0.7.0"
[package.extras]
dev = ["pylint", "toml", "yapf"]
dev = ["ruff", "toml"]
[package.source]
type = "git"
url = "https://github.com/OpusGang/awsmfunc"
reference = "HEAD"
resolved_reference = "e1290f799162749fc627951290bfb4089f2f39cb"
[[package]]
name = "black"
@ -216,10 +220,10 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co
[[package]]
name = "pyinstaller"
version = "6.3.0"
version = "6.10.0"
description = "PyInstaller bundles a Python application and all its dependencies into a single package."
optional = false
python-versions = "<3.13,>=3.8"
python-versions = "<3.14,>=3.8"
files = []
develop = true
@ -228,7 +232,7 @@ altgraph = "*"
macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""}
packaging = ">=22.0"
pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""}
pyinstaller-hooks-contrib = ">=2021.4"
pyinstaller-hooks-contrib = ">=2024.8"
pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""}
setuptools = ">=42.0.0"
@ -242,15 +246,19 @@ url = "custom-pyinstaller"
[[package]]
name = "pyinstaller-hooks-contrib"
version = "2023.11"
version = "2024.8"
description = "Community maintained hooks for PyInstaller"
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "pyinstaller-hooks-contrib-2023.11.tar.gz", hash = "sha256:5dd7a8a054a65c19cdaa381cabcfbe76f44d5f88d18214b0c570a0cd139be77f"},
{file = "pyinstaller_hooks_contrib-2023.11-py2.py3-none-any.whl", hash = "sha256:f2a75dac2968ec81f92dcd3768906f654fa4204bc496126ae8483e87a5d89602"},
{file = "pyinstaller_hooks_contrib-2024.8-py3-none-any.whl", hash = "sha256:0057fe9a5c398d3f580e73e58793a1d4a8315ca91c3df01efea1c14ed557825a"},
{file = "pyinstaller_hooks_contrib-2024.8.tar.gz", hash = "sha256:29b68d878ab739e967055b56a93eb9b58e529d5b054fbab7a2f2bacf80cef3e2"},
]
[package.dependencies]
packaging = ">=22.0"
setuptools = ">=42.0.0"
[[package]]
name = "pywin32-ctypes"
version = "0.2.2"
@ -278,41 +286,30 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
name = "unidecode"
version = "1.3.7"
description = "ASCII transliterations of Unicode text"
optional = false
python-versions = ">=3.5"
files = [
{file = "Unidecode-1.3.7-py3-none-any.whl", hash = "sha256:663a537f506834ed836af26a81b210d90cbde044c47bfbdc0fbbc9f94c86a6e4"},
{file = "Unidecode-1.3.7.tar.gz", hash = "sha256:3c90b4662aa0de0cb591884b934ead8d2225f1800d8da675a7750cbc3bd94610"},
]
[[package]]
name = "vapoursynth"
version = "64"
version = "65"
description = "A frameserver for the 21st century"
optional = false
python-versions = "*"
files = [
{file = "VapourSynth-64-cp311-cp311-win_amd64.whl", hash = "sha256:ad046a704537276f7ebb4132e1210e8922ec4b2403a4b44f5fe30fd9456a6412"},
{file = "VapourSynth-64-cp38-cp38-win_amd64.whl", hash = "sha256:794f93bcaa1ce79510c11962f677a7d18685a0c29aa36586cb307d1eb9d7f2e0"},
{file = "VapourSynth-64.zip", hash = "sha256:29425a135ca68cbb17b3dfcad0097375b75f94af1f499ba89bcd2b03b2651846"},
{file = "VapourSynth-65-cp311-cp311-win_amd64.whl", hash = "sha256:9fab72bb6dbcdd8d0e6643da68908f85387bc764b836524e97a1e6e8989ca13e"},
{file = "VapourSynth-65-cp38-cp38-win_amd64.whl", hash = "sha256:d9b49b595dc929d63250bd82f05e75238b6dfc4a50ab08e7fc4fe4f8888f6b95"},
{file = "VapourSynth-65.zip", hash = "sha256:1d42d461ef9988a3477134e478a2291d79f3469635cde8af2c66b1e87c36f711"},
]
[[package]]
name = "vapoursynth-portable"
version = "64"
version = "65"
description = "A frameserver for the 21st century"
optional = false
python-versions = "*"
files = [
{file = "VapourSynth_portable-64-py2.py3-none-win_amd64.whl", hash = "sha256:82b26b38774197a7ef53cbd3206bafb02d197100fad513d635bf83f9981dad6c"},
{file = "VapourSynth_portable-65-py2.py3-none-win_amd64.whl", hash = "sha256:6a66615d8a25a71766d035054f9980fd8be47dbd97332e9c068933d1b25061b3"},
]
[package.dependencies]
vapoursynth = "64"
vapoursynth = "65"
[[package]]
name = "vs-rekt"
@ -345,5 +342,5 @@ vapoursynth = "*"
[metadata]
lock-version = "2.0"
python-versions = "3.11.5"
content-hash = "820bfb62c5fba1b17221bc1a02144932dbce64fa44c698f24f7205c2fc4a3d18"
python-versions = "3.11.9"
content-hash = "42cace5dbe92132d04512c3dbc5a60c1cb06fa883a7030eb74f8f0e782b48226"

@ -1,17 +1,17 @@
[tool.poetry]
name = "frame-forge"
version = "1.0.3"
version = "1.3.3"
description = "CLI to offload image generation to"
authors = ["jlw4049 <jlw_4049@hotmail.com>"]
license = "MIT"
readme = "README.md"
package-mode = false
[tool.poetry.dependencies]
python = "3.11.5"
awsmfunc = "^1.3.4"
unidecode = "^1.3.7"
vapoursynth-portable = "64"
python = "3.11.9"
numpy = "^1.26.2"
vapoursynth-portable = "65"
awsmfunc = {git = "https://github.com/OpusGang/awsmfunc"}
[tool.poetry.group.dev.dependencies]