feat: initial commit

This commit is contained in:
jlw4049 2024-01-24 12:11:06 -05:00
commit 33670e0659
12 changed files with 1304 additions and 0 deletions

12
.gitignore vendored Normal file

@ -0,0 +1,12 @@
.idea/
.venv/
__pycache__/
env/
*.bin
*.exe
*.zip
*.dll
pyinstaller_build/
scrap_files/
.vscode/
custom-pyinstaller/

9
LICENSE Normal file

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2023 jlw_4049
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

61
README.md Normal file

@ -0,0 +1,61 @@
# Image-Generator
A CLI to generate comparison image sets with
## Usage
```
usage: Comparison Image Generator [-h] [-v] [--source SOURCE] [--encode ENCODE] [--image-dir IMAGE_DIR]
[--indexer {lsmash,ffms2}] [--index-dir INDEX_DIR] [--sub-size SUB_SIZE]
[--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] [--adv-resize-top ADV_RESIZE_TOP]
[--adv-resize-bottom ADV_RESIZE_BOTTOM] [--tone-map] [--re-sync RE_SYNC]
[--comparison-count COMPARISON_COUNT] [--subtitle-color SUBTITLE_COLOR]
[--release-sub-title RELEASE_SUB_TITLE]
options:
-h, --help show this help message and exit
-v, --version show program's version number and exit
--source SOURCE Path to source file
--encode ENCODE Path to encode file
--image-dir IMAGE_DIR
Path to base image folder
--indexer {lsmash,ffms2}
Indexer choice
--index-dir INDEX_DIR
Path to look/create indexes
--sub-size SUB_SIZE Size of subtitles
--left-crop LEFT_CROP
Left crop
--right-crop RIGHT_CROP
Right crop
--top-crop TOP_CROP Top crop
--bottom-crop BOTTOM_CROP
crop
--adv-resize-left ADV_RESIZE_LEFT
Advanced resize left
--adv-resize-right ADV_RESIZE_RIGHT
Advanced resize right
--adv-resize-top ADV_RESIZE_TOP
Advanced resize top
--adv-resize-bottom ADV_RESIZE_BOTTOM
Advanced resize bottom
--tone-map HDR tone-mapping
--re-sync RE_SYNC Sync offset for image generation in frames (i.e. --re-sync=-3)
--comparison-count COMPARISON_COUNT
Amount of comparisons to generate
--subtitle-color SUBTITLE_COLOR
Hex color code for subtitle color
--release-sub-title RELEASE_SUB_TITLE
Release group subtitle name (this will show on the encode images)
```
## Supports
Windows 8 and up
## Requirements
You will need lsmash, ffms2, libfpng, libimwri, and SubText vapoursynth plugins.
Place all of these in a folder 'img_plugins' beside the script/executable.

78
build.py Normal file

@ -0,0 +1,78 @@
from pathlib import Path
from subprocess import run
import os
import shutil
import sys
def build_app():
# Change directory to the project's root directory
project_root = Path(__file__).parent
os.chdir(project_root)
# Ensure we're in a virtual env, if we are, install dependencies using Poetry
if sys.prefix == sys.base_prefix:
raise Exception("You must activate your virtual environment first")
else:
# Use Poetry to install dependencies
run(["poetry", "install"])
# Define the PyInstaller output path
pyinstaller_folder = project_root / "pyinstaller_build"
# Delete the old build folder if it exists
shutil.rmtree(pyinstaller_folder, ignore_errors=True)
# Create a folder for the PyInstaller output
pyinstaller_folder.mkdir(exist_ok=True)
# Define paths before changing directory
img_gen = project_root / "image_generator.py"
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"
)
# Change directory so PyInstaller outputs all of its files in its own folder
os.chdir(pyinstaller_folder)
# Run PyInstaller command with Poetry's virtual environment
build_job = run(
[
"poetry",
"run",
"pyinstaller",
# "-w",
"--onefile",
f"--icon={icon_path}",
"--add-data",
f"{vapoursynth_64};vapoursynth64",
"--add-data",
f"{vapoursynth_64_portable};.",
"--name",
"Image-Generator",
str(img_gen),
f"--additional-hooks-dir={str(additional_hooks_path)}",
]
)
# Ensure the output of the .exe
success = "Did not complete successfully"
exe_path = project_root / pyinstaller_folder / "dist" / "Image-Generator.exe"
if exe_path.is_file() and str(build_job.returncode) == "0":
success = f"\nSuccess!\nPath to exe: {str(exe_path)}"
# Change directory back to the original directory
os.chdir(project_root)
# Return a success message
return success
if __name__ == "__main__":
build = build_app()
print(build)

27
hooks/hook-vapoursynth.py Normal file

@ -0,0 +1,27 @@
hiddenimports = [
"os",
"enum",
"ctypes",
"threading",
"traceback",
"gc",
"sys",
"inspect",
"weakref",
"atexit",
"contextlib",
"logging",
"functools",
"typing",
"traceback",
"warnings",
"threading.local",
"threading.Lock",
"threading.RLock",
"types.MappingProxyType",
"collections.namedtuple",
"collections.abc.Iterable",
"collections.abc.Mapping",
"concurrent.futures.Future",
"fractions",
]

110
image_generator.py Normal file

@ -0,0 +1,110 @@
from pathlib import Path
from argparse import ArgumentParser
from image_generator import GenerateImages
from image_generator.exceptions import ImageGeneratorError
from image_generator.utils import exit_application
program_name = "Comparison Image Generator"
__version__ = "1.1.0"
if __name__ == "__main__":
parser = ArgumentParser(prog=program_name)
parser.add_argument(
"-v", "--version", action="version", version=f"{program_name} v{__version__}"
)
parser.add_argument("--source", type=str, help="Path to source file")
parser.add_argument("--encode", type=str, help="Path to encode file")
parser.add_argument("--image-dir", type=str, help="Path to base image folder")
parser.add_argument(
"--indexer",
type=str,
choices=["lsmash", "ffms2"],
help="Indexer choice",
)
parser.add_argument("--index-dir", type=str, help="Path to look/create indexes")
parser.add_argument("--sub-size", type=int, help="Size of subtitles")
parser.add_argument("--left-crop", type=int, help="Left crop")
parser.add_argument("--right-crop", type=int, help="Right crop")
parser.add_argument("--top-crop", type=int, help="Top crop")
parser.add_argument("--bottom-crop", type=int, help=" crop")
parser.add_argument("--adv-resize-left", type=float, help="Advanced resize left")
parser.add_argument("--adv-resize-right", type=float, help="Advanced resize right")
parser.add_argument("--adv-resize-top", type=float, help="Advanced resize top")
parser.add_argument(
"--adv-resize-bottom", type=float, help="Advanced resize bottom"
)
parser.add_argument("--tone-map", action="store_true", help="HDR tone-mapping")
parser.add_argument(
"--re-sync",
type=str,
help="Sync offset for image generation in frames (i.e. --re-sync=-3)",
)
parser.add_argument(
"--comparison-count", type=int, help="Amount of comparisons to generate"
)
parser.add_argument(
"--subtitle-color", type=str, help="Hex color code for subtitle color"
)
parser.add_argument(
"--release-sub-title",
type=str,
help="Release group subtitle name (this will show on the encode images)",
)
args = parser.parse_args()
if not any(vars(args).values()):
parser.print_help()
exit_application("", 1)
if not args.source or not Path(args.source).is_file():
exit_application(
"Source input is not detected (--source 'path to file')",
1,
)
if not args.encode or not Path(args.encode).is_file():
exit_application(
"Encode input is not detected (--encode 'path to file')",
1,
)
if args.image_dir:
image_dir = Path(args.image_dir)
else:
image_dir = Path(Path(args.encode).parent / f"{Path(args.encode).stem}_images")
image_dir.mkdir(parents=True, exist_ok=True)
try:
img_generator = GenerateImages(
source_file=Path(args.source),
encode_file=Path(args.encode),
image_dir=image_dir,
indexer=args.indexer,
index_directory=args.index_dir,
sub_size=args.sub_size,
left_crop=args.left_crop,
right_crop=args.right_crop,
top_crop=args.top_crop,
bottom_crop=args.bottom_crop,
adv_resize_left=args.adv_resize_left,
adv_resize_right=args.adv_resize_right,
adv_resize_top=args.adv_resize_top,
adv_resize_bottom=args.adv_resize_bottom,
tone_map=args.tone_map,
re_sync=args.re_sync,
comparison_count=int(args.comparison_count)
if args.comparison_count
else 20,
subtitle_color=args.subtitle_color,
release_sub_title=args.release_sub_title,
)
img_gen = img_generator.process_images()
if img_gen:
exit_application(f"Output: {img_gen}", 0)
except ImageGeneratorError as error:
exit_application(error, 1)

585
image_generator/__init__.py Normal file

@ -0,0 +1,585 @@
import re
import shutil
from random import choice
from pathlib import Path
from numpy import linspace
from unidecode import unidecode
import awsmfunc
import vapoursynth as vs
from image_generator.exceptions import ImageGeneratorError
from image_generator.utils import get_working_dir, hex_to_bgr
class GenerateImages:
def __init__(
self,
source_file: Path,
encode_file: Path,
image_dir: Path,
indexer: str,
index_directory: None | str,
sub_size: int,
left_crop: int,
right_crop: int,
top_crop: int,
bottom_crop: int,
adv_resize_left: float,
adv_resize_right: float,
adv_resize_top: float,
adv_resize_bottom: float,
tone_map: bool,
re_sync: str,
comparison_count: int,
subtitle_color: str,
release_sub_title: str | None,
):
self.source_file = source_file
self.source_node = None
self.reference_source_file = None
self.encode_file = encode_file
self.encode_node = None
self.image_dir = image_dir
self.indexer = indexer
self.index_dir = index_directory
self.sub_size = sub_size
self.left_crop = left_crop
self.right_crop = right_crop
self.top_crop = top_crop
self.bottom_crop = bottom_crop
self.adv_resize_left = adv_resize_left
self.adv_resize_right = adv_resize_right
self.adv_resize_top = adv_resize_top
self.adv_resize_bottom = adv_resize_bottom
self.tone_map = tone_map
self.re_sync = re_sync
self.comparison_count = comparison_count
self.subtitle_color = subtitle_color
self.release_sub_title = release_sub_title
self.core = vs.core
self.load_plugins()
def process_images(self):
if self.indexer == "lsmash":
self.index_lsmash()
elif self.indexer == "ffms2":
self.index_ffms2()
num_source_frames = len(self.source_node)
num_encode_frames = len(self.encode_node)
# Format: Name, Fontname, Font-size, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic,
# Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL,
# MarginR, MarginV,
# bgr color
color = "&H000ac7f5"
if self.subtitle_color:
color = hex_to_bgr(self.subtitle_color)
selected_sub_style = (
f"Segoe UI,{self.sub_size},{color},&H00000000,&H00000000,&H00000000,"
"1,0,0,0,100,100,0,0,1,1,0,7,5,0,0,1"
)
self.check_de_interlaced(num_source_frames, num_encode_frames)
b_frames = self.get_b_frames(num_source_frames)
screenshot_comparison_dir, screenshot_sync_dir = self.generate_folders()
self.handle_crop()
self.handle_resize()
self.handle_hdr()
vs_source_info, vs_encode_info = self.handle_subtitles(selected_sub_style)
img_job = self.generate_screens(
b_frames,
vs_source_info,
vs_encode_info,
screenshot_comparison_dir,
screenshot_sync_dir,
selected_sub_style,
)
return img_job
@staticmethod
def screen_gen_callback(sg_call_back):
print(
str(sg_call_back).replace("ScreenGen: ", "").replace("\n", "").strip(),
flush=True,
)
def generate_screens(
self,
b_frames,
vs_source_info,
vs_encode_info,
screenshot_comparison_dir,
screenshot_sync_dir,
selected_sub_style,
):
print("\nGenerating screenshots, please wait", flush=True)
# handle re_sync if needed
sync_frames = []
if self.re_sync:
get_sync_digits = re.search(r"(\d+)", self.re_sync)
sync_digits = int(get_sync_digits.group(1)) if get_sync_digits else 0
for x_frames in b_frames:
if self.re_sync.startswith("-"):
sync_frames.append(int(x_frames) - sync_digits)
else:
sync_frames.append(int(x_frames) + sync_digits)
else:
sync_frames = b_frames
# generate source images
awsmfunc.ScreenGen(
vs_source_info,
frame_numbers=sync_frames,
fpng_compression=1,
folder=screenshot_comparison_dir,
suffix="a_source__%d",
callback=self.screen_gen_callback,
)
# generate encode images
awsmfunc.ScreenGen(
vs_encode_info,
frame_numbers=b_frames,
fpng_compression=1,
folder=screenshot_comparison_dir,
suffix="b_encode__%d",
callback=self.screen_gen_callback,
)
# generate some sync frames
print("\nGenerating a few sync frames", flush=True)
# select two frames randomly from list
sync_1 = choice(b_frames)
remove_sync1 = b_frames.copy()
remove_sync1.remove(sync_1)
sync_2 = choice(remove_sync1)
# reference subs
vs_source_ref_info = self.core.sub.Subtitle(
clip=self.source_node, text="Sync", style=selected_sub_style
)
vs_encode_ref_info = self.core.sub.Subtitle(
clip=self.encode_node, text="Reference", style=selected_sub_style
)
# generate screens for the two reference frames
awsmfunc.ScreenGen(
vs_encode_ref_info,
frame_numbers=[sync_1, sync_2],
fpng_compression=1,
folder=screenshot_sync_dir,
suffix="b_encode__%d",
callback=self.screen_gen_callback,
)
# generate 10 source frames around those reference frames
awsmfunc.ScreenGen(
vs_source_ref_info,
frame_numbers=[
sync_1 - 4,
sync_1 - 3,
sync_1 - 2,
sync_1 - 1,
sync_1,
sync_1 + 1,
sync_1 + 2,
sync_1 + 3,
sync_1 + 4,
],
fpng_compression=1,
folder=Path(Path(screenshot_sync_dir) / "sync1"),
suffix="a_source__%d",
callback=self.screen_gen_callback,
)
awsmfunc.ScreenGen(
vs_source_ref_info,
frame_numbers=[
sync_2 - 4,
sync_2 - 3,
sync_2 - 2,
sync_2 - 1,
sync_2,
sync_2 + 1,
sync_2 + 2,
sync_2 + 3,
sync_2 + 4,
],
fpng_compression=1,
folder=Path(Path(screenshot_sync_dir) / "sync2"),
suffix="a_source__%d",
callback=self.screen_gen_callback,
)
print("Screen generation completed", flush=True)
return str(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(
clip=self.encode_node,
title=self.release_sub_title if self.release_sub_title else "",
style=selected_sub_style,
)
return vs_source_info, vs_encode_info
def handle_hdr(self):
if self.tone_map:
self.source_node = awsmfunc.DynamicTonemap(
clip=self.source_node, libplacebo=False
)
self.encode_node = awsmfunc.DynamicTonemap(
clip=self.encode_node,
reference=self.reference_source_file,
libplacebo=False,
)
def handle_resize(self):
if (
self.source_node.width != self.encode_node.width
and self.source_node.height != self.encode_node.height
or any(
[
self.adv_resize_left,
self.adv_resize_right,
self.adv_resize_top,
self.adv_resize_bottom,
]
)
):
# advanced resize offset vars
advanced_resize_left = self.adv_resize_left if self.adv_resize_left else 0
advanced_resize_top = self.adv_resize_top if self.adv_resize_top else 0
advanced_resize_width = (
self.adv_resize_right if self.adv_resize_right else 0
)
advanced_resize_height = (
self.adv_resize_bottom if self.adv_resize_bottom else 0
)
# resize source to match encode for screenshots
self.source_node = self.core.resize.Spline36(
self.source_node,
width=int(self.encode_node.width),
height=int(self.encode_node.height),
src_left=advanced_resize_left,
src_top=advanced_resize_top,
src_width=float(
self.source_node.width
- (advanced_resize_left + advanced_resize_width)
),
src_height=float(
self.source_node.height
- (advanced_resize_top + advanced_resize_height)
),
dither_type="error_diffusion",
)
def handle_crop(self):
if any([self.left_crop, self.right_crop, self.top_crop, self.bottom_crop]):
self.source_node = self.core.std.Crop(
self.source_node,
left=self.left_crop if self.left_crop else 0,
right=self.right_crop if self.right_crop else 0,
top=self.top_crop if self.top_crop else 0,
bottom=self.bottom_crop if self.bottom_crop else 0,
)
def generate_folders(self):
print("\nCreating folders for images", flush=True)
if self.image_dir:
image_output_dir = Path(self.image_dir)
else:
image_output_dir = Path(
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)
# 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
def get_b_frames(self, num_source_frames):
print(
f"\nGenerating {self.comparison_count} 'B' frames for " "comparison images",
flush=True,
)
b_frames = list(
linspace(
int(num_source_frames * 0.15),
int(num_source_frames * 0.75),
int(self.comparison_count),
).astype(int)
)
try:
for i, frame in enumerate(b_frames):
while (
self.encode_node.get_frame(frame).props["_PictType"].decode() != "B"
):
frame += 1
b_frames[i] = frame
except ValueError:
raise ImageGeneratorError(
"Error! Your encode file is likely an incomplete or corrupted encode"
)
print(f"Finished generating {self.comparison_count} 'B' frames", flush=True)
return b_frames
def check_de_interlaced(self, num_source_frames, num_encode_frames):
print("\nChecking if encode has been de-interlaced", flush=True)
try:
source_fps = float(self.source_node.fps)
encode_fps = float(self.encode_node.fps)
if source_fps != encode_fps:
if num_source_frames == num_encode_frames:
self.source_node = self.core.std.AssumeFPS(
self.source_node,
fpsnum=self.encode_node.fps.numerator,
fpsden=self.encode_node.fps.denominator,
)
print(
"Adjusting source fps to match the encode using AssumeFPS() on the source",
flush=True,
)
else:
even_frames_for = ""
if num_source_frames != num_encode_frames:
file_differences = float(num_source_frames / num_encode_frames)
if file_differences > 1.01:
even_frames_for = "source"
self.source_node = self.core.std.SelectEvery(
self.source_node, cycle=2, offsets=0
)
elif file_differences < 0.99:
even_frames_for = "encode"
self.encode_node = self.core.std.SelectEvery(
self.encode_node, cycle=2, offsets=0
)
print(
f"Source: FPS={source_fps} Frames={num_source_frames}\n"
f"Encode: FPS={encode_fps} Frames={num_encode_frames}\n"
"Source vs Encode appears to be different, we're going to assume encode has been"
f" de-interlaced, automatically generating even frames for {even_frames_for}",
flush=True,
)
else:
print("No de-interlacing detected", flush=True)
except ValueError:
print(
"There was an error while detecting source or encode fps, attempting to continue",
flush=True,
)
def index_lsmash(self):
print("Indexing source", flush=True)
# index source file
# if index is found in the StaxRip temp working directory, attempt to use it
if (
Path(str(Path(self.source_file).with_suffix("")) + "_temp/").is_dir()
and Path(
str(Path(self.source_file).with_suffix("")) + "_temp/temp.lwi"
).is_file()
):
print("Index found in StaxRip temp, attempting to use", flush=True)
# define cache path
lwi_cache_path = Path(
str(Path(self.source_file).with_suffix("")) + "_temp/temp.lwi"
)
# try to use index on source file with the cache path
try:
self.source_node = self.core.lsmas.LWLibavSource(
source=self.source_file, cachefile=lwi_cache_path
)
self.reference_source_file = self.core.lsmas.LWLibavSource(
source=self.source_file, cachefile=lwi_cache_path
)
print("Using existing index", flush=True)
# if index cannot be used
except vs.Error:
print("L-Smash version miss-match, indexing source again", flush=True)
# index source file
self.source_node = self.core.lsmas.LWLibavSource(self.source_file)
self.reference_source_file = self.core.lsmas.LWLibavSource(
self.source_file
)
# if no existing index is found index source file
else:
cache_path = Path(Path(self.source_file).with_suffix(".lwi"))
try:
# create index
self.source_node = self.core.lsmas.LWLibavSource(
self.source_file, cachefile=cache_path
)
self.reference_source_file = self.core.lsmas.LWLibavSource(
self.source_file, cachefile=cache_path
)
except vs.Error:
# delete index
Path(self.source_file).with_suffix(".lwi").unlink(missing_ok=True)
# create index
self.source_node = self.core.lsmas.LWLibavSource(
self.source_file, cachefile=cache_path
)
self.reference_source_file = self.core.lsmas.LWLibavSource(
self.source_file, cachefile=cache_path
)
print("Source index completed\n\nIndexing encode", flush=True)
# define a path for encode index to go
if self.index_dir:
index_base_path = Path(self.index_dir) / Path(self.encode_file).name
cache_path_enc = index_base_path.with_suffix(".lwi")
else:
cache_path_enc = Path(Path(self.encode_file).with_suffix(".lwi"))
try:
# create index
self.encode_node = self.core.lsmas.LWLibavSource(
self.encode_file, cachefile=cache_path_enc
)
except vs.Error:
# delete index
cache_path_enc.unlink(missing_ok=True)
# create index
self.encode_node = self.core.lsmas.LWLibavSource(
self.encode_file, cachefile=cache_path_enc
)
print("Encode index completed", flush=True)
def index_ffms2(self):
print("Indexing source", flush=True)
# index source file
# if index is found in the StaxRip temp working directory, attempt to use it
if (
Path(str(Path(self.source_file).with_suffix("")) + "_temp/").is_dir()
and Path(
str(Path(self.source_file).with_suffix("")) + "_temp/temp.ffindex"
).is_file()
):
print("Index found in StaxRip temp, attempting to use", flush=True)
# define cache path
ffindex_cache_path = Path(
str(Path(self.source_file).with_suffix("")) + "_temp/temp.ffindex"
)
# try to use index on source file with the cache path
try:
self.source_node = self.core.ffms2.Source(
source=self.source_file, cachefile=ffindex_cache_path
)
self.reference_source_file = self.core.ffms2.Source(
source=self.source_file, cachefile=ffindex_cache_path
)
print("Using existing index", flush=True)
# if index cannot be used
except vs.Error:
print("FFMS2 version miss-match, indexing source again", flush=True)
# index source file
self.source_node = self.core.ffms2.Source(self.source_file)
self.reference_source_file = self.core.ffms2.Source(self.source_file)
# if no existing index is found index source file
else:
try:
# create index
print(
"FFMS2 library doesn't allow progress, please wait while the index is completed",
flush=True,
)
self.source_node = self.core.ffms2.Source(self.source_file)
self.reference_source_file = self.core.ffms2.Source(self.source_file)
except vs.Error:
# delete index
Path(self.source_file).with_suffix(".ffindex").unlink(missing_ok=True)
# create index
print(
"FFMS2 library doesn't allow progress, please wait while the index is completed",
flush=True,
)
self.source_node = self.core.ffms2.Source(self.source_file)
self.reference_source_file = self.core.ffms2.Source(self.source_file)
print("Source index completed\n\nIndexing encode", flush=True)
# define a path for encode index to go
if self.index_dir:
index_base_path = Path(self.index_dir) / Path(self.encode_file).name
cache_path_enc = Path(str(index_base_path) + ".ffindex")
else:
cache_path_enc = Path(self.encode_file + ".ffindex")
try:
self.encode_node = self.core.ffms2.Source(
self.encode_file, cachefile=cache_path_enc
)
except vs.Error:
cache_path_enc.unlink(missing_ok=True)
self.encode_node = self.core.ffms2.Source(
self.encode_file, cachefile=cache_path_enc
)
print("Encode index completed", flush=True)
def load_plugins(self):
plugin_path = get_working_dir() / "img_plugins"
if not plugin_path.is_dir() and not plugin_path.exists():
raise ImageGeneratorError("Can not detect plugin directory")
else:
for plugin in plugin_path.glob("*.dll"):
self.core.std.LoadPlugin(Path(plugin).resolve())

@ -0,0 +1,2 @@
class ImageGeneratorError(Exception):
"""Handle all exceptions thrown by image generation"""

48
image_generator/utils.py Normal file

@ -0,0 +1,48 @@
import sys
from pathlib import Path
def exit_application(msg: str, exit_code: int = 0):
"""A clean way to exit the program without raising traceback errors
Args:
msg (str): Success or Error message you'd like to display in the console
exit_code (int): Can either be 0 (success) or 1 (fail)
"""
if exit_code not in {0, 1}:
raise ValueError("exit_code must only be '0' or '1' (int)")
if exit_code == 0:
output = sys.stdout
elif exit_code == 1:
output = sys.stderr
print(msg, file=output, flush=True)
sys.exit(exit_code)
def get_working_dir():
"""
Used to determine the correct working directory automatically.
This way we can utilize files/relative paths easily.
Returns:
(Path): Current working directory
"""
# we're in a pyinstaller bundle
if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
return Path(sys.executable).parent
# we're running from a *.py file
else:
return Path.cwd()
def hex_to_bgr(hex_code):
# Remove the '#' if present
hex_code = hex_code.lstrip("#")
# Convert hex to BGR format
bgr_values = f"&H{int(hex_code[4:6], 16):02X}{int(hex_code[2:4], 16):02X}{int(hex_code[0:2], 16):02X}"
return bgr_values

BIN
images/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

349
poetry.lock generated Normal file

@ -0,0 +1,349 @@
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
[[package]]
name = "altgraph"
version = "0.17.4"
description = "Python graph (network) package"
optional = false
python-versions = "*"
files = [
{file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"},
{file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"},
]
[[package]]
name = "awsmfunc"
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"},
]
[package.dependencies]
numpy = "*"
VapourSynth = ">=57"
vs-rekt = ">=1.0.0"
vsutil = ">=0.7.0"
[package.extras]
dev = ["pylint", "toml", "yapf"]
[[package]]
name = "black"
version = "23.10.1"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.8"
files = [
{file = "black-23.10.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:ec3f8e6234c4e46ff9e16d9ae96f4ef69fa328bb4ad08198c8cee45bb1f08c69"},
{file = "black-23.10.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:1b917a2aa020ca600483a7b340c165970b26e9029067f019e3755b56e8dd5916"},
{file = "black-23.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c74de4c77b849e6359c6f01987e94873c707098322b91490d24296f66d067dc"},
{file = "black-23.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:7b4d10b0f016616a0d93d24a448100adf1699712fb7a4efd0e2c32bbb219b173"},
{file = "black-23.10.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b15b75fc53a2fbcac8a87d3e20f69874d161beef13954747e053bca7a1ce53a0"},
{file = "black-23.10.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:e293e4c2f4a992b980032bbd62df07c1bcff82d6964d6c9496f2cd726e246ace"},
{file = "black-23.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d56124b7a61d092cb52cce34182a5280e160e6aff3137172a68c2c2c4b76bcb"},
{file = "black-23.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:3f157a8945a7b2d424da3335f7ace89c14a3b0625e6593d21139c2d8214d55ce"},
{file = "black-23.10.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:cfcce6f0a384d0da692119f2d72d79ed07c7159879d0bb1bb32d2e443382bf3a"},
{file = "black-23.10.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:33d40f5b06be80c1bbce17b173cda17994fbad096ce60eb22054da021bf933d1"},
{file = "black-23.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:840015166dbdfbc47992871325799fd2dc0dcf9395e401ada6d88fe11498abad"},
{file = "black-23.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:037e9b4664cafda5f025a1728c50a9e9aedb99a759c89f760bd83730e76ba884"},
{file = "black-23.10.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:7cb5936e686e782fddb1c73f8aa6f459e1ad38a6a7b0e54b403f1f05a1507ee9"},
{file = "black-23.10.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:7670242e90dc129c539e9ca17665e39a146a761e681805c54fbd86015c7c84f7"},
{file = "black-23.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed45ac9a613fb52dad3b61c8dea2ec9510bf3108d4db88422bacc7d1ba1243d"},
{file = "black-23.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:6d23d7822140e3fef190734216cefb262521789367fbdc0b3f22af6744058982"},
{file = "black-23.10.1-py3-none-any.whl", hash = "sha256:d431e6739f727bb2e0495df64a6c7a5310758e87505f5f8cde9ff6c0f2d7e4fe"},
{file = "black-23.10.1.tar.gz", hash = "sha256:1f8ce316753428ff68749c65a5f7844631aa18c8679dfd3ca9dc1a289979c258"},
]
[package.dependencies]
click = ">=8.0.0"
mypy-extensions = ">=0.4.3"
packaging = ">=22.0"
pathspec = ">=0.9.0"
platformdirs = ">=2"
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "click"
version = "8.1.7"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.7"
files = [
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
]
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "macholib"
version = "1.16.3"
description = "Mach-O header analysis and editing"
optional = false
python-versions = "*"
files = [
{file = "macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c"},
{file = "macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30"},
]
[package.dependencies]
altgraph = ">=0.17"
[[package]]
name = "mypy-extensions"
version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.5"
files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
]
[[package]]
name = "numpy"
version = "1.26.2"
description = "Fundamental package for array computing in Python"
optional = false
python-versions = ">=3.9"
files = [
{file = "numpy-1.26.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3703fc9258a4a122d17043e57b35e5ef1c5a5837c3db8be396c82e04c1cf9b0f"},
{file = "numpy-1.26.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cc392fdcbd21d4be6ae1bb4475a03ce3b025cd49a9be5345d76d7585aea69440"},
{file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36340109af8da8805d8851ef1d74761b3b88e81a9bd80b290bbfed61bd2b4f75"},
{file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcc008217145b3d77abd3e4d5ef586e3bdfba8fe17940769f8aa09b99e856c00"},
{file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ced40d4e9e18242f70dd02d739e44698df3dcb010d31f495ff00a31ef6014fe"},
{file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b272d4cecc32c9e19911891446b72e986157e6a1809b7b56518b4f3755267523"},
{file = "numpy-1.26.2-cp310-cp310-win32.whl", hash = "sha256:22f8fc02fdbc829e7a8c578dd8d2e15a9074b630d4da29cda483337e300e3ee9"},
{file = "numpy-1.26.2-cp310-cp310-win_amd64.whl", hash = "sha256:26c9d33f8e8b846d5a65dd068c14e04018d05533b348d9eaeef6c1bd787f9919"},
{file = "numpy-1.26.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b96e7b9c624ef3ae2ae0e04fa9b460f6b9f17ad8b4bec6d7756510f1f6c0c841"},
{file = "numpy-1.26.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aa18428111fb9a591d7a9cc1b48150097ba6a7e8299fb56bdf574df650e7d1f1"},
{file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06fa1ed84aa60ea6ef9f91ba57b5ed963c3729534e6e54055fc151fad0423f0a"},
{file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96ca5482c3dbdd051bcd1fce8034603d6ebfc125a7bd59f55b40d8f5d246832b"},
{file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:854ab91a2906ef29dc3925a064fcd365c7b4da743f84b123002f6139bcb3f8a7"},
{file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f43740ab089277d403aa07567be138fc2a89d4d9892d113b76153e0e412409f8"},
{file = "numpy-1.26.2-cp311-cp311-win32.whl", hash = "sha256:a2bbc29fcb1771cd7b7425f98b05307776a6baf43035d3b80c4b0f29e9545186"},
{file = "numpy-1.26.2-cp311-cp311-win_amd64.whl", hash = "sha256:2b3fca8a5b00184828d12b073af4d0fc5fdd94b1632c2477526f6bd7842d700d"},
{file = "numpy-1.26.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a4cd6ed4a339c21f1d1b0fdf13426cb3b284555c27ac2f156dfdaaa7e16bfab0"},
{file = "numpy-1.26.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d5244aabd6ed7f312268b9247be47343a654ebea52a60f002dc70c769048e75"},
{file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a3cdb4d9c70e6b8c0814239ead47da00934666f668426fc6e94cce869e13fd7"},
{file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa317b2325f7aa0a9471663e6093c210cb2ae9c0ad824732b307d2c51983d5b6"},
{file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:174a8880739c16c925799c018f3f55b8130c1f7c8e75ab0a6fa9d41cab092fd6"},
{file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f79b231bf5c16b1f39c7f4875e1ded36abee1591e98742b05d8a0fb55d8a3eec"},
{file = "numpy-1.26.2-cp312-cp312-win32.whl", hash = "sha256:4a06263321dfd3598cacb252f51e521a8cb4b6df471bb12a7ee5cbab20ea9167"},
{file = "numpy-1.26.2-cp312-cp312-win_amd64.whl", hash = "sha256:b04f5dc6b3efdaab541f7857351aac359e6ae3c126e2edb376929bd3b7f92d7e"},
{file = "numpy-1.26.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4eb8df4bf8d3d90d091e0146f6c28492b0be84da3e409ebef54349f71ed271ef"},
{file = "numpy-1.26.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a13860fdcd95de7cf58bd6f8bc5a5ef81c0b0625eb2c9a783948847abbef2c2"},
{file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64308ebc366a8ed63fd0bf426b6a9468060962f1a4339ab1074c228fa6ade8e3"},
{file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baf8aab04a2c0e859da118f0b38617e5ee65d75b83795055fb66c0d5e9e9b818"},
{file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d73a3abcac238250091b11caef9ad12413dab01669511779bc9b29261dd50210"},
{file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b361d369fc7e5e1714cf827b731ca32bff8d411212fccd29ad98ad622449cc36"},
{file = "numpy-1.26.2-cp39-cp39-win32.whl", hash = "sha256:bd3f0091e845164a20bd5a326860c840fe2af79fa12e0469a12768a3ec578d80"},
{file = "numpy-1.26.2-cp39-cp39-win_amd64.whl", hash = "sha256:2beef57fb031dcc0dc8fa4fe297a742027b954949cabb52a2a376c144e5e6060"},
{file = "numpy-1.26.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1cc3d5029a30fb5f06704ad6b23b35e11309491c999838c31f124fee32107c79"},
{file = "numpy-1.26.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94cc3c222bb9fb5a12e334d0479b97bb2df446fbe622b470928f5284ffca3f8d"},
{file = "numpy-1.26.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe6b44fb8fcdf7eda4ef4461b97b3f63c466b27ab151bec2366db8b197387841"},
{file = "numpy-1.26.2.tar.gz", hash = "sha256:f65738447676ab5777f11e6bbbdb8ce11b785e105f690bc45966574816b6d3ea"},
]
[[package]]
name = "packaging"
version = "23.2"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.7"
files = [
{file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
]
[[package]]
name = "pathspec"
version = "0.11.2"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
python-versions = ">=3.7"
files = [
{file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"},
{file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"},
]
[[package]]
name = "pefile"
version = "2023.2.7"
description = "Python PE parsing module"
optional = false
python-versions = ">=3.6.0"
files = [
{file = "pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6"},
{file = "pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc"},
]
[[package]]
name = "platformdirs"
version = "3.11.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
optional = false
python-versions = ">=3.7"
files = [
{file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"},
{file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"},
]
[package.extras]
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
[[package]]
name = "pyinstaller"
version = "6.3.0"
description = "PyInstaller bundles a Python application and all its dependencies into a single package."
optional = false
python-versions = "<3.13,>=3.8"
files = []
develop = true
[package.dependencies]
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"
pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""}
setuptools = ">=42.0.0"
[package.extras]
completion = ["argcomplete"]
hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"]
[package.source]
type = "directory"
url = "custom-pyinstaller"
[[package]]
name = "pyinstaller-hooks-contrib"
version = "2023.11"
description = "Community maintained hooks for PyInstaller"
optional = false
python-versions = ">=3.7"
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"},
]
[[package]]
name = "pywin32-ctypes"
version = "0.2.2"
description = "A (partial) reimplementation of pywin32 using ctypes/cffi"
optional = false
python-versions = ">=3.6"
files = [
{file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"},
{file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"},
]
[[package]]
name = "setuptools"
version = "69.0.3"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"},
{file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
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"
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"},
]
[[package]]
name = "vapoursynth-portable"
version = "64"
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"},
]
[package.dependencies]
vapoursynth = "64"
[[package]]
name = "vs-rekt"
version = "1.0.0"
description = "VapourSynth wrapper for Cropping and Stacking clips."
optional = false
python-versions = "*"
files = [
{file = "vs-rekt-1.0.0.tar.gz", hash = "sha256:24fbc0b577074e841b80c7d02b7ab2c88c1a5c703276d37dea530d6ba109ae31"},
{file = "vs_rekt-1.0.0-py3-none-any.whl", hash = "sha256:e217d00c5bdb746d5124f9a10a50e90fc0f1f170bef02c89a4cb036547d2d132"},
]
[package.dependencies]
VapourSynth = ">=57"
vsutil = ">=0.7.0"
[[package]]
name = "vsutil"
version = "0.8.0"
description = "A collection of general-purpose Vapoursynth functions to be reused in modules and scripts."
optional = false
python-versions = ">=3.8"
files = [
{file = "vsutil-0.8.0-py3-none-any.whl", hash = "sha256:b14751680f1aa47a8671a943932e160fc3b6029b3e60227875fd73d7a47dac86"},
{file = "vsutil-0.8.0.tar.gz", hash = "sha256:e01831203dfb9c3af86a101fba5e7fe04e3686bddec94bb3057ca73e84c98768"},
]
[package.dependencies]
vapoursynth = "*"
[metadata]
lock-version = "2.0"
python-versions = "3.11.5"
content-hash = "820bfb62c5fba1b17221bc1a02144932dbce64fa44c698f24f7205c2fc4a3d18"

23
pyproject.toml Normal file

@ -0,0 +1,23 @@
[tool.poetry]
name = "image-generator"
version = "1.1.0"
description = "CLI to offload image generation to"
authors = ["jlw4049 <jlw_4049@hotmail.com>"]
license = "MIT"
readme = "README.md"
[tool.poetry.dependencies]
python = "3.11.5"
awsmfunc = "^1.3.4"
unidecode = "^1.3.7"
vapoursynth-portable = "64"
numpy = "^1.26.2"
[tool.poetry.group.dev.dependencies]
black = "^23.10.1"
pyinstaller = {path = "custom-pyinstaller", develop = true}
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"