Compare commits
No commits in common. "master" and "1.0.0" have entirely different histories.
81
README.md
81
README.md
@ -1,17 +1,17 @@
|
|||||||
# Image-Generator
|
# Image-Generator
|
||||||
|
|
||||||
A CLI to generate comparison image sets utilizing the power of VapourSynth
|
A CLI to generate comparison image sets with
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
usage: FrameForge [-h] [-v] [--source SOURCE] [--encode ENCODE] [--frames FRAMES] [--image-dir IMAGE_DIR]
|
usage: Comparison Image Generator [-h] [-v] [--source SOURCE] [--encode ENCODE] [--image-dir IMAGE_DIR]
|
||||||
[--indexer {lsmash,ffms2}] [--img-lib {imwri,fpng}] [--source-index-path SOURCE_INDEX_PATH]
|
[--indexer {lsmash,ffms2}] [--index-dir INDEX_DIR] [--sub-size SUB_SIZE]
|
||||||
[--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]
|
||||||
[--left-crop LEFT_CROP] [--right-crop RIGHT_CROP] [--top-crop TOP_CROP] [--bottom-crop BOTTOM_CROP]
|
[--bottom-crop BOTTOM_CROP] [--adv-resize-left ADV_RESIZE_LEFT]
|
||||||
[--adv-resize-left ADV_RESIZE_LEFT] [--adv-resize-right ADV_RESIZE_RIGHT]
|
[--adv-resize-right ADV_RESIZE_RIGHT] [--adv-resize-top ADV_RESIZE_TOP]
|
||||||
[--adv-resize-top ADV_RESIZE_TOP] [--adv-resize-bottom ADV_RESIZE_BOTTOM] [--tone-map]
|
[--adv-resize-bottom ADV_RESIZE_BOTTOM] [--tone-map] [--re-sync RE_SYNC]
|
||||||
[--re-sync RE_SYNC] [--comparison-count COMPARISON_COUNT] [--subtitle-color SUBTITLE_COLOR]
|
[--comparison-count COMPARISON_COUNT] [--subtitle-color SUBTITLE_COLOR]
|
||||||
[--release-sub-title RELEASE_SUB_TITLE]
|
[--release-sub-title RELEASE_SUB_TITLE]
|
||||||
|
|
||||||
options:
|
options:
|
||||||
@ -19,20 +19,13 @@ options:
|
|||||||
-v, --version show program's version number and exit
|
-v, --version show program's version number and exit
|
||||||
--source SOURCE Path to source file
|
--source SOURCE Path to source file
|
||||||
--encode ENCODE Path to encode file
|
--encode ENCODE Path to encode file
|
||||||
--frames FRAMES Only use this if you want to specify the frames to generate, this disables sync frames
|
|
||||||
--image-dir IMAGE_DIR
|
--image-dir IMAGE_DIR
|
||||||
Path to base image folder
|
Path to base image folder
|
||||||
--indexer {lsmash,ffms2}
|
--indexer {lsmash,ffms2}
|
||||||
Indexer choice
|
Indexer choice
|
||||||
--img-lib {imwri,fpng}
|
--index-dir INDEX_DIR
|
||||||
Image library to use
|
Path to look/create indexes
|
||||||
--source-index-path SOURCE_INDEX_PATH
|
|
||||||
Path to look/create indexes for source
|
|
||||||
--encode-index-path ENCODE_INDEX_PATH
|
|
||||||
Path to look/create indexes for encode
|
|
||||||
--sub-size SUB_SIZE Size of subtitles
|
--sub-size SUB_SIZE Size of subtitles
|
||||||
--sub-alignment SUB_ALIGNMENT
|
|
||||||
Alignment of subtitles (.ass)
|
|
||||||
--left-crop LEFT_CROP
|
--left-crop LEFT_CROP
|
||||||
Left crop
|
Left crop
|
||||||
--right-crop RIGHT_CROP
|
--right-crop RIGHT_CROP
|
||||||
@ -53,62 +46,16 @@ options:
|
|||||||
--comparison-count COMPARISON_COUNT
|
--comparison-count COMPARISON_COUNT
|
||||||
Amount of comparisons to generate
|
Amount of comparisons to generate
|
||||||
--subtitle-color SUBTITLE_COLOR
|
--subtitle-color SUBTITLE_COLOR
|
||||||
Hex color code for subtitle color (i.e. --subtitle-color "#fff000")
|
Hex color code for subtitle color
|
||||||
--release-sub-title RELEASE_SUB_TITLE
|
--release-sub-title RELEASE_SUB_TITLE
|
||||||
Release group subtitle name (this will show on the encode images)
|
Release group subtitle name (this will show on the encode images)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Supports
|
## Supports
|
||||||
|
|
||||||
Windows 8 and up (x64).
|
Windows 8 and up
|
||||||
Technically could support Linux/MacOS but binaries will only be compiled for Windows for now.
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
You will need lsmash, ffms2, libfpng, libimwri, and SubText vapoursynth plugins. For windows
|
You will need lsmash, ffms2, libfpng, libimwri, and SubText vapoursynth plugins.
|
||||||
I have compiled an executable with the needed runtime libraries.
|
Place all of these in a folder 'img_plugins' beside the script/executable.
|
||||||
|
|
||||||
## Example Usage
|
|
||||||
|
|
||||||
```
|
|
||||||
FrameForge.exe --source "path/Ant-Man.2015.mkv" --encode "path/Ant-Man.2015.Encoded.mkv" --sub-size 12 --indexer lsmash --top-crop 22 --bottom-crop 22 --subtitle-color #00FF00
|
|
||||||
|
|
||||||
Indexing source
|
|
||||||
...
|
|
||||||
Source index completed
|
|
||||||
|
|
||||||
Indexing encode
|
|
||||||
...
|
|
||||||
Encode index completed
|
|
||||||
|
|
||||||
Checking if encode has been de-interlaced
|
|
||||||
No de-interlacing detected
|
|
||||||
|
|
||||||
Generating 20 'B' frames for comparison images
|
|
||||||
Finished generating 20 'B' frames
|
|
||||||
|
|
||||||
Creating folders for images
|
|
||||||
Folder creation completed
|
|
||||||
|
|
||||||
Generating screenshots, please wait
|
|
||||||
Writing file: 01a_source__%d.png, frame: 25270
|
|
||||||
Writing file: 02a_source__%d.png, frame: 30590
|
|
||||||
...
|
|
||||||
|
|
||||||
Generating a few sync frames
|
|
||||||
Writing file: 01b_encode__%d.png, frame: 35910
|
|
||||||
Writing file: 02b_encode__%d.png, frame: 25270
|
|
||||||
...
|
|
||||||
|
|
||||||
Screen generation completed
|
|
||||||
Output: path/Ant-Man.2015_images/
|
|
||||||
```
|
|
||||||
|
|
||||||
[![04a_source__41230.png](https://thumbs2.imgbox.com/8d/47/X3l54wjy_t.png)](https://imgbox.com/X3l54wjy)
|
|
||||||
[![04b_encode__41230.png](https://thumbs2.imgbox.com/07/65/WvJ55AZp_t.png)](https://imgbox.com/WvJ55AZp)
|
|
||||||
|
|
||||||
[![11a_source__41230.png](https://thumbs2.imgbox.com/59/bd/woQpbay2_t.png)](https://imgbox.com/woQpbay2)
|
|
||||||
[![11b_encode__41230.png](https://thumbs2.imgbox.com/96/24/WzCtO3dY_t.png)](https://imgbox.com/WzCtO3dY)
|
|
||||||
|
|
||||||
[![16a_source__41230.png](https://thumbs2.imgbox.com/a8/5b/GftN1tCw_t.png)](https://imgbox.com/GftN1tCw)
|
|
||||||
[![16b_encode__41230.png](https://thumbs2.imgbox.com/c2/94/tFhRztHT_t.png)](https://imgbox.com/tFhRztHT)
|
|
||||||
|
22
build.py
22
build.py
@ -1,5 +1,5 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import run, PIPE
|
from subprocess import run
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
@ -31,23 +31,11 @@ def build_app():
|
|||||||
icon_path = project_root / "images" / "icon.ico"
|
icon_path = project_root / "images" / "icon.ico"
|
||||||
additional_hooks_path = Path(Path.cwd() / "hooks")
|
additional_hooks_path = Path(Path.cwd() / "hooks")
|
||||||
|
|
||||||
# get poetry venv path
|
# paths to needed vapoursynth files
|
||||||
poetry_venv_path = run(
|
vapoursynth_64 = project_root / ".venv" / "Lib" / "site-packages" / "vapoursynth64"
|
||||||
["cmd", "/c", "poetry", "env", "info", "--path"],
|
vapoursynth_64_portable = (
|
||||||
stdout=PIPE,
|
project_root / ".venv" / "Lib" / "site-packages" / "portable.vs"
|
||||||
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
|
# Change directory so PyInstaller outputs all of its files in its own folder
|
||||||
os.chdir(pyinstaller_folder)
|
os.chdir(pyinstaller_folder)
|
||||||
|
@ -3,11 +3,10 @@ from argparse import ArgumentParser
|
|||||||
from frame_forge import GenerateImages
|
from frame_forge import GenerateImages
|
||||||
from frame_forge.exceptions import FrameForgeError
|
from frame_forge.exceptions import FrameForgeError
|
||||||
from frame_forge.utils import exit_application
|
from frame_forge.utils import exit_application
|
||||||
from frame_forge.cli_utils import frame_list
|
|
||||||
|
|
||||||
|
|
||||||
program_name = "FrameForge"
|
program_name = "FrameForge"
|
||||||
__version__ = "1.3.3"
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@ -19,37 +18,15 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
parser.add_argument("--source", type=str, help="Path to source file")
|
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("--encode", type=str, help="Path to encode file")
|
||||||
parser.add_argument(
|
|
||||||
"--frames",
|
|
||||||
type=frame_list,
|
|
||||||
help="Only use this if you want to specify the "
|
|
||||||
"frames to generate, this disables sync frames",
|
|
||||||
)
|
|
||||||
parser.add_argument("--image-dir", type=str, help="Path to base image folder")
|
parser.add_argument("--image-dir", type=str, help="Path to base image folder")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--indexer",
|
"--indexer",
|
||||||
type=str,
|
type=str,
|
||||||
choices=["lsmash", "ffms2"],
|
choices=["lsmash", "ffms2"],
|
||||||
default="lsmash",
|
|
||||||
help="Indexer choice",
|
help="Indexer choice",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument("--index-dir", type=str, help="Path to look/create indexes")
|
||||||
"--img-lib",
|
parser.add_argument("--sub-size", type=int, help="Size of subtitles")
|
||||||
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"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--encode-index-path", type=str, help="Path to look/create indexes for encode"
|
|
||||||
)
|
|
||||||
parser.add_argument("--sub-size", type=int, default=20, help="Size of subtitles")
|
|
||||||
parser.add_argument(
|
|
||||||
"--sub-alignment", type=int, default=7, help="Alignment of subtitles (.ass)"
|
|
||||||
)
|
|
||||||
parser.add_argument("--left-crop", type=int, help="Left crop")
|
parser.add_argument("--left-crop", type=int, help="Left crop")
|
||||||
parser.add_argument("--right-crop", type=int, help="Right crop")
|
parser.add_argument("--right-crop", type=int, help="Right crop")
|
||||||
parser.add_argument("--top-crop", type=int, help="Top crop")
|
parser.add_argument("--top-crop", type=int, help="Top crop")
|
||||||
@ -70,9 +47,7 @@ if __name__ == "__main__":
|
|||||||
"--comparison-count", type=int, help="Amount of comparisons to generate"
|
"--comparison-count", type=int, help="Amount of comparisons to generate"
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--subtitle-color",
|
"--subtitle-color", type=str, help="Hex color code for subtitle color"
|
||||||
type=str,
|
|
||||||
help='Hex color code for subtitle color (i.e. --subtitle-color "#fff000")',
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--release-sub-title",
|
"--release-sub-title",
|
||||||
@ -98,16 +73,6 @@ if __name__ == "__main__":
|
|||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
|
||||||
index_suffix = ".lwi" if args.indexer == "lsmash" else ".ffindex"
|
|
||||||
for index_input in [args.source_index_path, args.encode_index_path]:
|
|
||||||
if index_input:
|
|
||||||
if Path(index_input).suffix != index_suffix:
|
|
||||||
exit_application(
|
|
||||||
f"When using {args.indexer} indexer you must use '{index_suffix}' "
|
|
||||||
"for your source/encode index path suffix",
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
|
|
||||||
if args.image_dir:
|
if args.image_dir:
|
||||||
image_dir = Path(args.image_dir)
|
image_dir = Path(args.image_dir)
|
||||||
else:
|
else:
|
||||||
@ -118,14 +83,10 @@ if __name__ == "__main__":
|
|||||||
img_generator = GenerateImages(
|
img_generator = GenerateImages(
|
||||||
source_file=Path(args.source),
|
source_file=Path(args.source),
|
||||||
encode_file=Path(args.encode),
|
encode_file=Path(args.encode),
|
||||||
frames=args.frames,
|
|
||||||
image_dir=image_dir,
|
image_dir=image_dir,
|
||||||
indexer=args.indexer,
|
indexer=args.indexer,
|
||||||
img_lib=args.img_lib,
|
index_directory=args.index_dir,
|
||||||
source_index_path=args.source_index_path,
|
|
||||||
encode_index_path=args.encode_index_path,
|
|
||||||
sub_size=args.sub_size,
|
sub_size=args.sub_size,
|
||||||
sub_alignment=args.sub_alignment,
|
|
||||||
left_crop=args.left_crop,
|
left_crop=args.left_crop,
|
||||||
right_crop=args.right_crop,
|
right_crop=args.right_crop,
|
||||||
top_crop=args.top_crop,
|
top_crop=args.top_crop,
|
||||||
@ -142,18 +103,8 @@ if __name__ == "__main__":
|
|||||||
subtitle_color=args.subtitle_color,
|
subtitle_color=args.subtitle_color,
|
||||||
release_sub_title=args.release_sub_title,
|
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()
|
img_gen = img_generator.process_images()
|
||||||
if img_gen:
|
if img_gen:
|
||||||
exit_application(f"\nOutput: {img_gen}", 0)
|
exit_application(f"Output: {img_gen}", 0)
|
||||||
except FrameForgeError as ff_error:
|
except FrameForgeError as error:
|
||||||
img_generator.clean_temp(False)
|
exit_application(error, 1)
|
||||||
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,13 +1,11 @@
|
|||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
|
||||||
from random import choice
|
from random import choice
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Tuple
|
|
||||||
from numpy import linspace
|
from numpy import linspace
|
||||||
|
from unidecode import unidecode
|
||||||
|
import awsmfunc
|
||||||
import vapoursynth as vs
|
import vapoursynth as vs
|
||||||
from awsmfunc import ScreenGenEncoder, ScreenGen, FrameInfo, DynamicTonemap
|
|
||||||
from frame_forge.exceptions import FrameForgeError
|
from frame_forge.exceptions import FrameForgeError
|
||||||
from frame_forge.utils import get_working_dir, hex_to_bgr
|
from frame_forge.utils import get_working_dir, hex_to_bgr
|
||||||
|
|
||||||
@ -17,14 +15,10 @@ class GenerateImages:
|
|||||||
self,
|
self,
|
||||||
source_file: Path,
|
source_file: Path,
|
||||||
encode_file: Path,
|
encode_file: Path,
|
||||||
frames: str,
|
|
||||||
image_dir: Path,
|
image_dir: Path,
|
||||||
indexer: str,
|
indexer: str,
|
||||||
img_lib: str,
|
index_directory: None | str,
|
||||||
source_index_path: None | str,
|
|
||||||
encode_index_path: None | str,
|
|
||||||
sub_size: int,
|
sub_size: int,
|
||||||
sub_alignment: int,
|
|
||||||
left_crop: int,
|
left_crop: int,
|
||||||
right_crop: int,
|
right_crop: int,
|
||||||
top_crop: int,
|
top_crop: int,
|
||||||
@ -43,15 +37,11 @@ class GenerateImages:
|
|||||||
self.source_node = None
|
self.source_node = None
|
||||||
self.reference_source_file = None
|
self.reference_source_file = None
|
||||||
self.encode_file = encode_file
|
self.encode_file = encode_file
|
||||||
self.frames = frames
|
|
||||||
self.encode_node = None
|
self.encode_node = None
|
||||||
self.image_dir = image_dir
|
self.image_dir = image_dir
|
||||||
self.indexer = indexer
|
self.indexer = indexer
|
||||||
self.img_lib = ScreenGenEncoder(img_lib)
|
self.index_dir = index_directory
|
||||||
self.source_index_path = source_index_path
|
|
||||||
self.encode_index_path = encode_index_path
|
|
||||||
self.sub_size = sub_size
|
self.sub_size = sub_size
|
||||||
self.sub_alignment = sub_alignment
|
|
||||||
self.left_crop = left_crop
|
self.left_crop = left_crop
|
||||||
self.right_crop = right_crop
|
self.right_crop = right_crop
|
||||||
self.top_crop = top_crop
|
self.top_crop = top_crop
|
||||||
@ -69,11 +59,7 @@ class GenerateImages:
|
|||||||
self.core = vs.core
|
self.core = vs.core
|
||||||
self.load_plugins()
|
self.load_plugins()
|
||||||
|
|
||||||
self.temp_dir: Path = None
|
def process_images(self):
|
||||||
|
|
||||||
def process_images(self) -> Path:
|
|
||||||
self.check_index_paths()
|
|
||||||
|
|
||||||
if self.indexer == "lsmash":
|
if self.indexer == "lsmash":
|
||||||
self.index_lsmash()
|
self.index_lsmash()
|
||||||
|
|
||||||
@ -83,42 +69,25 @@ class GenerateImages:
|
|||||||
num_source_frames = len(self.source_node)
|
num_source_frames = len(self.source_node)
|
||||||
num_encode_frames = len(self.encode_node)
|
num_encode_frames = len(self.encode_node)
|
||||||
|
|
||||||
# ASS subtitle styles
|
# Format: Name, Fontname, Font-size, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic,
|
||||||
# Font Name, Font Size, Primary Color, Secondary Color, Outline Color, Back Color, Bold,
|
# Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL,
|
||||||
# Italic, Underline, Strikeout, Scale X, Scale Y, Spacing, Angle, Border Style, Outline Width,
|
# MarginR, MarginV,
|
||||||
# Shadow Depth, Alignment, Left Margin, Right Margin, Vertical Margin, Encoding
|
|
||||||
|
|
||||||
# bgr color
|
# bgr color
|
||||||
color = "&H14FF39"
|
color = "&H000ac7f5"
|
||||||
if self.subtitle_color:
|
if self.subtitle_color:
|
||||||
color = hex_to_bgr(self.subtitle_color)
|
color = hex_to_bgr(self.subtitle_color)
|
||||||
|
|
||||||
selected_sub_style = (
|
selected_sub_style = (
|
||||||
f"Segoe UI,{self.sub_size},{color},&H00000000,&H00000000,&H00000000,"
|
f"Segoe UI,{self.sub_size},{color},&H00000000,&H00000000,&H00000000,"
|
||||||
f"1,0,0,0,100,100,0,0,1,1,0,{self.sub_alignment},10,10,10,1"
|
"1,0,0,0,100,100,0,0,1,1,0,7,5,0,0,1"
|
||||||
)
|
|
||||||
sync_sub_base = (
|
|
||||||
"Segoe UI,{size},&H31FF31&,&H00000000,&H00000000,&H00000000,"
|
|
||||||
"1,0,0,0,100,100,0,0,1,1,0,{pos},10,10,10,1"
|
|
||||||
)
|
|
||||||
selected_sub_style_ref = sync_sub_base.format(
|
|
||||||
size=str(self.sub_size + 5), pos="7"
|
|
||||||
)
|
|
||||||
selected_sub_style_sync = sync_sub_base.format(
|
|
||||||
size=str(self.sub_size + 5), pos="9"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.check_de_interlaced(num_source_frames, num_encode_frames)
|
self.check_de_interlaced(num_source_frames, num_encode_frames)
|
||||||
|
|
||||||
b_frames = None
|
|
||||||
if not self.frames:
|
|
||||||
b_frames = self.get_b_frames(num_source_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()
|
self.handle_crop()
|
||||||
|
|
||||||
@ -128,28 +97,16 @@ class GenerateImages:
|
|||||||
|
|
||||||
vs_source_info, vs_encode_info = self.handle_subtitles(selected_sub_style)
|
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,
|
b_frames,
|
||||||
vs_source_info,
|
vs_source_info,
|
||||||
vs_encode_info,
|
vs_encode_info,
|
||||||
temp_screenshot_comparison_dir,
|
screenshot_comparison_dir,
|
||||||
temp_screenshot_sync_dir,
|
screenshot_sync_dir,
|
||||||
selected_sub_style_ref,
|
selected_sub_style,
|
||||||
selected_sub_style_sync,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.generate_exact_screens(
|
|
||||||
vs_source_info,
|
|
||||||
vs_encode_info,
|
|
||||||
temp_screenshot_comparison_dir,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
final_folder = self.generate_final_folder()
|
return img_job
|
||||||
self.move_images(temp_screenshot_comparison_dir.parent, final_folder)
|
|
||||||
self.clean_temp()
|
|
||||||
|
|
||||||
return final_folder
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def screen_gen_callback(sg_call_back):
|
def screen_gen_callback(sg_call_back):
|
||||||
@ -158,79 +115,6 @@ class GenerateImages:
|
|||||||
flush=True,
|
flush=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def generate_ref_screens(
|
|
||||||
self, selected_sub_style_ref, frames: list, screenshot_sync_dir
|
|
||||||
):
|
|
||||||
"""Generates reference frames"""
|
|
||||||
for ref_frame in frames:
|
|
||||||
vs_encode_ref_info = self.core.sub.Subtitle(
|
|
||||||
clip=self.encode_node,
|
|
||||||
text=f"Reference\nFrame: {ref_frame}",
|
|
||||||
style=selected_sub_style_ref,
|
|
||||||
)
|
|
||||||
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(
|
|
||||||
self, frame_list, selected_sub_style_sync, screenshot_sync_dir
|
|
||||||
):
|
|
||||||
"""Generates sync frames"""
|
|
||||||
for sync_frame in frame_list:
|
|
||||||
vs_sync_info = self.core.sub.Subtitle(
|
|
||||||
clip=self.source_node,
|
|
||||||
text=f"Sync\nFrame: {sync_frame}",
|
|
||||||
style=selected_sub_style_sync,
|
|
||||||
)
|
|
||||||
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(
|
|
||||||
self,
|
|
||||||
vs_source_info,
|
|
||||||
vs_encode_info,
|
|
||||||
screenshot_comparison_dir,
|
|
||||||
) -> Path:
|
|
||||||
print("\nGenerating screenshots, please wait", flush=True)
|
|
||||||
|
|
||||||
# generate source images
|
|
||||||
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
|
|
||||||
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 screenshot_comparison_dir
|
|
||||||
|
|
||||||
def generate_screens(
|
def generate_screens(
|
||||||
self,
|
self,
|
||||||
b_frames,
|
b_frames,
|
||||||
@ -238,9 +122,8 @@ class GenerateImages:
|
|||||||
vs_encode_info,
|
vs_encode_info,
|
||||||
screenshot_comparison_dir,
|
screenshot_comparison_dir,
|
||||||
screenshot_sync_dir,
|
screenshot_sync_dir,
|
||||||
selected_sub_style_ref,
|
selected_sub_style,
|
||||||
selected_sub_style_sync,
|
):
|
||||||
) -> Path:
|
|
||||||
print("\nGenerating screenshots, please wait", flush=True)
|
print("\nGenerating screenshots, please wait", flush=True)
|
||||||
|
|
||||||
# handle re_sync if needed
|
# handle re_sync if needed
|
||||||
@ -257,70 +140,99 @@ class GenerateImages:
|
|||||||
sync_frames = b_frames
|
sync_frames = b_frames
|
||||||
|
|
||||||
# generate source images
|
# generate source images
|
||||||
ScreenGen(
|
awsmfunc.ScreenGen(
|
||||||
vs_source_info,
|
vs_source_info,
|
||||||
frame_numbers=sync_frames,
|
frame_numbers=sync_frames,
|
||||||
fpng_compression=1,
|
fpng_compression=1,
|
||||||
folder=screenshot_comparison_dir,
|
folder=screenshot_comparison_dir,
|
||||||
suffix="a_source__%d",
|
suffix="a_source__%d",
|
||||||
callback=self.screen_gen_callback,
|
callback=self.screen_gen_callback,
|
||||||
encoder=self.img_lib,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# generate encode images
|
# generate encode images
|
||||||
ScreenGen(
|
awsmfunc.ScreenGen(
|
||||||
vs_encode_info,
|
vs_encode_info,
|
||||||
frame_numbers=b_frames,
|
frame_numbers=b_frames,
|
||||||
fpng_compression=1,
|
fpng_compression=1,
|
||||||
folder=screenshot_comparison_dir,
|
folder=screenshot_comparison_dir,
|
||||||
suffix="b_encode__%d",
|
suffix="b_encode__%d",
|
||||||
callback=self.screen_gen_callback,
|
callback=self.screen_gen_callback,
|
||||||
encoder=self.img_lib,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# generate some sync frames
|
# generate some sync frames
|
||||||
print("\nGenerating a few sync frames", flush=True)
|
print("\nGenerating a few sync frames", flush=True)
|
||||||
|
|
||||||
# select two frames randomly from list
|
# select two frames randomly from list
|
||||||
get_sync_1 = choice(b_frames)
|
sync_1 = choice(b_frames)
|
||||||
remove_sync1 = b_frames.copy()
|
remove_sync1 = b_frames.copy()
|
||||||
remove_sync1.remove(get_sync_1)
|
remove_sync1.remove(sync_1)
|
||||||
get_sync_2 = choice(remove_sync1)
|
sync_2 = choice(remove_sync1)
|
||||||
|
|
||||||
# sync list
|
|
||||||
ref_sync_list = sorted([get_sync_1, get_sync_2])
|
|
||||||
|
|
||||||
# reference subs
|
# reference subs
|
||||||
self.generate_ref_screens(
|
vs_source_ref_info = self.core.sub.Subtitle(
|
||||||
selected_sub_style_ref, ref_sync_list, screenshot_sync_dir
|
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
|
||||||
)
|
)
|
||||||
|
|
||||||
# sync subs 1
|
# generate screens for the two reference frames
|
||||||
sync_subs_1 = [ref_sync_list[0] + i for i in range(-5, 6)]
|
awsmfunc.ScreenGen(
|
||||||
|
vs_encode_ref_info,
|
||||||
self.generate_sync_screens(
|
frame_numbers=[sync_1, sync_2],
|
||||||
sync_subs_1,
|
fpng_compression=1,
|
||||||
selected_sub_style_sync,
|
folder=screenshot_sync_dir,
|
||||||
Path(Path(screenshot_sync_dir) / "sync1"),
|
suffix="b_encode__%d",
|
||||||
|
callback=self.screen_gen_callback,
|
||||||
)
|
)
|
||||||
|
|
||||||
# sync subs 2
|
# generate 10 source frames around those reference frames
|
||||||
sync_subs_2 = [ref_sync_list[1] + i for i in range(-5, 6)]
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
self.generate_sync_screens(
|
awsmfunc.ScreenGen(
|
||||||
sync_subs_2,
|
vs_source_ref_info,
|
||||||
selected_sub_style_sync,
|
frame_numbers=[
|
||||||
Path(Path(screenshot_sync_dir) / "sync2"),
|
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)
|
print("Screen generation completed", flush=True)
|
||||||
return screenshot_comparison_dir
|
return str(screenshot_comparison_dir)
|
||||||
|
|
||||||
def handle_subtitles(self, selected_sub_style):
|
def handle_subtitles(self, selected_sub_style):
|
||||||
vs_source_info = self.core.sub.Subtitle(
|
vs_source_info = self.core.sub.Subtitle(
|
||||||
clip=self.source_node, text="Source", style=selected_sub_style
|
clip=self.source_node, text="Source", style=selected_sub_style
|
||||||
)
|
)
|
||||||
vs_encode_info = FrameInfo(
|
vs_encode_info = awsmfunc.FrameInfo(
|
||||||
clip=self.encode_node,
|
clip=self.encode_node,
|
||||||
title=self.release_sub_title if self.release_sub_title else "",
|
title=self.release_sub_title if self.release_sub_title else "",
|
||||||
style=selected_sub_style,
|
style=selected_sub_style,
|
||||||
@ -330,8 +242,10 @@ class GenerateImages:
|
|||||||
|
|
||||||
def handle_hdr(self):
|
def handle_hdr(self):
|
||||||
if self.tone_map:
|
if self.tone_map:
|
||||||
self.source_node = DynamicTonemap(clip=self.source_node, libplacebo=False)
|
self.source_node = awsmfunc.DynamicTonemap(
|
||||||
self.encode_node = DynamicTonemap(
|
clip=self.source_node, libplacebo=False
|
||||||
|
)
|
||||||
|
self.encode_node = awsmfunc.DynamicTonemap(
|
||||||
clip=self.encode_node,
|
clip=self.encode_node,
|
||||||
reference=self.reference_source_file,
|
reference=self.reference_source_file,
|
||||||
libplacebo=False,
|
libplacebo=False,
|
||||||
@ -388,8 +302,8 @@ class GenerateImages:
|
|||||||
bottom=self.bottom_crop if self.bottom_crop else 0,
|
bottom=self.bottom_crop if self.bottom_crop else 0,
|
||||||
)
|
)
|
||||||
|
|
||||||
def generate_final_folder(self) -> Path:
|
def generate_folders(self):
|
||||||
print("\nCreating final output folder", flush=True)
|
print("\nCreating folders for images", flush=True)
|
||||||
if self.image_dir:
|
if self.image_dir:
|
||||||
image_output_dir = Path(self.image_dir)
|
image_output_dir = Path(self.image_dir)
|
||||||
else:
|
else:
|
||||||
@ -397,61 +311,34 @@ class GenerateImages:
|
|||||||
Path(self.encode_file).parent / f"{Path(self.encode_file).stem}_images"
|
Path(self.encode_file).parent / f"{Path(self.encode_file).stem}_images"
|
||||||
)
|
)
|
||||||
|
|
||||||
if image_output_dir.exists():
|
# remove any accent characters from path
|
||||||
for folder in ("img_comparison", "img_selected", "img_sync"):
|
image_output_dir = Path(unidecode(str(image_output_dir)))
|
||||||
rm_path = image_output_dir / folder
|
|
||||||
if rm_path.is_dir() and rm_path.exists():
|
|
||||||
shutil.rmtree(rm_path, ignore_errors=True)
|
|
||||||
|
|
||||||
|
# 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)
|
image_output_dir.mkdir(exist_ok=True, parents=True)
|
||||||
|
|
||||||
print("Folder creation completed", flush=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"))
|
||||||
|
|
||||||
return image_output_dir
|
# create selected image directory and define it as variable
|
||||||
|
Path(Path(image_output_dir) / "img_selected").mkdir(exist_ok=True)
|
||||||
|
|
||||||
def generate_temp_folders(self) -> Tuple[Path, Path, Path]:
|
# create sync image directory and define it as variable
|
||||||
print("\nCreating temporary folders for images", flush=True)
|
Path(Path(image_output_dir) / "img_sync").mkdir(exist_ok=True)
|
||||||
self.temp_dir = Path(tempfile.mkdtemp(prefix="ff_"))
|
screenshot_sync_dir = str(Path(Path(image_output_dir) / "img_sync"))
|
||||||
|
|
||||||
screenshot_comparison_dir = Path(Path(self.temp_dir) / "img_comparison")
|
# create sub directories
|
||||||
screenshot_comparison_dir.mkdir(exist_ok=True)
|
Path(Path(image_output_dir) / "img_sync/sync1").mkdir(exist_ok=True)
|
||||||
|
Path(Path(image_output_dir) / "img_sync/sync2").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)
|
print("Folder creation completed", flush=True)
|
||||||
|
|
||||||
return screenshot_comparison_dir, selected_dir, screenshot_sync_dir
|
return screenshot_comparison_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):
|
def get_b_frames(self, num_source_frames):
|
||||||
print(
|
print(
|
||||||
@ -468,11 +355,9 @@ class GenerateImages:
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pict_types = ("B", b"B")
|
|
||||||
for i, frame in enumerate(b_frames):
|
for i, frame in enumerate(b_frames):
|
||||||
while (
|
while (
|
||||||
self.encode_node.get_frame(frame).props["_PictType"]
|
self.encode_node.get_frame(frame).props["_PictType"].decode() != "B"
|
||||||
not in pict_types
|
|
||||||
):
|
):
|
||||||
frame += 1
|
frame += 1
|
||||||
b_frames[i] = frame
|
b_frames[i] = frame
|
||||||
@ -531,9 +416,10 @@ class GenerateImages:
|
|||||||
flush=True,
|
flush=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _index_source_lsmash(self):
|
def index_lsmash(self):
|
||||||
print("Indexing source", flush=True)
|
print("Indexing source", flush=True)
|
||||||
|
|
||||||
|
# index source file
|
||||||
# if index is found in the StaxRip temp working directory, attempt to use it
|
# if index is found in the StaxRip temp working directory, attempt to use it
|
||||||
if (
|
if (
|
||||||
Path(str(Path(self.source_file).with_suffix("")) + "_temp/").is_dir()
|
Path(str(Path(self.source_file).with_suffix("")) + "_temp/").is_dir()
|
||||||
@ -543,18 +429,12 @@ class GenerateImages:
|
|||||||
):
|
):
|
||||||
print("Index found in StaxRip temp, attempting to use", flush=True)
|
print("Index found in StaxRip temp, attempting to use", flush=True)
|
||||||
|
|
||||||
|
# define cache path
|
||||||
lwi_cache_path = Path(
|
lwi_cache_path = Path(
|
||||||
str(Path(self.source_file).with_suffix("")) + "_temp/temp.lwi"
|
str(Path(self.source_file).with_suffix("")) + "_temp/temp.lwi"
|
||||||
)
|
)
|
||||||
|
|
||||||
elif self.source_index_path.exists():
|
# try to use index on source file with the cache path
|
||||||
print("Index found, attempting to use", flush=True)
|
|
||||||
lwi_cache_path = self.source_index_path
|
|
||||||
|
|
||||||
# if no existing index is found index source file
|
|
||||||
else:
|
|
||||||
lwi_cache_path = Path(Path(self.source_file).with_suffix(".lwi"))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.source_node = self.core.lsmas.LWLibavSource(
|
self.source_node = self.core.lsmas.LWLibavSource(
|
||||||
source=self.source_file, cachefile=lwi_cache_path
|
source=self.source_file, cachefile=lwi_cache_path
|
||||||
@ -563,42 +443,66 @@ class GenerateImages:
|
|||||||
source=self.source_file, cachefile=lwi_cache_path
|
source=self.source_file, cachefile=lwi_cache_path
|
||||||
)
|
)
|
||||||
print("Using existing index", flush=True)
|
print("Using existing index", flush=True)
|
||||||
|
# if index cannot be used
|
||||||
except vs.Error:
|
except vs.Error:
|
||||||
print("L-Smash version miss-match, indexing source again", flush=True)
|
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.source_node = self.core.lsmas.LWLibavSource(self.source_file)
|
||||||
self.reference_source_file = self.core.lsmas.LWLibavSource(self.source_file)
|
self.reference_source_file = self.core.lsmas.LWLibavSource(
|
||||||
|
self.source_file
|
||||||
|
)
|
||||||
|
|
||||||
print("Source index completed", flush=True)
|
# 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
|
||||||
|
)
|
||||||
|
|
||||||
def _index_encode_lsmash(self):
|
print("Source index completed\n\nIndexing encode", flush=True)
|
||||||
print("\nIndexing encode", flush=True)
|
|
||||||
|
|
||||||
if self.encode_index_path:
|
# define a path for encode index to go
|
||||||
cache_path_enc = self.encode_index_path
|
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:
|
else:
|
||||||
cache_path_enc = Path(Path(self.encode_file).with_suffix(".lwi"))
|
cache_path_enc = Path(Path(self.encode_file).with_suffix(".lwi"))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# create index
|
||||||
self.encode_node = self.core.lsmas.LWLibavSource(
|
self.encode_node = self.core.lsmas.LWLibavSource(
|
||||||
self.encode_file, cachefile=cache_path_enc
|
self.encode_file, cachefile=cache_path_enc
|
||||||
)
|
)
|
||||||
except vs.Error:
|
except vs.Error:
|
||||||
|
# delete index
|
||||||
cache_path_enc.unlink(missing_ok=True)
|
cache_path_enc.unlink(missing_ok=True)
|
||||||
|
# create index
|
||||||
self.encode_node = self.core.lsmas.LWLibavSource(
|
self.encode_node = self.core.lsmas.LWLibavSource(
|
||||||
self.encode_file, cachefile=cache_path_enc
|
self.encode_file, cachefile=cache_path_enc
|
||||||
)
|
)
|
||||||
|
|
||||||
print("Encode index completed", flush=True)
|
print("Encode index completed", flush=True)
|
||||||
|
|
||||||
def index_lsmash(self):
|
def index_ffms2(self):
|
||||||
"""Index source/encode with lsmash"""
|
|
||||||
|
|
||||||
self._index_source_lsmash()
|
|
||||||
self._index_encode_lsmash()
|
|
||||||
|
|
||||||
def _index_source_ffms2(self):
|
|
||||||
print("Indexing source", flush=True)
|
print("Indexing source", flush=True)
|
||||||
|
|
||||||
|
# index source file
|
||||||
# if index is found in the StaxRip temp working directory, attempt to use it
|
# if index is found in the StaxRip temp working directory, attempt to use it
|
||||||
if (
|
if (
|
||||||
Path(str(Path(self.source_file).with_suffix("")) + "_temp/").is_dir()
|
Path(str(Path(self.source_file).with_suffix("")) + "_temp/").is_dir()
|
||||||
@ -608,51 +512,57 @@ class GenerateImages:
|
|||||||
):
|
):
|
||||||
print("Index found in StaxRip temp, attempting to use", flush=True)
|
print("Index found in StaxRip temp, attempting to use", flush=True)
|
||||||
|
|
||||||
|
# define cache path
|
||||||
ffindex_cache_path = Path(
|
ffindex_cache_path = Path(
|
||||||
str(Path(self.source_file).with_suffix("")) + "_temp/temp.ffindex"
|
str(Path(self.source_file).with_suffix("")) + "_temp/temp.ffindex"
|
||||||
)
|
)
|
||||||
|
|
||||||
elif self.source_index_path.exists():
|
# try to use index on source file with the cache path
|
||||||
print("Index found, attempting to use", flush=True)
|
try:
|
||||||
ffindex_cache_path = self.source_index_path
|
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
|
# if no existing index is found index source file
|
||||||
else:
|
else:
|
||||||
ffindex_cache_path = Path(Path(self.source_file).with_suffix(".ffindex"))
|
|
||||||
print(
|
|
||||||
"FFMS2 library doesn't allow progress, please wait while the index is completed",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.source_node = self.core.ffms2.Source(
|
# create index
|
||||||
self.source_file, cachefile=ffindex_cache_path
|
|
||||||
)
|
|
||||||
self.reference_source_file = self.core.ffms2.Source(
|
|
||||||
self.source_file, cachefile=ffindex_cache_path
|
|
||||||
)
|
|
||||||
except vs.Error:
|
|
||||||
Path(self.source_file).with_suffix(".ffindex").unlink(missing_ok=True)
|
|
||||||
print(
|
print(
|
||||||
"FFMS2 library doesn't allow progress, please wait while the index is completed",
|
"FFMS2 library doesn't allow progress, please wait while the index is completed",
|
||||||
flush=True,
|
flush=True,
|
||||||
)
|
)
|
||||||
self.source_node = self.core.ffms2.Source(
|
self.source_node = self.core.ffms2.Source(self.source_file)
|
||||||
self.source_file, cachefile=ffindex_cache_path
|
self.reference_source_file = self.core.ffms2.Source(self.source_file)
|
||||||
)
|
except vs.Error:
|
||||||
self.reference_source_file = self.core.ffms2.Source(
|
# delete index
|
||||||
self.source_file, cachefile=ffindex_cache_path
|
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", flush=True)
|
print("Source index completed\n\nIndexing encode", flush=True)
|
||||||
|
|
||||||
def _index_encode_ffms2(self):
|
# define a path for encode index to go
|
||||||
print("\nIndexing encode", flush=True)
|
if self.index_dir:
|
||||||
|
index_base_path = Path(self.index_dir) / Path(self.encode_file).name
|
||||||
if self.encode_index_path:
|
cache_path_enc = Path(str(index_base_path) + ".ffindex")
|
||||||
cache_path_enc = self.encode_index_path
|
|
||||||
else:
|
else:
|
||||||
cache_path_enc = Path(str(self.encode_file) + ".ffindex")
|
cache_path_enc = Path(self.encode_file + ".ffindex")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.encode_node = self.core.ffms2.Source(
|
self.encode_node = self.core.ffms2.Source(
|
||||||
@ -666,12 +576,6 @@ class GenerateImages:
|
|||||||
|
|
||||||
print("Encode index completed", flush=True)
|
print("Encode index completed", flush=True)
|
||||||
|
|
||||||
def index_ffms2(self):
|
|
||||||
"""Index source/encode with ffms2"""
|
|
||||||
|
|
||||||
self._index_source_ffms2()
|
|
||||||
self._index_encode_ffms2()
|
|
||||||
|
|
||||||
def load_plugins(self):
|
def load_plugins(self):
|
||||||
plugin_path = get_working_dir() / "img_plugins"
|
plugin_path = get_working_dir() / "img_plugins"
|
||||||
if not plugin_path.is_dir() and not plugin_path.exists():
|
if not plugin_path.is_dir() and not plugin_path.exists():
|
||||||
@ -679,21 +583,3 @@ class GenerateImages:
|
|||||||
else:
|
else:
|
||||||
for plugin in plugin_path.glob("*.dll"):
|
for plugin in plugin_path.glob("*.dll"):
|
||||||
self.core.std.LoadPlugin(Path(plugin).resolve())
|
self.core.std.LoadPlugin(Path(plugin).resolve())
|
||||||
|
|
||||||
def check_index_paths(self):
|
|
||||||
indexer_ext = ".lwi" if self.indexer == "lsmash" else ".ffindex"
|
|
||||||
if not self.source_index_path or not Path(self.source_index_path).exists():
|
|
||||||
source_path_obj = Path(self.source_file)
|
|
||||||
self.source_index_path = source_path_obj.parent / Path(
|
|
||||||
f"{source_path_obj.stem}{indexer_ext}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
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.encode_file)
|
|
||||||
self.encode_index_path = encode_path_obj.parent / Path(
|
|
||||||
f"{encode_path_obj.stem}{indexer_ext}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.encode_index_path = Path(self.encode_index_path)
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
def frame_list(frames: str) -> list:
|
|
||||||
if not re.match(r"\d+(?::\d+)*$", frames):
|
|
||||||
raise ValueError("Input must be in the format of int:int i.e. 101:104")
|
|
||||||
return [int(x) for x in frames.split(":")]
|
|
@ -22,7 +22,6 @@ hiddenimports = [
|
|||||||
"collections.namedtuple",
|
"collections.namedtuple",
|
||||||
"collections.abc.Iterable",
|
"collections.abc.Iterable",
|
||||||
"collections.abc.Mapping",
|
"collections.abc.Mapping",
|
||||||
"concurrent.futures",
|
|
||||||
"concurrent.futures.Future",
|
"concurrent.futures.Future",
|
||||||
"fractions",
|
"fractions",
|
||||||
]
|
]
|
||||||
|
63
poetry.lock
generated
63
poetry.lock
generated
@ -1,4 +1,4 @@
|
|||||||
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "altgraph"
|
name = "altgraph"
|
||||||
@ -17,8 +17,10 @@ version = "1.3.4"
|
|||||||
description = "awesome VapourSynth functions"
|
description = "awesome VapourSynth functions"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
files = []
|
files = [
|
||||||
develop = false
|
{file = "awsmfunc-1.3.4-py3-none-any.whl", hash = "sha256:d9ce9cf90dfdb66b4561a5d3b011232e663ad0d879e2a276827bff9b8b3b37e1"},
|
||||||
|
{file = "awsmfunc-1.3.4.tar.gz", hash = "sha256:8330332f5c4818322b4090b24499b1dc4e4e371460de70c4bd62a112f4157255"},
|
||||||
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
numpy = "*"
|
numpy = "*"
|
||||||
@ -27,13 +29,7 @@ vs-rekt = ">=1.0.0"
|
|||||||
vsutil = ">=0.7.0"
|
vsutil = ">=0.7.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
dev = ["ruff", "toml"]
|
dev = ["pylint", "toml", "yapf"]
|
||||||
|
|
||||||
[package.source]
|
|
||||||
type = "git"
|
|
||||||
url = "https://github.com/OpusGang/awsmfunc"
|
|
||||||
reference = "HEAD"
|
|
||||||
resolved_reference = "e1290f799162749fc627951290bfb4089f2f39cb"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "black"
|
name = "black"
|
||||||
@ -220,10 +216,10 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyinstaller"
|
name = "pyinstaller"
|
||||||
version = "6.10.0"
|
version = "6.3.0"
|
||||||
description = "PyInstaller bundles a Python application and all its dependencies into a single package."
|
description = "PyInstaller bundles a Python application and all its dependencies into a single package."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "<3.14,>=3.8"
|
python-versions = "<3.13,>=3.8"
|
||||||
files = []
|
files = []
|
||||||
develop = true
|
develop = true
|
||||||
|
|
||||||
@ -232,7 +228,7 @@ altgraph = "*"
|
|||||||
macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""}
|
macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""}
|
||||||
packaging = ">=22.0"
|
packaging = ">=22.0"
|
||||||
pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""}
|
pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""}
|
||||||
pyinstaller-hooks-contrib = ">=2024.8"
|
pyinstaller-hooks-contrib = ">=2021.4"
|
||||||
pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""}
|
pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""}
|
||||||
setuptools = ">=42.0.0"
|
setuptools = ">=42.0.0"
|
||||||
|
|
||||||
@ -246,19 +242,15 @@ url = "custom-pyinstaller"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyinstaller-hooks-contrib"
|
name = "pyinstaller-hooks-contrib"
|
||||||
version = "2024.8"
|
version = "2023.11"
|
||||||
description = "Community maintained hooks for PyInstaller"
|
description = "Community maintained hooks for PyInstaller"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "pyinstaller_hooks_contrib-2024.8-py3-none-any.whl", hash = "sha256:0057fe9a5c398d3f580e73e58793a1d4a8315ca91c3df01efea1c14ed557825a"},
|
{file = "pyinstaller-hooks-contrib-2023.11.tar.gz", hash = "sha256:5dd7a8a054a65c19cdaa381cabcfbe76f44d5f88d18214b0c570a0cd139be77f"},
|
||||||
{file = "pyinstaller_hooks_contrib-2024.8.tar.gz", hash = "sha256:29b68d878ab739e967055b56a93eb9b58e529d5b054fbab7a2f2bacf80cef3e2"},
|
{file = "pyinstaller_hooks_contrib-2023.11-py2.py3-none-any.whl", hash = "sha256:f2a75dac2968ec81f92dcd3768906f654fa4204bc496126ae8483e87a5d89602"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
packaging = ">=22.0"
|
|
||||||
setuptools = ">=42.0.0"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pywin32-ctypes"
|
name = "pywin32-ctypes"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@ -286,30 +278,41 @@ 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 = ["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"]
|
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]]
|
[[package]]
|
||||||
name = "vapoursynth"
|
name = "vapoursynth"
|
||||||
version = "65"
|
version = "64"
|
||||||
description = "A frameserver for the 21st century"
|
description = "A frameserver for the 21st century"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
files = [
|
||||||
{file = "VapourSynth-65-cp311-cp311-win_amd64.whl", hash = "sha256:9fab72bb6dbcdd8d0e6643da68908f85387bc764b836524e97a1e6e8989ca13e"},
|
{file = "VapourSynth-64-cp311-cp311-win_amd64.whl", hash = "sha256:ad046a704537276f7ebb4132e1210e8922ec4b2403a4b44f5fe30fd9456a6412"},
|
||||||
{file = "VapourSynth-65-cp38-cp38-win_amd64.whl", hash = "sha256:d9b49b595dc929d63250bd82f05e75238b6dfc4a50ab08e7fc4fe4f8888f6b95"},
|
{file = "VapourSynth-64-cp38-cp38-win_amd64.whl", hash = "sha256:794f93bcaa1ce79510c11962f677a7d18685a0c29aa36586cb307d1eb9d7f2e0"},
|
||||||
{file = "VapourSynth-65.zip", hash = "sha256:1d42d461ef9988a3477134e478a2291d79f3469635cde8af2c66b1e87c36f711"},
|
{file = "VapourSynth-64.zip", hash = "sha256:29425a135ca68cbb17b3dfcad0097375b75f94af1f499ba89bcd2b03b2651846"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vapoursynth-portable"
|
name = "vapoursynth-portable"
|
||||||
version = "65"
|
version = "64"
|
||||||
description = "A frameserver for the 21st century"
|
description = "A frameserver for the 21st century"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
files = [
|
||||||
{file = "VapourSynth_portable-65-py2.py3-none-win_amd64.whl", hash = "sha256:6a66615d8a25a71766d035054f9980fd8be47dbd97332e9c068933d1b25061b3"},
|
{file = "VapourSynth_portable-64-py2.py3-none-win_amd64.whl", hash = "sha256:82b26b38774197a7ef53cbd3206bafb02d197100fad513d635bf83f9981dad6c"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
vapoursynth = "65"
|
vapoursynth = "64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vs-rekt"
|
name = "vs-rekt"
|
||||||
@ -342,5 +345,5 @@ vapoursynth = "*"
|
|||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "3.11.9"
|
python-versions = "3.11.5"
|
||||||
content-hash = "42cace5dbe92132d04512c3dbc5a60c1cb06fa883a7030eb74f8f0e782b48226"
|
content-hash = "820bfb62c5fba1b17221bc1a02144932dbce64fa44c698f24f7205c2fc4a3d18"
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "frame-forge"
|
name = "frame-forge"
|
||||||
version = "1.3.3"
|
version = "1.0.0"
|
||||||
description = "CLI to offload image generation to"
|
description = "CLI to offload image generation to"
|
||||||
authors = ["jlw4049 <jlw_4049@hotmail.com>"]
|
authors = ["jlw4049 <jlw_4049@hotmail.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
package-mode = false
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "3.11.9"
|
python = "3.11.5"
|
||||||
|
awsmfunc = "^1.3.4"
|
||||||
|
unidecode = "^1.3.7"
|
||||||
|
vapoursynth-portable = "64"
|
||||||
numpy = "^1.26.2"
|
numpy = "^1.26.2"
|
||||||
vapoursynth-portable = "65"
|
|
||||||
awsmfunc = {git = "https://github.com/OpusGang/awsmfunc"}
|
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
Loading…
Reference in New Issue
Block a user