Render Flutter animation directly to video Render Flutter animation directly to video dart dart

Render Flutter animation directly to video


It's not pretty, but I have managed to get a prototype working.Firstly, all animations need to be powered by a single main animation controller so that we can step through to any part of the animation we want. Secondly, the widget tree that we want to record has to be wrapped inside a RepaintBoundary with a global key. The RepaintBoundary and it's key can produce snapshots of the widget tree as such:

Future<Uint8List> _capturePngToUint8List() async {    // renderBoxKey is the global key of my RepaintBoundary    RenderRepaintBoundary boundary = renderBoxKey.currentContext.findRenderObject();         // pixelratio allows you to render it at a higher resolution than the actual widget in the application.    ui.Image image = await boundary.toImage(pixelRatio: 2.0);    ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);    Uint8List pngBytes = byteData.buffer.asUint8List();    return pngBytes;  }

The above method can then be used inside a loop which captures the widget tree into pngBytes, and steps the animationController forward by a deltaT specified by the framerate you want as such:

double t = 0;int i = 1;setState(() {  animationController.value = 0.0;});Map<int, Uint8List> frames = {};double dt = (1 / 60) / animationController.duration.inSeconds.toDouble();while (t <= 1.0) {  print("Rendering... ${t * 100}%");  var bytes = await _capturePngToUint8List();  frames[i] = bytes;  t += dt;  setState(() {    animationController.value = t;  });  i++;}

Finally, all these png frames can be piped into an ffmpeg subprocess to be written into a video.I haven't managed to get this part working nicely yet, so what I've done instead is write out all the png-frames into actual png files and I then manually run ffmpeg inside the folder where they are written. (Note: I've used flutter desktop to be able to access my installation of ffmpeg, but there is a package on pub.dev to get ffmpeg on mobile too)

List<Future<File>> fileWriterFutures = [];frames.forEach((key, value) {  fileWriterFutures.add(_writeFile(bytes: value, location: r"D:\path\to\my\images\folder\" + "frame_$key.png"));});await Future.wait(fileWriterFutures);_runFFmpeg();

Here is my file-writer help-function:

Future<File> _writeFile({@required String location, @required Uint8List bytes}) async {  File file = File(location);  return file.writeAsBytes(bytes);}

And here is my FFmpeg runner function:

void _runFFmpeg() async {  // ffmpeg -y -r 60 -start_number 1 -i frame_%d.png -c:v libx264 -preset medium -tune animation -pix_fmt yuv420p test.mp4  var process = await Process.start(      "ffmpeg",      [        "-y", // replace output file if it already exists        "-r", "60", // framrate        "-start_number", "1",        "-i", r"./test/frame_%d.png", // <- Change to location of images        "-an", // don't expect audio        "-c:v", "libx264rgb", // H.264 encoding        "-preset", "medium",        "-crf",        "10", // Ranges 0-51 indicates lossless compression to worst compression. Sane options are 0-30        "-tune", "animation",        "-preset", "medium",        "-pix_fmt", "yuv420p",        r"./test/test.mp4" // <- Change to location of output      ],      mode: ProcessStartMode.inheritStdio // This mode causes some issues at times, so just remove it if it doesn't work. I use it mostly to debug the ffmpeg process' output   );  print("Done Rendering");}


I used Erik's answer as a starting point for my own implementation and would like to add to his original answer.

After saving all the png images to the target location, I used the ffmpeg package for Flutter to create a video of all the images. Since it took a while to find the right settings to create a video that could also be played by QuickTime Player, I want to share them with you:

final FlutterFFmpeg _flutterFFmpeg =    new FlutterFFmpeg(); // Create new ffmpeg instance somewhere in your code// Function to create the video. All png files must be available in your target location prior to calling this function.Future<String> _createVideoFromPngFiles(String location, int framerate) async {  final dateAsString = DateFormat('ddMMyyyy_hhmmss').format(DateTime.now());  final filePath =      "$location/video_$dateAsString.mov"; // had to use mov to be able to play the video on QuickTime  var arguments = [    "-y", // Replace output file if it already exists    "-r", "$framerate", // Your target framerate    "-start_number", "1",    "-i",    "$location/frame_%d.png", // The location where you saved all your png files    "-an", // Don't expect audio    "-c:v",    "libx264", // H.264 encoding, make sure to use the full-gpl ffmpeg package version    "-preset", "medium",    "-crf",    "10", // Ranges 0-51 indicates lossless compression to worst compression. Sane options are 0-30    "-tune", "animation",    "-preset", "medium",    "-pix_fmt",    "yuv420p", // Set the pixel format to make it compatible for QuickTime    "-vf",    "pad=ceil(iw/2)*2:ceil(ih/2)*2", // Make sure that height and width are divisible by 2    filePath  ];  final result = await _flutterFFmpeg.executeWithArguments(arguments);  return result == 0      ? filePath      : ''; // Result == 0 indicates that video creation was successful}

If you are using libx264, make sure that you follow the instructions for the flutter_ffmpeg package: You must use the full-gpl version, which includes the x264 library.

Depending on the length of your animation, the desired framerate, the pixel ratio and the device's memory, saving all frames before writing the files could cause memory issues. Therefore, depending on your use case, you may want to pause/resume your animation and write the files in multiple batches so that you don't risk exceeding the available memory.