Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/python3
- # Copyright (c) 2021-2024 Pixel Grass
- #
- # 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.
- # Algorithm example for the 4:3 to 16:9/widescreen conversion case:
- # w - screen width (according to localcoord),
- # b1 - old camera's horizontal bound value (abs/radius), d1 - old 4:3 horizontal delta,
- # b2 - new camera's horizontal bound value (abs/radius), d2 - new 16:9 horizontal delta.
- #
- # Case diagram:
- #
- # d1 * b1
- # |-------------------|
- # d2 * b2 1/6w 1/2w
- # |------------|------|------------------|
- # off-screen ^ ^ ^
- # | +-- 4:3 edge +-- screen center
- # +-- 16:9 edge
- #
- # Bound/delta equations:
- #
- # 1) according to the diagram:
- # d1 * b1 - d2 * b2 = 1/6 * w
- # d2 * b2 = d1 * b1 - 1/6 * w
- # d2 = (d1 * b1 - 1/6 * w) / b2
- #
- # 2) substitute b2 with the following bound modification formula:
- # b2 = b1 - 1/6 * w
- #
- # 3) final deltas modification formula:
- # d2 = (d1 * b1 - 1/6 * w) / (b1 - 1/6 * w)
- import argparse
- import re
- import shutil
- import sys
- from dataclasses import dataclass
- from decimal import Decimal, Context, ROUND_HALF_DOWN
- from math import floor, log10, isclose
- from pathlib import Path
- from typing import Any, Callable, Optional, Pattern
- MODE_MUGEN = "mugen".casefold()
- MODE_IKEMEN = "ikemen".casefold()
- MODE_NONE = "none".casefold()
- BOUNDS_MODE_VALUES = [MODE_MUGEN, MODE_IKEMEN, MODE_NONE]
- BOUNDS_MODES = " | ".join(BOUNDS_MODE_VALUES)
- CMD_PARSER = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, description="""
- A stage DEF file converter for M.U.G.E.N/Ikemen that allows e.g. to adjust a zoom-less 4:3 stage
- to support a widescreen aspect ratio, by adjusting zoom, horizontal deltas and other parameters.
- Alternatively, this tool can also adjust a zoom-less stage to support a target zoom level.
- When using this tool to enable widescreen support, then the resulting stage is meant to
- be displayed with the `StageFit` parameter (from the `mugen.cfg` config file) set to `0`.
- This converter works by setting the `zoomin` and `zoomout` values and then calculating and adjusting
- the `boundleft` and `boundright` parameters, as well as the horizontal `delta` values of backgrounds,
- including the old-style `parallax` type backgrounds using the `xscale` parameter.
- The players' starting position values of `p1startx` and `p2startx` are also adjusted appropriately.
- The camera's vertical bounding parameters are also tweaked, depending on the target engine.
- Unfortunately, this part is mostly guesswork, so manual follow-up adjustments will be necessary,
- however it should still be much easier than editing the whole stage completely by hand. To adjust
- the vertical camera bounds after conversion, use e.g. `boundhigh`, `cutlow` and similar parameters.
- It should also be mentioned, that Mugen's and Ikemen's vertical camera bounding algorithms work
- very differently, especially in the case of a wide zoom range.
- The parallax floors using the `xscale` parameter should also receive automatic delta adjustments,
- however these may need to be tweaked after conversion.
- Stages already having zoom are not supported. Highres stages using `highres` are also
- not supported. Highres stages using `localcoord` and/or `(x/y)scale` should theoretically
- be supported, however this has not been extensively tested.
- """, epilog="""
- examples:
- %(prog)s DEF_FILE
- modify the given zoom-less stage DEF file to support a 16:9 aspect ratio
- (this is the default behavior when no additional options are given)
- %(prog)s -z 0.85 DEF_FILE
- modify the given zoom-less stage DEF file to support a zoom of 0.85
- the stage will be set to always display in this exact zoom
- %(prog)s -z 0.8 -n 1.0 DEF_FILE
- modify the given zoom-less stage DEF file to support a zoom-out of 0.8
- set the zoom-in value to 1.0, making the effective zoom range [0.8, 1.0]
- %(prog)s -r 64x27 DEF_FILE
- modify the given zoom-less stage DEF file to support the ultra-wide 64:27 aspect ratio
- (this implies a large zoom-out value, therefore some artifacts may not be avoided)
- Copyright (c) 2021-2023 Pixel Grass, License: MIT
- ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. **
- """)
- CMD_PARSER.add_argument("stage_def_files", metavar="DEF_FILE", type=Path, nargs="+", help="""
- the stage DEF file (multiple files can be given)\nthe files will be modified in place!
- """.strip())
- CMD_PARSER.add_argument("-r", "--target-aspect-ratio", metavar="RATIO", help="""
- adjust the stage to support the given widescreen aspect ratio (e.g. 16x9)
- mutually exclusive with the `--target-zoom` option: use only one or the other
- """.strip())
- CMD_PARSER.add_argument("-z", "--target-zoom", metavar="ZOOM", type=float, help="""
- adjust the stage to support at least the given zoom value (e.g. 0.75)
- warning: setting this value too low may introduce artifacts
- mutually exclusive with the `--target-aspect-ratio` option: use only one or the other
- """.strip())
- CMD_PARSER.add_argument("-n", "--set-zoom-in", metavar="ZOOM_IN", type=float, help="""
- set/override the `zoomin` camera parameter to the given value
- (default: calculated from the target aspect ratio or the same as target zoom)
- """.strip())
- CMD_PARSER.add_argument("-t", "--set-zoom-out", metavar="ZOOM_OUT", type=float, help="""
- set/override the `zoomout` camera parameter to the given value
- warning: setting this value lower than the target zoom *will* introduce artifacts
- (default: calculated from the target aspect ratio or the same as target zoom)
- """.strip())
- CMD_PARSER.add_argument("-m", "--bounds-mode", metavar="CB_MODE", help=f"""
- choose camera's bounding mode (values: {BOUNDS_MODES}, default: {MODE_MUGEN})
- * the `{MODE_MUGEN}` mode enables hacks for MUGEN's quirks, to possibly avoid artifacts
- * the `{MODE_IKEMEN}` mode disables stage height cutting
- * the `{MODE_NONE}` mode leaves the camera's vertical bounding parameters unchanged
- """.strip())
- CMD_PARSER.add_argument("-p", "--no-parallax", default=False, action='store_true', help="""
- do not modify the parallax backgrounds (default: modify parallax backgrounds)
- """.strip())
- CMD_PARSER.add_argument("-d", "--debug-bg", default=False, action='store_true', help="""
- set the `debugbg` property to `1` in the modified stage DEF file (default: do not set)
- """.strip())
- CMD_PARSER.add_argument("-o", "--output-path", metavar="OUT_PATH", type=Path, help="""
- write the modified stage DEF file or files to the given output file or
- directory respectively, instead of modifying the given DEF files in-place
- """.strip())
- CMD_PARSER.add_argument('--no-backup', default=False, action='store_true', help="""
- do not create a backup for the modified stage DEF files
- all of the file will be irreversibly modified in place!
- """.strip())
- CMD_PARSER.add_argument('--version', action='version', version="%(prog)s 1.1.0")
- INFO = "Info".casefold()
- STAGE_INFO = "StageInfo".casefold()
- PLAYER_INFO = "PlayerInfo".casefold()
- CAMERA = "Camera".casefold()
- BG_DEF = "BGdef".casefold()
- MUGEN_VERSION = "mugenversion".casefold()
- LOCAL_COORD = "localcoord".casefold()
- HIRES = "hires".casefold()
- BOUND_HIGH = "boundhigh".casefold()
- BOUND_LEFT = "boundleft".casefold()
- BOUND_RIGHT = "boundright".casefold()
- CUT_LOW = "cutlow".casefold()
- ZOOM_IN = "zoomin".casefold()
- ZOOM_OUT = "zoomout".casefold()
- VERTICAL_FOLLOW = "verticalfollow".casefold()
- P1_START_X = "p1startx".casefold()
- P2_START_X = "p2startx".casefold()
- TYPE = "type".casefold()
- DELTA = "delta".casefold()
- XSCALE = "xscale".casefold()
- WINDOW = "window".casefold()
- DEBUG_BG = "debugbg".casefold()
- PARALLAX = "parallax".casefold()
- VALUES_COUNT = {
- MUGEN_VERSION: 1, LOCAL_COORD: 2, HIRES: 1,
- BOUND_HIGH: 1, BOUND_LEFT: 1, BOUND_RIGHT: 1, CUT_LOW: 1,
- ZOOM_IN: 1, ZOOM_OUT: 1, VERTICAL_FOLLOW: 1,
- P1_START_X: 1, P2_START_X: 1, DELTA: {1, 2}, WINDOW: 4,
- BG_DEF: 1, TYPE: 1
- }
- DECIMAL_PLACES = 8
- WINDOW_TOLERANCE = 5
- VERTICAL_FOLLOW_MIN = 0.00000001
- MARGIN_FACTOR = {MODE_MUGEN: 1.0 / 30.0, MODE_IKEMEN: 1.0 / 30.0}
- MARGIN_FACTOR_STATIC = {MODE_MUGEN: 0.1, MODE_IKEMEN: 1.0 / 30.0}
- COMMENT_RE = re.compile(r";.*")
- SECTION_RE = re.compile(r"^\[([^]]+)]$")
- PARAMETER_RE = re.compile(r"^([^=]+)=([^=]*)$")
- LINE_BREAK_RE = re.compile("[\r\n]+$")
- FIRST_VALUE_RE = re.compile(r"(^\s*[^=;]+\s*=\s*)([^=,;\s]+)(.*$)")
- SECOND_VALUE_RE = re.compile(r"(^\s*[^=;]+\s*=\s*[^=,;\s]+\s*,\s*)([^=,;\s]+)(.*$)")
- @dataclass(frozen=True)
- class Parameter:
- name: str
- values: list[str]
- @dataclass(frozen=True)
- class LocalCoord:
- width: float = 320.0
- height: float = 240.0
- @dataclass(frozen=True)
- class ParallaxInfo:
- bg: str
- xscale_index: int
- Parser = Callable[[str], float]
- Modifier = Callable[[float], float]
- Verifier = Callable[[float, float], None]
- def parse_ratio(ratio: str) -> float:
- match = re.search(r"^\s*([0-9]+)[^0-9]([0-9]+)\s*$", ratio)
- if match is None:
- CMD_PARSER.error(f"invalid aspect ratio definition: '{match}', "
- f"use e.g. '16x9', '21x9' or '16:9', '21:9' etc.")
- width = int(match.group(1))
- height = int(match.group(2))
- return float(width) / height
- def parse_parameter(param_line: str, stage_name: str) -> Optional[Parameter]:
- match = PARAMETER_RE.search(param_line)
- if match is None:
- return None
- param_name = match.group(1).strip().casefold()
- param_values = match.group(2).strip().split(",")
- if param_name in VALUES_COUNT:
- actual_len = len(param_values)
- expected_len = VALUES_COUNT[param_name]
- if actual_len not in (expected_len if isinstance(expected_len, set) else {expected_len}):
- CMD_PARSER.error(f"invalid parameter for stage '{stage_name}', line: '{param_line}'")
- return Parameter(param_name, list(value.strip() for value in param_values))
- def parse_value(param_line: str, pattern: Pattern, name: str) -> float:
- try:
- match = pattern.search(param_line)
- return float(match.group(2))
- except:
- CMD_PARSER.error(f"failed to parse the {name} parameter value, line: '{param_line}'")
- def parse_first_value(param_line: str) -> float:
- return parse_value(param_line, FIRST_VALUE_RE, "first")
- def parse_second_value(param_line: str) -> float:
- return parse_value(param_line, SECOND_VALUE_RE, "second")
- def round_value(value: float) -> Decimal:
- precision = DECIMAL_PLACES + (0 if abs(value) < 1 else int(floor(log10(abs(value)) + 1.0)))
- return Decimal(value).normalize(Context(prec=precision, rounding=ROUND_HALF_DOWN))
- def modify_value(lines: list[str], index: int, pattern: Pattern,
- parse: Parser, modify: Modifier, verify: Optional[Verifier] = None) -> None:
- param_line = lines[index]
- old_value = parse(param_line)
- new_value = modify(old_value)
- not verify or verify(old_value, new_value)
- rounded_value = round_value(new_value)
- lines[index] = pattern.sub(f"\\g<1>{rounded_value:f}\\g<3>", param_line)
- def modify_first_value(lines: list[str], index: int, modify: Modifier, verify: Optional[Verifier] = None) -> None:
- return modify_value(lines, index, FIRST_VALUE_RE, parse_first_value, modify, verify)
- def modify_second_value(lines: list[str], index: int, modify: Modifier, verify: Optional[Verifier] = None) -> None:
- return modify_value(lines, index, SECOND_VALUE_RE, parse_second_value, modify, verify)
- def set_value(lines: list[str], index: int, value: float) -> None:
- param_line = lines[index]
- lines[index] = FIRST_VALUE_RE.sub(f"\\g<1>{round_value(value):f}\\g<3>", param_line)
- def insert_value(lines: list[tuple[int, str]], index: int, param_name: str, value: float, line_break_str: str) -> None:
- lines.append((index + 1, f"{param_name} = {round_value(value):f}{line_break_str}"))
- def set_or_insert_value(
- set_lines: list[str], set_index: int, insert_lines: list[tuple[int, str]], insert_index: int,
- param_name: str, value: float, line_break_str: str) -> None:
- if set_index is not None:
- set_value(set_lines, set_index, value)
- else:
- insert_value(insert_lines, insert_index, param_name, value, line_break_str)
- def get_width_extension_ratio(zoom: float) -> float:
- return ((1.0 / zoom) - 1.0) / 2.0
- def get_height_margin(bounds_mode: str, full_height: float, bound_high: float) -> Optional[float]:
- factor = MARGIN_FACTOR if bound_high else MARGIN_FACTOR_STATIC
- return full_height * factor[bounds_mode] if factor[bounds_mode] else None
- def debug(a: float, b: [float, None] = None) -> str:
- return f"({round_value(a)})" if b is None else f"({round_value(a)}, {round_value(b)})"
- def process_stage(args, stage_file: Path, target_ratio: float, bounds_mode: str) -> list[str]:
- with open(stage_file, newline="", encoding="iso8859_1") as stage_input:
- lines = stage_input.readlines()
- stage_name = stage_file.name
- values: dict[str, Any] = {LOCAL_COORD: LocalCoord(), BOUND_HIGH: -25.0, WINDOW: []}
- indexes: dict[str, Any] = {DELTA: [], WINDOW: []}
- parallax_infos: list[ParallaxInfo] = []
- parallax_delta_indexes: dict[str, int] = {}
- current_section = None
- current_bg_type = None
- for line_index, line in enumerate(lines):
- clean_line = COMMENT_RE.sub("", line).strip()
- if not clean_line:
- continue
- section_match = SECTION_RE.search(clean_line)
- if section_match is not None:
- current_section = section_match.group(1).strip().casefold()
- indexes[current_section] = line_index
- current_bg_type = None
- continue
- if current_section is None:
- continue
- parameter = parse_parameter(clean_line, stage_name)
- if parameter is None:
- continue
- if parameter.name == TYPE:
- current_bg_type = parameter.values[0].strip().casefold()
- continue
- elif parameter.name == DELTA:
- delta_value = float(parameter.values[0])
- if delta_value < 0.0:
- CMD_PARSER.error(f"for stage '{stage_name}', "
- f"negative delta values {debug(delta_value)} are not supported, aborting'")
- if current_bg_type == PARALLAX:
- parallax_delta_indexes[current_section] = line_index
- else:
- indexes[DELTA].append(line_index)
- continue
- elif parameter.name == XSCALE and current_bg_type == PARALLAX:
- parallax_infos.append(ParallaxInfo(current_section, line_index))
- continue
- elif parameter.name == WINDOW:
- indexes[WINDOW].append(line_index)
- values[WINDOW].append(list(float(coord) for coord in parameter.values))
- continue
- if current_section == INFO:
- if parameter.name == MUGEN_VERSION:
- indexes[MUGEN_VERSION] = line_index
- elif current_section == STAGE_INFO:
- if parameter.name == LOCAL_COORD:
- values[LOCAL_COORD] = LocalCoord(float(parameter.values[0]), float(parameter.values[1]))
- elif parameter.name == HIRES:
- CMD_PARSER.error(f"the 'hires' parameter is not supported for stage '{stage_name}'")
- elif current_section == PLAYER_INFO:
- if parameter.name in {P1_START_X, P2_START_X}:
- indexes[parameter.name] = line_index
- elif current_section == CAMERA:
- if parameter.name in {ZOOM_IN, ZOOM_OUT}:
- indexes[parameter.name] = line_index
- zoom_value = parse_first_value(clean_line)
- if zoom_value != 1.0:
- CMD_PARSER.error(f"only zoom-less stages are supported, "
- f"but stage '{stage_name}' has zoom values other than 1.0")
- elif parameter.name in {BOUND_HIGH, BOUND_LEFT, BOUND_RIGHT, CUT_LOW, VERTICAL_FOLLOW}:
- indexes[parameter.name], values[parameter.name] = line_index, float(parameter.values[0])
- elif current_section == BG_DEF:
- if parameter.name == DEBUG_BG:
- indexes[DEBUG_BG] = line_index
- if not (BOUND_LEFT in indexes and BOUND_RIGHT in indexes):
- CMD_PARSER.error(f"camera bound parameters missing for stage '{stage_name}'")
- local_width = values[LOCAL_COORD].width
- local_height = values[LOCAL_COORD].height
- for window_index, line_index in enumerate(indexes[WINDOW]):
- window_coords = values[WINDOW][window_index]
- window_width = window_coords[2] - window_coords[0]
- window_height = window_coords[3] - window_coords[1]
- # detect and remove full-screen windows
- if local_width - window_width <= WINDOW_TOLERANCE and local_height - window_height <= WINDOW_TOLERANCE:
- lines[line_index] = ";" + lines[line_index]
- local_ratio = local_width / local_height
- target_zoom = args.target_zoom or 1.0 / (target_ratio / local_ratio)
- width_extension = local_width * get_width_extension_ratio(target_zoom)
- modify_first_value(lines, indexes[BOUND_LEFT], lambda bound: bound + width_extension)
- modify_first_value(lines, indexes[BOUND_RIGHT], lambda bound: bound - width_extension)
- bound_radius = (values[BOUND_RIGHT] - values[BOUND_LEFT]) / 2.0
- def delta_modifier(delta: float) -> float:
- return max((delta * bound_radius - width_extension) / (bound_radius - width_extension), 0.0)
- def delta_verifier(old_delta: float, new_delta: float):
- if old_delta > 0.0 and new_delta == 0.0:
- print(f"{CMD_PARSER.prog}: warning: for stage '{stage_name}', "
- f"capping a non-zero delta {debug(old_delta)} to zero; "
- f"this suggests that some artifacts may remain", file=sys.stderr)
- for delta_index in indexes[DELTA]:
- modify_first_value(lines, delta_index, delta_modifier, delta_verifier)
- for parallax_info in parallax_infos if not args.no_parallax else []:
- xscale_index = parallax_info.xscale_index
- top_xscale = parse_first_value(lines[xscale_index])
- bottom_xscale = parse_second_value(lines[xscale_index])
- if isclose(top_xscale, 0.0) or isclose(bottom_xscale, 0.0):
- print(f"{CMD_PARSER.prog}: warning: for stage '{stage_name}', will not modify parallax "
- f"with xscale values {debug(top_xscale, bottom_xscale)} too close to zero")
- continue
- bottom_delta_factor = bottom_xscale / top_xscale
- delta_index = parallax_delta_indexes[parallax_info.bg]
- top_delta = parse_first_value(lines[delta_index])
- bottom_delta = top_delta * bottom_delta_factor
- if isclose(top_delta, 0.0) or isclose(bottom_delta, 0.0):
- print(f"{CMD_PARSER.prog}: warning: for stage '{stage_name}', will not modify parallax "
- f"with effective delta values {debug(top_delta, bottom_delta)} too close to zero")
- continue
- if isclose(bottom_delta_factor, 1.0) or isclose(bottom_delta, 1.0):
- modify_first_value(lines, delta_index, delta_modifier, delta_verifier)
- continue
- if isclose(top_delta, 1.0):
- new_bottom_delta = delta_modifier(bottom_delta_factor)
- if isclose(new_bottom_delta, 0.0):
- print(f"{CMD_PARSER.prog}: warning: for stage '{stage_name}', will not modify parallax "
- f"with resulting bottom xscale value {debug(new_bottom_delta)} too close to zero")
- continue
- modify_first_value(lines, xscale_index, lambda _: 1.0)
- modify_second_value(lines, xscale_index, lambda _: new_bottom_delta)
- continue
- if top_delta < bottom_delta:
- new_top_delta = delta_modifier(top_delta)
- new_bottom_delta = (bottom_delta - 1.0) * (1.0 - new_top_delta) / (1.0 - top_delta) + 1.0
- else:
- new_bottom_delta = delta_modifier(bottom_delta)
- new_top_delta = (top_delta - 1.0) * (1.0 - new_bottom_delta) / (1.0 - bottom_delta) + 1.0
- if isclose(new_top_delta, 0.0) or isclose(new_bottom_delta, 0.0):
- print(f"{CMD_PARSER.prog}: warning: for stage '{stage_name}', will not modify parallax "
- f"with resulting effective delta values {debug(new_top_delta, new_bottom_delta)} too close to zero")
- continue
- modify_first_value(lines, delta_index, lambda _: new_top_delta)
- modify_first_value(lines, xscale_index, lambda _: 1.0)
- modify_second_value(lines, xscale_index, lambda _: new_bottom_delta / new_top_delta)
- zoom_in = args.set_zoom_in or target_zoom
- zoom_out = args.set_zoom_out or target_zoom
- if zoom_out < target_zoom:
- print(f"{CMD_PARSER.prog}: warning: for stage '{stage_name}', setting the zoom-out ({zoom_out}) "
- f"lower than the target zoom ({target_zoom}) will introduce artifacts", file=sys.stderr)
- start_x_ratio = get_width_extension_ratio(zoom_in) + 1.0
- for start_index in [indexes[P1_START_X], indexes[P2_START_X]]:
- modify_first_value(lines, start_index, lambda start_x: start_x * start_x_ratio)
- if values[VERTICAL_FOLLOW] == 0:
- assert VERTICAL_FOLLOW in indexes, "'verticalfollow' must exist in the DEF file to be non-zero"
- set_value(lines, indexes[VERTICAL_FOLLOW], VERTICAL_FOLLOW_MIN)
- line_break = LINE_BREAK_RE.search(lines[indexes[INFO]]).group(0)
- insert_lines = []
- def update(param: str, insert_index: int, value: float) -> None:
- set_or_insert_value(lines, indexes.get(param), insert_lines, insert_index, param.lower(), value, line_break)
- if args.debug_bg:
- update(DEBUG_BG, indexes[BG_DEF], 1)
- camera_index = indexes[CAMERA]
- if not bounds_mode == MODE_NONE:
- is_static = values[BOUND_HIGH] == 0.0
- height_margin = round(get_height_margin(bounds_mode, local_height, values[BOUND_HIGH]))
- if bounds_mode == MODE_MUGEN or (bounds_mode == MODE_IKEMEN and not is_static):
- update(BOUND_HIGH, camera_index, values[BOUND_HIGH] + height_margin)
- update(CUT_LOW, camera_index, height_margin)
- else: # bounds_mode == MODE_IKEMEN and is_static:
- update(BOUND_HIGH, camera_index, 1)
- update(CUT_LOW, camera_index, height_margin + 1)
- update(VERTICAL_FOLLOW, camera_index, VERTICAL_FOLLOW_MIN)
- update(ZOOM_OUT, camera_index, zoom_out)
- update(ZOOM_IN, camera_index, zoom_in)
- update(MUGEN_VERSION, indexes[INFO], 1.1)
- for insert_line in sorted(insert_lines, key = lambda it: it[0], reverse = True):
- lines.insert(insert_line[0], insert_line[1])
- return lines
- def get_stage_out_file(stage_file: Path, out_path: Path) -> Path:
- if not out_path:
- out_file = stage_file
- elif out_path.is_dir():
- out_file = Path(out_path, stage_file.name)
- else:
- out_file = out_path
- if out_file.exists() and not out_file.is_file():
- CMD_PARSER.error(f"invalid output file: '{out_file}'")
- return out_file
- def main():
- args = CMD_PARSER.parse_args()
- if len(args.stage_def_files) > 1 and args.output_path and not args.output_path.is_dir():
- CMD_PARSER.error(f"the output path needs to be a directory "
- f"if multiple input file are given: '{args.output_path}'")
- if args.target_aspect_ratio and args.target_zoom:
- CMD_PARSER.error(f"the target aspect ratio (-r) and target zoom (-z) options are mutually exclusive")
- bounds_mode = (args.bounds_mode or MODE_MUGEN).casefold()
- if bounds_mode not in BOUNDS_MODE_VALUES:
- CMD_PARSER.error(f"invalid bounds mode: '{bounds_mode}', allowed values: {BOUNDS_MODE_VALUES}")
- target_ratio = parse_ratio(args.target_aspect_ratio or "16x9")
- for stage_file in args.stage_def_files:
- if not stage_file.is_file():
- CMD_PARSER.error(f"the given stage DEF_FILE path: '{stage_file}' does not point to a regular file")
- stage_out_file = get_stage_out_file(stage_file, args.output_path)
- stage_bak_file = stage_out_file.with_name(stage_out_file.name + ".bak")
- make_backup = stage_out_file.exists() and not args.no_backup
- if make_backup and stage_bak_file.exists():
- CMD_PARSER.error(f"the stage backup file: '{stage_bak_file}' already exists, aborting")
- lines = process_stage(args, stage_file, target_ratio, bounds_mode)
- if make_backup:
- shutil.copyfile(stage_out_file, stage_bak_file)
- stage_out_file.parent.mkdir(parents=True, exist_ok=True)
- with open(stage_out_file, "w", newline="", encoding="iso8859_1") as stage_output:
- stage_output.writelines(lines)
- if __name__ == '__main__':
- main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement