1
0
mirror of synced 2024-11-27 15:40:48 +01:00

Add warning and command-line flag for working with animations that loop forever.

This commit is contained in:
Jennifer Taylor 2021-05-25 02:01:36 +00:00
parent 089eef1588
commit bc46d83452
2 changed files with 76 additions and 8 deletions

View File

@ -398,6 +398,7 @@ class AFPRenderer(VerboseOutput):
background_color: Optional[Color] = None,
background_image: Optional[Image.Image] = None,
only_depths: Optional[List[int]] = None,
only_frames: Optional[List[int]] = None,
movie_transform: Matrix = Matrix.identity(),
verbose: bool = False,
) -> Generator[Image.Image, None, None]:
@ -407,7 +408,7 @@ class AFPRenderer(VerboseOutput):
# This is the SWF we care about.
with self.debugging(verbose):
swf.color = background_color or swf.color
yield from self.__render(swf, only_depths, movie_transform, background_image)
yield from self.__render(swf, only_depths, only_frames, movie_transform, background_image)
return
raise Exception(f'{path} not found in registered SWFs!')
@ -1077,6 +1078,8 @@ class AFPRenderer(VerboseOutput):
# during some bytecode update in this loop.
if clip.frame > clip.requested_frame:
# Rewind this clip to the beginning so we can replay until the requested frame.
if clip is self.__root:
print("WARNING: Root clip was rewound, its possible this animation plays forever!")
clip.rewind()
self.vprint(f"{prefix} Processing frame {clip.frame} on our way to frame {clip.requested_frame}")
@ -1235,6 +1238,7 @@ class AFPRenderer(VerboseOutput):
self,
swf: SWF,
only_depths: Optional[List[int]],
only_frames: Optional[List[int]],
movie_transform: Matrix,
background_image: Optional[Image.Image],
) -> Generator[Image.Image, None, None]:
@ -1338,11 +1342,15 @@ class AFPRenderer(VerboseOutput):
actual_add_color = Color(0.0, 0.0, 0.0, 0.0)
actual_blend = 0
if only_frames:
max_frame = max(only_frames)
else:
max_frame = None
# Now play the frames of the root clip.
try:
while root_clip.playing and not root_clip.finished:
# Create a new image to render into.
color = swf.color or Color(0.0, 0.0, 0.0, 0.0)
self.vprint(f"Rendering frame {frameno + 1}/{len(root_clip.source.frames)}")
# Go through all registered clips, place all needed tags.
@ -1350,8 +1358,17 @@ class AFPRenderer(VerboseOutput):
while self.__is_dirty(root_clip):
changed = self.__process_tags(root_clip, True) or changed
# If we're only rendering some frames, don't bother to do the draw operations
# if we aren't going to return the frame.
if only_frames and (frameno + 1) not in only_frames:
self.vprint(f"Skipped rendering frame {frameno + 1}/{len(root_clip.source.frames)}")
last_rendered_frame = None
frameno += 1
continue
if changed or last_rendered_frame is None:
# Now, render out the placed objects.
color = swf.color or Color(0.0, 0.0, 0.0, 0.0)
curimage = Image.new("RGBA", actual_size, color=color.as_tuple())
curimage = self.__render_object(curimage, root_clip, movie_transform, movie_mask, actual_mult_color, actual_add_color, actual_blend, only_depths=only_depths)
else:
@ -1360,12 +1377,17 @@ class AFPRenderer(VerboseOutput):
curimage = last_rendered_frame.copy()
# Return that frame, advance our bookkeeping.
self.vprint(f"Finished rendering frame {frameno + 1}/{len(root_clip.source.frames)}")
last_rendered_frame = curimage
frameno += 1
yield curimage
# See if we should bail because we passed the last requested frame.
if max_frame is not None and frameno == max_frame:
break
except KeyboardInterrupt:
# Allow ctrl-c to end early and render a partial animation.
print(f"WARNING: Interrupted early, will render only {frameno}/{len(root_clip.source.frames)} frames of animation!")
# Clean up
self.movie = None
self.__root = None

View File

@ -64,6 +64,22 @@ def write_bytecode(swf: SWF, directory: str, verbose: bool=False) -> None:
bfp.write(f"{os.linesep}{os.linesep}".join(buff).encode('utf-8'))
def parse_intlist(data: str) -> List[int]:
ints: List[int] = []
for chunk in data.split(","):
chunk = chunk.strip()
if '-' in chunk:
start, end = chunk.split('-', 1)
start_int = int(start.strip())
end_int = int(end.strip())
ints.extend(range(start_int, end_int + 1))
else:
ints.append(int(chunk))
return sorted(set(ints))
def main() -> int:
parser = argparse.ArgumentParser(description="Konami AFP graphic file unpacker/repacker")
subparsers = parser.add_subparsers(help='Action to take', dest='action')
@ -272,7 +288,13 @@ def main() -> int:
"--only-depths",
type=str,
default=None,
help="Only render objects on these depth planes. Can provide either a number or a comma-separated list of numbers.",
help="Only render objects on these depth planes. Can provide either a number or a comma-separated list of numbers, or a range such as 3-5.",
)
render_parser.add_argument(
"--only-frames",
type=str,
default=None,
help="Only render these frames. Can provide either a number or a comma-separated list of numbers, or a range such as 10-20.",
)
render_parser.add_argument(
"--force-aspect-ratio",
@ -792,14 +814,30 @@ def main() -> int:
# Support rendering only certain depth planes.
if args.only_depths is not None:
depths = [int(d.strip()) for d in args.only_depths.split(",")]
requested_depths = parse_intlist(args.only_depths)
else:
depths = None
requested_depths = None
# Support rendering only certain frames.
if args.only_frames is not None:
requested_frames = parse_intlist(args.only_frames)
else:
requested_frames = None
if fmt in ["GIF", "WEBP"]:
# Write all the frames out in one file.
duration = renderer.compute_path_frame_duration(args.path)
images = list(renderer.render_path(args.path, verbose=args.verbose, background_color=color, background_image=background, only_depths=depths, movie_transform=transform))
images = list(
renderer.render_path(
args.path,
verbose=args.verbose,
background_color=color,
background_image=background,
only_depths=requested_depths,
only_frames=requested_frames,
movie_transform=transform,
)
)
if len(images) == 0:
raise Exception("Did not render any frames!")
@ -818,7 +856,15 @@ def main() -> int:
digits = f"0{int(math.log10(frames)) + 1}"
for i, img in enumerate(
renderer.render_path(args.path, verbose=args.verbose, background_color=color, background_image=background, only_depths=depths, movie_transform=transform)
renderer.render_path(
args.path,
verbose=args.verbose,
background_color=color,
background_image=background,
only_depths=requested_depths,
only_frames=requested_frames,
movie_transform=transform,
)
):
fullname = f"{filename}-{i:{digits}}{ext}"