Skip to content

Commit

Permalink
CPU Version
Browse files Browse the repository at this point in the history
  • Loading branch information
DreamGallery committed Sep 10, 2023
1 parent c493ec0 commit 0bb152a
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 34 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Also using `OPENCV` to fix timeline of subtitle frame by frame.<br />

※This tool is based on [MalitsPlus/HoshimiToolkit](https://github.com/MalitsPlus/HoshimiToolkit), you need to get subtitle files in the game through this project first, the names of required files are usually `adv_***.txt`.

# Usage
# Usage(CPU Version)

## Install from the repository

Expand Down Expand Up @@ -35,7 +35,7 @@ python generate.py
## Generate .ass file with time-fix

To use time-fix option you need to put the recorded video in `adv/video`, and the recommended resolution is `[1920x1080]` or you can change the `[Font Config]` in `config.ini` to fit your video(compare in PS is a good idea).<br />
If your resolution ratio is not `16:9`, you may also have to modify the cutting area of frames near line `26~29` in `src/frame.py`.
If your resolution ratio is not `16:9`, you may also have to modify the cutting area of frames in `src/frame.py`.

```
python main.py
Expand Down
8 changes: 4 additions & 4 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@
for dial in extract(sub_file_name):
dial_list.append(dial)

files = []
current_count = 0
start_file_index = 0
files: list[str] = []
current_count = int(0)
start_file_index = int(0)
content = script_info + "\n" + garbage + "\n" + style + "\n" + event
print("ASS-Generate-Progress start")

Expand All @@ -49,7 +49,7 @@

for dial in dial_list:
if "SkipTime" in dial:
start_file_index = start_file_index + int(float(str(dial).split(":")[1]) * stream.fps)
start_file_index = start_file_index + int(float(dial.split(":")[1]) * stream.fps)
continue
dial_event = AssEvents()
dial_event.from_dialogue(dial)
Expand Down
10 changes: 5 additions & 5 deletions src/adv_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
_KEY_TITLE = config.get("Text KEY", "KEY_TITLE")


def extract(input: str) -> list:
dial_list = []
def extract(input: str) -> list[str]:
dial_list: list[str] = []
with open(f"{_TXT_PATH}/{input}", "r", encoding="utf8") as f:
for line in f:
if "text" in line:
Expand All @@ -30,7 +30,7 @@ def get_title(input: str) -> str:
return title


def get_text(input: str) -> [str, bool]:
def get_text(input: str) -> tuple[str, bool]:
if _KEY_MASSAGE in input:
text = (
input[1:-2].split(_KEY_MASSAGE)[1].split(f"\u0020{_KEY_NAME}")[0].replace("{user}", _player_name)
Expand All @@ -43,7 +43,7 @@ def get_text(input: str) -> [str, bool]:
elif _KEY_NARRATION in input:
text = input[1:-2].split(_KEY_NARRATION)[1].split(f"\u0020{_KEY_CLIP}")[0]
gray = True
return text, gray
return (text, gray)


def get_name(input: str) -> str:
Expand All @@ -54,7 +54,7 @@ def get_name(input: str) -> str:
return name


def get_clip(input: str) -> any:
def get_clip(input: str):
clip = input[1:-2].split(f"\u0020{_KEY_CLIP}")[1].replace("\\", "")
data = json.loads(clip)
return data
Expand Down
2 changes: 1 addition & 1 deletion src/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def __init__(
self.Effect = Effect
self.Text = Text

def from_dialogue(self, input: str):
def from_dialogue(self, input: str) -> None:
self.Start = to_time(get_clip(input)["_startTime"])
self.Duration = get_clip(input)["_duration"]
self.End = end_time(get_clip(input)["_startTime"], get_clip(input)["_duration"])
Expand Down
27 changes: 19 additions & 8 deletions src/frame.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import cv2
import cv2, cv2.typing
import os, sys
import threading
import numpy as np
Expand All @@ -11,18 +11,25 @@
_VIDEO_PATH = config.get("File PATH", "VIDEO_PATH")

_lock = threading.Lock()
_current_count = 0
_current_count = int(0)


class FrameProcess(object):
fps: float

def one_task(self, image_folder_path: str, frame: any, milliseconds: float, total_fps: int):
def one_task(
self,
image_folder_path: str,
frame: cv2.typing.MatLike,
width: int,
height: int,
milliseconds: float,
total_fps: int,
) -> None:
global _current_count
seconds = "%.4f" % (milliseconds // 1000 + (milliseconds % 1000) / 1000)
name = seconds[:-1].replace(".", "_")
height = len(frame)
width = len(frame[0])
# Modify the following content if your resolution ratio is not 16:9
img = frame[
(height * 29 // 36) : (height * 8 // 9),
(width * 1 // 16) : (width * 15 // 16),
Expand All @@ -43,12 +50,14 @@ def one_task(self, image_folder_path: str, frame: any, milliseconds: float, tota
sys.stdout.flush()
_lock.release()

def to_frame(self, input: str):
def to_frame(self, input: str) -> None:
image_folder_path = f"{_CACHE_PATH}/{input.split('.')[0]}"
os.makedirs(image_folder_path, exist_ok=True)
video_path = f"{_VIDEO_PATH}/{input}"
vc = cv2.VideoCapture(video_path)
self.fps = vc.get(cv2.CAP_PROP_FPS)
width = int(vc.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(vc.get(cv2.CAP_PROP_FRAME_HEIGHT))
total_fps = int(vc.get(cv2.CAP_PROP_FRAME_COUNT))
executor = ThreadPoolExecutor(max_workers=20)
frame_tasks = []
Expand All @@ -58,13 +67,15 @@ def to_frame(self, input: str):
break
milliseconds = vc.get(cv2.CAP_PROP_POS_MSEC)
frame_tasks.append(
executor.submit(self.one_task, image_folder_path, frame, milliseconds, total_fps)
executor.submit(
self.one_task, image_folder_path, frame, width, height, milliseconds, total_fps
)
)
vc.release()
wait(frame_tasks, return_when="ALL_COMPLETED")
print("\u0020", "Pre-Progress finished")

def get_fps(self, input: str):
def get_fps(self, input: str) -> None:
video_path = f"{_VIDEO_PATH}/{input}"
vc = cv2.VideoCapture(video_path)
self.fps = vc.get(cv2.CAP_PROP_FPS)
31 changes: 17 additions & 14 deletions src/match.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
import cv2
import cv2, cv2.typing
import numpy as np
from src.read_ini import config
from PIL import Image, ImageDraw, ImageFont
from PIL.ImageFont import FreeTypeFont

_half_split_length = config.getint("Arg", "half_split_length")


def to_binary(img: any, thresh: float) -> any:
def to_binary(img: cv2.typing.MatLike, thresh: float) -> cv2.typing.MatLike:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, thresh, 255, cv2.THRESH_BINARY)
return binary


def to_binary_adaptive(img: any, blocksize: int, C: float) -> any:
def to_binary_adaptive(img: cv2.typing.MatLike, blocksize: int, C: float) -> cv2.typing.MatLike:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, blocksize, C)
return binary


def draw_text(
text: str, font_path: list[str], fontsize: list[int], strokewidth: int, kerning: int
) -> [list[any], list[any]]:
) -> tuple[list[cv2.typing.MatLike], list[cv2.typing.MatLike]]:
font_japan = ImageFont.truetype(font_path[0], fontsize[0])
font_alpha = ImageFont.truetype(font_path[1], fontsize[1])
font_digit = ImageFont.truetype(font_path[2], fontsize[2])

char_info = []
text_height = 0
char_info: list[tuple[FreeTypeFont, int]] = []
text_height = int(0)
for char in text:
if char.encode("utf-8").isalpha():
font = font_alpha
Expand All @@ -39,14 +40,14 @@ def draw_text(
text_height = max((char_bbox[3] - char_bbox[1]), text_height)
char_info.append([font, char_width])

text_width = 0
text_width = int(0)
for info in char_info:
text_width += info[1]
text_size = ((text_width + (len(text) - 1) * kerning), text_height)
text_img = Image.new("RGBA", text_size)
draw = ImageDraw.Draw(text_img)

tmp_width = 0
tmp_width = int(0)
for index, char in enumerate(text):
draw.text(
(((char_info[index][1]) // 2 + tmp_width), (text_size[1] // 2)),
Expand All @@ -57,8 +58,8 @@ def draw_text(
stroke_fill=(32, 32, 32),
)
tmp_width = tmp_width + char_info[index][1] + kerning
binary = []
mask = []
binary: list[cv2.typing.MatLike] = []
mask: list[cv2.typing.MatLike] = []
kernel = np.ones((3, 3), np.uint8)
if len(text) >= _half_split_length:
spilt_pixel = sum(
Expand All @@ -76,15 +77,17 @@ def draw_text(
binary.append(to_binary(np.asarray(text_img), 127))
mask.append(cv2.erode(to_binary(np.asarray(text_img), 30), kernel, iterations=1))

return binary, mask
return (binary, mask)


def compare(img_path: str, binary: list[any], threshold: float, mask: list[any]) -> bool:
img = cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2GRAY)
def compare(
img_path: str, binary: list[cv2.typing.MatLike], threshold: float, mask: list[cv2.typing.MatLike]
) -> bool:
img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
white_pixels = cv2.countNonZero(img)
if white_pixels < 100:
return False
part_max = []
part_max: list[float] = []
for image in zip(binary, mask):
res = cv2.matchTemplate(img, image[0], cv2.TM_CCORR_NORMED, mask=image[1])
res[np.isinf(res)] = 0
Expand Down

0 comments on commit 0bb152a

Please sign in to comment.