Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,20 @@ call show() ! viewer
use fortplot

anim = FuncAnimation(update_frame, frames=100, interval=50, fig=fig)
call anim%save("movie.mp4", fps=24, status=status)
call anim%save("movie.mp4", fps=24, status=status) ! ffmpeg
call anim%save("movie.txt", status=status) ! ASCII frames
```
Requires ffmpeg: `apt install ffmpeg` / `brew install ffmpeg` / `choco install ffmpeg`

Output formats: `.mp4`, `.avi`, `.mkv` (require ffmpeg), and `.txt` (ASCII frames, no
extra dependencies).

Replay a `.txt` animation in the terminal:

```bash
fpm run --target fortplot_play_ascii -- movie.txt --fps 24 --loop
```

ffmpeg install: `apt install ffmpeg` / `brew install ffmpeg` / `choco install ffmpeg`

## Build

Expand Down
94 changes: 94 additions & 0 deletions app/fortplot_play_ascii.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
program fortplot_play_ascii
!! CLI player for .txt animations produced by save_animation.
!!
!! Usage: fortplot_play_ascii <file.txt> [--fps N] [--loop] [--no-clear]
use iso_fortran_env, only: output_unit, error_unit
use fortplot_ascii_player, only: play_ascii_animation, ascii_player_options_t, &
ASCII_PLAYER_DEFAULT_FPS
implicit none

character(len=4096) :: arg
character(len=4096) :: filename
type(ascii_player_options_t) :: opts
integer :: i, nargs, status, ios

filename = ""
opts%fps = ASCII_PLAYER_DEFAULT_FPS
opts%loop = .false.
opts%clear_screen = .true.
opts%dry_run = .false.
opts%max_loops = 1

nargs = command_argument_count()
if (nargs < 1) then
call print_usage()
stop 2
end if

i = 1
do while (i <= nargs)
call get_command_argument(i, arg)
select case (trim(arg))
case ("-h", "--help")
call print_usage()
stop 0
case ("--fps")
if (i + 1 > nargs) call die("--fps requires a value")
i = i + 1
call get_command_argument(i, arg)
read(arg, *, iostat=ios) opts%fps
if (ios /= 0 .or. opts%fps <= 0) call die("invalid --fps value")
case ("--loop")
opts%loop = .true.
opts%max_loops = 0
case ("--no-clear")
opts%clear_screen = .false.
case ("--once")
opts%loop = .false.
case ("--dry-run")
opts%dry_run = .true.
opts%clear_screen = .false.
case default
if (len_trim(filename) == 0) then
filename = arg
else
call die("unexpected argument: " // trim(arg))
end if
end select
i = i + 1
end do

if (len_trim(filename) == 0) then
call print_usage()
stop 2
end if

call play_ascii_animation(trim(filename), opts, status=status)
if (status /= 0) stop 1

contains

subroutine print_usage()
write(output_unit, '(A)') &
"Usage: fortplot_play_ascii <file.txt> [--fps N] [--loop] [--once] [--no-clear] [--dry-run]"
write(output_unit, '(A)') &
" Plays an ASCII animation produced by save_animation('*.txt')."
write(output_unit, '(A)') &
" --fps N target frames per second (default 10, must be > 0)"
write(output_unit, '(A)') &
" --loop loop forever (Ctrl-C to stop)"
write(output_unit, '(A)') &
" --once play once (default)"
write(output_unit, '(A)') &
" --no-clear do not emit ANSI clear-screen between frames"
write(output_unit, '(A)') &
" --dry-run parse and count frames without printing or sleeping"
end subroutine print_usage

subroutine die(msg)
character(len=*), intent(in) :: msg
write(error_unit, '(A)') "fortplot_play_ascii: " // msg
write(error_unit, '(A)') "Try --help."
stop 2
end subroutine die
end program fortplot_play_ascii
8 changes: 8 additions & 0 deletions example/fortran/animation/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
title: Animation

Generate an MP4 animation from a sequence of frames.

`save_animation` also accepts a `.txt` filename to emit frames as ASCII renderings
delimited by `=== Frame N ===` headers. Replay them in the terminal with the
`fortplot_play_ascii` CLI app:

```bash
fpm run --target fortplot_play_ascii -- output.txt --fps 24 --loop
```
36 changes: 25 additions & 11 deletions src/animation/fortplot_animation.f90
Original file line number Diff line number Diff line change
@@ -1,44 +1,58 @@
! Facade module for animation functionality - maintains backward compatibility
module fortplot_animation
use fortplot_animation_core
use fortplot_animation_validation
use fortplot_animation_rendering
use fortplot_animation_pipeline
use fortplot_animation_core, only: animation_t, animate_interface, &
save_animation_impl, FuncAnimation_core => FuncAnimation, &
DEFAULT_FRAME_INTERVAL_MS
use fortplot_figure_core, only: figure_t
use fortplot_animation_pipeline, only: save_animation_full
implicit none
private

! Re-export everything needed for backward compatibility
public :: animation_t
public :: FuncAnimation
public :: animate_interface
public :: save_animation

! Register the save implementation on module initialization
logical, save :: impl_registered = .false.

contains

! Wrapper to maintain backward compatibility for save method
function FuncAnimation(animate_func, frames, interval, fig) result(anim)
!! Facade constructor: builds the animation_t and registers the save
!! implementation so anim%save(...) works directly (per README).
procedure(animate_interface) :: animate_func
integer, intent(in) :: frames
integer, intent(in), optional :: interval
type(figure_t), target, intent(in), optional :: fig
type(animation_t) :: anim

if (present(fig)) then
anim = FuncAnimation_core(animate_func, frames, interval, fig)
else
anim = FuncAnimation_core(animate_func, frames, interval)
end if
call register_save_implementation()
end function FuncAnimation

subroutine save_animation(anim, filename, fps, status)
type(animation_t), intent(inout) :: anim
character(len=*), intent(in) :: filename
integer, intent(in), optional :: fps
integer, intent(out), optional :: status

call register_save_implementation()
call save_animation_full(anim, filename, fps, status)
end subroutine save_animation

! Type-bound procedure for animation save method implementation
subroutine animation_save_impl(anim, filename, fps, status)
class(animation_t), intent(inout) :: anim
character(len=*), intent(in) :: filename
integer, intent(in), optional :: fps
integer, intent(out), optional :: status

call save_animation_full(anim, filename, fps, status)
end subroutine animation_save_impl

! Register the save implementation pointer
subroutine register_save_implementation()
if (.not. impl_registered) then
save_animation_impl => animation_save_impl
Expand Down
Loading
Loading