Add warning and command-line flag for working with animations that loop forever.
This commit is contained in:
parent
089eef1588
commit
bc46d83452
@ -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
|
||||||
|
@ -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}"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user