Skip to content
This repository has been archived by the owner on Jul 6, 2024. It is now read-only.

Commit

Permalink
add: プレビュー機能実装
Browse files Browse the repository at this point in the history
  • Loading branch information
PigeonsHouse committed Jan 15, 2024
1 parent 96f925f commit a91db70
Show file tree
Hide file tree
Showing 10 changed files with 328 additions and 23 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ dist/
__pycache__/
debug.json
*.mp4
*.png
7 changes: 7 additions & 0 deletions src/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ def init_parser():
type=str,
help="path to output video",
)
parser.add_argument(
"-f",
"--frame",
metavar="preview_frame",
type=int,
help="frame for preview",
)
parser.add_argument(
"--debug",
action="store_true",
Expand Down
1 change: 1 addition & 0 deletions src/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class VSMLContent:
style: Style
exist_video: bool
exist_audio: bool
_second: float

def __init__(
self,
Expand Down
1 change: 1 addition & 0 deletions src/converter/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from .main import convert_video
from .preview import *
15 changes: 0 additions & 15 deletions src/converter/main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import json
from typing import Optional

from content import SourceContent, VSMLContent, WrapContent
Expand Down Expand Up @@ -71,20 +70,6 @@ def convert_video(
audio_process=process.audio,
)

if debug_mode:
content_str = (
str(vsml_data.content)
.replace("'", '"')
.replace("True", "true")
.replace("False", "false")
.replace("None", "null")
)
content_str = json.dumps(
(json.loads(content_str)), indent=2, ensure_ascii=False
)
with open("./debug.json", "w") as f:
f.write(content_str)

export_video(
process.video, process.audio, out_filename, debug_mode, overwrite
)
1 change: 1 addition & 0 deletions src/converter/preview/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .main import convert_image_from_frame
84 changes: 84 additions & 0 deletions src/converter/preview/content.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from typing import Optional

from style import Order
from vsml import SourceContent, VSMLContent, WrapContent


def pick_data(
vsml_content: VSMLContent, second: float
) -> Optional[VSMLContent]:
if not vsml_content.exist_video:
return None
if isinstance(vsml_content, WrapContent):
if vsml_content.style.order == Order.SEQUENCE:
items = vsml_content.items
vsml_content.items = []
whole_second = 0
left_margin_end = 0

for item in items:
whole_second += max(
item.style.time_margin_start.get_second(), left_margin_end
)
if second < whole_second:
return vsml_content
whole_second += item.style.time_padding_start.get_second()
if second < whole_second:
item._second = -1
vsml_content.items = [item]
return vsml_content
whole_second_with_object_length = (
whole_second + item.style.object_length.get_second()
)
if second < whole_second_with_object_length:
child = pick_data(item, second - whole_second)
if child is not None:
vsml_content.items = [child]
return vsml_content
whole_second = whole_second_with_object_length
whole_second += item.style.time_padding_end.get_second()
if second < whole_second:
item._second = -1
vsml_content.items = [item]
return vsml_content
left_margin_end = item.style.time_margin_end.get_second()

elif vsml_content.style.order == Order.PARALLEL:
items = vsml_content.items
vsml_content.items = []

for item in items:
whole_second = item.style.time_margin_start.get_second()
if second < whole_second:
continue
whole_second += item.style.time_padding_start.get_second()
if second < whole_second:
item._second = -1
vsml_content.items.append(item)
continue
whole_second_with_object_length = (
whole_second + item.style.object_length.get_second()
)
if (
second < whole_second_with_object_length
or item.style.object_length.is_fit()
):
child = pick_data(item, second - whole_second)
if child is not None:
vsml_content.items.append(child)
continue
whole_second = whole_second_with_object_length
whole_second += item.style.time_padding_end.get_second()
if second < whole_second:
item._second = -1
vsml_content.items.append(item)
continue
return vsml_content

else:
raise Exception()
elif isinstance(vsml_content, SourceContent):
vsml_content._second = second
return vsml_content
else:
raise Exception()
38 changes: 38 additions & 0 deletions src/converter/preview/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from typing import Optional

import ffmpeg

from converter.ffmpeg import set_background_filter
from utils import VSMLManager
from vsml import VSML, WrapContent

from .content import pick_data
from .process import create_preview_process


def convert_image_from_frame(
vsml_data: VSML, frame: int, output_path: Optional[str]
) -> None:
output_path = "preview.png" if output_path is None else output_path
second = frame / VSMLManager.get_root_fps()
if second > vsml_data.content.style.object_length.get_second():
raise Exception()
vsml_content_for_pick = None
vsml_content = vsml_data.content
if second >= vsml_content.style.time_margin_start.get_second():
if second < vsml_content.style.time_padding_start.get_second():
if isinstance(vsml_content, WrapContent):
vsml_content.items = []
vsml_content_for_pick = vsml_content
else:
vsml_content_for_pick = pick_data(vsml_content, second)

process = create_preview_process(vsml_content_for_pick)
process.video = set_background_filter(
background_color=vsml_content.style.background_color,
resolution_text=VSMLManager.get_root_resolution().get_str(),
video_process=process.video,
fit_video_process=True,
)
process = ffmpeg.output(process.video, output_path, vframes=1)
process.run(overwrite_output=True)
164 changes: 164 additions & 0 deletions src/converter/preview/process.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
from typing import Optional

import ffmpeg

from converter.ffmpeg import (
get_background_process,
get_source_process,
get_text_process,
layering_filter,
set_background_filter,
width_height_filter,
)
from converter.schemas import Process
from style import GraphicValue, LayerMode, Order
from utils import SourceType
from vsml import SourceContent, VSMLContent, WrapContent


def create_preview_source_process(vsml_content: SourceContent) -> Process:
style = vsml_content.style
if vsml_content.type == SourceType.TEXT:
video_process = get_text_process(
vsml_content.src_path,
style.get_width_with_padding(),
style.get_height_with_padding(),
style.padding_left,
style.padding_top,
style.background_color,
style.using_font_path,
style.font_size,
style.font_color,
style.font_border_color,
style.font_border_width,
)
else:
video_process = get_source_process(
vsml_content.src_path,
exist_video=True,
exist_audio=False,
)["video"]
if vsml_content.type != SourceType.TEXT:
video_process = width_height_filter(
style.width, style.height, video_process
)
if (
style.padding_top.is_zero_over()
or style.padding_left.is_zero_over()
):
video_process = set_background_filter(
width=style.get_width_with_padding(),
height=style.get_height_with_padding(),
background_color=style.background_color,
video_process=video_process,
position_x=style.padding_left,
position_y=style.padding_top,
fit_video_process=True,
)
if vsml_content._second != -1 and vsml_content.tag_name == "vid":
video_process = ffmpeg.trim(video_process, start=vsml_content._second)
return Process(video_process, None, vsml_content.style)


def create_preview_wrap_process(
child_processes: list[Process], vsml_content: WrapContent
) -> Process:
style = vsml_content.style
video_process = None
width = style.get_width_with_padding()
height = style.get_height_with_padding()
if style.order == Order.SEQUENCE:
if len(child_processes):
video_process = child_processes[0].video
else:
video_process = get_background_process(
"{}x{}".format(
width.get_pixel(),
height.get_pixel(),
),
style.background_color,
)

elif style.order == Order.PARALLEL:
video_process = get_background_process(
"{}x{}".format(
width.get_pixel(),
height.get_pixel(),
),
style.background_color,
)
is_single = style.layer_mode == LayerMode.SINGLE
is_row = style.direction is None or style.direction.is_row()
is_reverse = style.direction is None or style.direction.is_reverse
current_graphic_length = (
(
width - style.padding_right
if is_row
else height - style.padding_bottom
)
if is_reverse
else (style.padding_left if is_row else style.padding_top)
)
remain_margin = GraphicValue("0")

for child_process in child_processes:
child_style = child_process.style
if child_process.video is not None:
max_space = max(
(
child_style.margin_left
if is_row
else child_style.margin_top
),
remain_margin,
)
child_graphic_length = (
child_style.get_width_with_padding()
if is_row
else child_style.get_height_with_padding()
)

current_graphic_length += (
-child_graphic_length if is_reverse else max_space
)
video_process = layering_filter(
video_process,
child_process.video,
(
current_graphic_length
if is_single and is_row
else style.padding_left + child_style.margin_left
),
(
current_graphic_length
if is_single and not is_row
else style.padding_top + child_style.margin_top
),
)
current_graphic_length += (
-max_space if is_reverse else child_graphic_length
)
remain_margin = (
child_style.margin_right
if is_row
else child_style.margin_bottom
)
else:
raise Exception()
return Process(video_process, None, style)


def create_preview_process(vsml_content: Optional[VSMLContent]) -> Process:
if isinstance(vsml_content, SourceContent):
process = create_preview_source_process(vsml_content)
elif isinstance(vsml_content, WrapContent):
child_processes = []
for item in vsml_content.items:
child_process = create_preview_process(item)
if child_process is not None:
child_processes.append(child_process)
process = create_preview_wrap_process(child_processes, vsml_content)
else:
raise Exception()

return process
39 changes: 31 additions & 8 deletions src/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import json

from args import get_args
from converter import convert_video
from converter import convert_image_from_frame, convert_video
from xml_parser import parsing_vsml


Expand All @@ -14,13 +16,34 @@ def main():
# ファイルのVSMLを解析
vsml_data = parsing_vsml(args.filename, args.offline)

# 解析したデータをもとにffmpegで動画を構築
convert_video(
vsml_data,
args.output,
args.debug,
args.overwrite,
)
if args.debug:
content_str = (
str(vsml_data.content)
.replace("'", '"')
.replace("True", "true")
.replace("False", "false")
.replace("None", "null")
)
content_str = json.dumps(
(json.loads(content_str)), indent=2, ensure_ascii=False
)
with open("./debug.json", "w") as f:
f.write(content_str)

if args.frame is None:
# 解析したデータをもとにffmpegで動画を構築
convert_video(
vsml_data,
args.output,
args.debug,
args.overwrite,
)
else:
convert_image_from_frame(
vsml_data,
args.frame,
args.output,
)


if __name__ == "__main__":
Expand Down

0 comments on commit a91db70

Please sign in to comment.