1
0
mirror of synced 2024-11-30 16:54:30 +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_color: Optional[Color] = None,
background_image: Optional[Image.Image] = None, background_image: Optional[Image.Image] = None,
only_depths: Optional[List[int]] = None, only_depths: Optional[List[int]] = None,
only_frames: Optional[List[int]] = None,
movie_transform: Matrix = Matrix.identity(), movie_transform: Matrix = Matrix.identity(),
verbose: bool = False, verbose: bool = False,
) -> Generator[Image.Image, None, None]: ) -> Generator[Image.Image, None, None]:
@ -407,7 +408,7 @@ class AFPRenderer(VerboseOutput):
# This is the SWF we care about. # This is the SWF we care about.
with self.debugging(verbose): with self.debugging(verbose):
swf.color = background_color or swf.color 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 return
raise Exception(f'{path} not found in registered SWFs!') raise Exception(f'{path} not found in registered SWFs!')
@ -1077,6 +1078,8 @@ class AFPRenderer(VerboseOutput):
# during some bytecode update in this loop. # during some bytecode update in this loop.
if clip.frame > clip.requested_frame: if clip.frame > clip.requested_frame:
# Rewind this clip to the beginning so we can replay until the 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() clip.rewind()
self.vprint(f"{prefix} Processing frame {clip.frame} on our way to frame {clip.requested_frame}") self.vprint(f"{prefix} Processing frame {clip.frame} on our way to frame {clip.requested_frame}")
@ -1235,6 +1238,7 @@ class AFPRenderer(VerboseOutput):
self, self,
swf: SWF, swf: SWF,
only_depths: Optional[List[int]], only_depths: Optional[List[int]],
only_frames: Optional[List[int]],
movie_transform: Matrix, movie_transform: Matrix,
background_image: Optional[Image.Image], background_image: Optional[Image.Image],
) -> Generator[Image.Image, None, None]: ) -> 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_add_color = Color(0.0, 0.0, 0.0, 0.0)
actual_blend = 0 actual_blend = 0
if only_frames:
max_frame = max(only_frames)
else:
max_frame = None
# Now play the frames of the root clip. # Now play the frames of the root clip.
try: try:
while root_clip.playing and not root_clip.finished: while root_clip.playing and not root_clip.finished:
# Create a new image to render into. # 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)}") self.vprint(f"Rendering frame {frameno + 1}/{len(root_clip.source.frames)}")
# Go through all registered clips, place all needed tags. # Go through all registered clips, place all needed tags.
@ -1350,8 +1358,17 @@ class AFPRenderer(VerboseOutput):
while self.__is_dirty(root_clip): while self.__is_dirty(root_clip):
changed = self.__process_tags(root_clip, True) or changed 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: if changed or last_rendered_frame is None:
# Now, render out the placed objects. # 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 = 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) curimage = self.__render_object(curimage, root_clip, movie_transform, movie_mask, actual_mult_color, actual_add_color, actual_blend, only_depths=only_depths)
else: else:
@ -1360,12 +1377,17 @@ class AFPRenderer(VerboseOutput):
curimage = last_rendered_frame.copy() curimage = last_rendered_frame.copy()
# Return that frame, advance our bookkeeping. # Return that frame, advance our bookkeeping.
self.vprint(f"Finished rendering frame {frameno + 1}/{len(root_clip.source.frames)}")
last_rendered_frame = curimage last_rendered_frame = curimage
frameno += 1 frameno += 1
yield curimage 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: except KeyboardInterrupt:
# Allow ctrl-c to end early and render a partial animation. # 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!") print(f"WARNING: Interrupted early, will render only {frameno}/{len(root_clip.source.frames)} frames of animation!")
# Clean up # 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')) 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: def main() -> int:
parser = argparse.ArgumentParser(description="Konami AFP graphic file unpacker/repacker") parser = argparse.ArgumentParser(description="Konami AFP graphic file unpacker/repacker")
subparsers = parser.add_subparsers(help='Action to take', dest='action') subparsers = parser.add_subparsers(help='Action to take', dest='action')
@ -272,7 +288,13 @@ def main() -> int:
"--only-depths", "--only-depths",
type=str, type=str,
default=None, 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( render_parser.add_argument(
"--force-aspect-ratio", "--force-aspect-ratio",
@ -792,14 +814,30 @@ def main() -> int:
# Support rendering only certain depth planes. # Support rendering only certain depth planes.
if args.only_depths is not None: 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: 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"]: if fmt in ["GIF", "WEBP"]:
# Write all the frames out in one file. # Write all the frames out in one file.
duration = renderer.compute_path_frame_duration(args.path) 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: if len(images) == 0:
raise Exception("Did not render any frames!") raise Exception("Did not render any frames!")
@ -818,7 +856,15 @@ def main() -> int:
digits = f"0{int(math.log10(frames)) + 1}" digits = f"0{int(math.log10(frames)) + 1}"
for i, img in enumerate( 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}" fullname = f"{filename}-{i:{digits}}{ext}"