Share technical knowledge of Java,.NET, Javascript, efficiency, software engineering, programming languages, etc.

GitHub TechShare, Just For Fun

To begin with, merry Christmas to the nuggets. 🎄

PSD automatically generates animated video

No more words, see the effect first.

Results 1:

Effect 2:

Note: The above is a video preview, which uses animate. CSS for random generation of animation effects.

While the video animations implemented in this article are very simple, mainly showing how to automatically generate video from a hierarchical PSD file, the cornerstone remotion project of this code goes beyond that and can be used to achieve very complex videos.

remotion

The above effect is implemented using Remotion, an open source project that lets you programmatically create videos using React. Remotion promo video is made using Remotion. This is a very complex video. Through this promo video, it shows the power of Remotion.

Core concepts of Remotion

The core concept behind Remotion is that you can render any element freely using React as long as you provide a currentFrame and a blank canvas.

import { useCurrentFrame } from "remotion";
 
export const MyVideo = () = > {
  const frame = useCurrentFrame();
 
  return (
    <div style={{ flex: 1.justifyContent: "center", alignItems: "center}} ">
      The current frame is {frame}.
    </div>
  );
};
Copy the code

A video can be expressed as an image function that changes over time, changing the content with each frame, and eventually producing an animation effect.

A remotion video also has several core attributes: width, height, durationInFrames, and FPS. These properties are available via useVideoConfig hook.

import { useVideoConfig } from "remotion";
 
export const MyVideo = () = > {
  const { fps, durationInFrames, width, height } = useVideoConfig();
 
  return (
    <div style={{ flex: 1.justifyContent: "center", alignItems: "center}} ">
      This video is {durationInFrames / fps} seconds long.
    </div>
  );
};
Copy the code

That is, if we define the width, height, total frame count, and frame rate (FPS) of the video, remotion will provide us with the currentFrame at runtime and render the image in its current state based on the currentFrame count and video properties.

Encapsulates animate. CSS animations

Instead of specifying only one animation style when using animate. CSS, rendering images now requires computing the current animation position based on the three core parameters durationInFrames, currentFrame, and FPS. Wouldn’t it be tedious to code the computation every time, so we package an Animate component for reuse.

Calculation logic:

const frame = useCurrentFrame(); const { durationInFrames, fps } = useVideoConfig(); const duration = durationInFrames / fps; // Animation duration const delay = (frame/FPS) < duration? (frame/FPS) : duration-0.00001; // Where is the animationCopy the code

It can be seen that the calculation logic is not complicated, so the animation playing position can be set on the style attribute of the animation element.

<div
	style={{
		animationPlayState: 'paused',
		animationName: animate.animationDelay: `-The ${delay}s`,
		animationDuration:` ${duration}s`,}} >
	{children}
</div>
Copy the code

Complete code: remotion-Animation.

We’re halfway through mastering the animation.CSS wrapper, so we’ll use Java to parse the PSD and set random animation effects on the returned PSD elements.

Parse the PSD using Java

There are a lot of methods to parse PSD, because the author just conveniently write PSD parsing in Java, so here is the use of Java to introduce, but also recommend everyone to use PSD.js, so that only through front-end coding can achieve the effect of this article.

Parsing PSD using the Java-PSD-Library open source project, PSD processing part of the code as follows:

// Save the picture element in the PSD and return to the PSD animation configuration
private static PsdAnimateConfig processPsd(File inputFile, File outputDir) throws IOException {
    Psd psdFile = new Psd(inputFile);
    outputDir.mkdirs();

    PsdAnimateConfig animateConfig = new PsdAnimateConfig();
    animateConfig.setWidth(psdFile.getWidth());
    animateConfig.setHeight(psdFile.getHeight());
    animateConfig.setLayers(new ArrayList<>());

    int total = psdFile.getLayersCount();
    for (int i = 0; i < total; i++) {
        Layer layer = psdFile.getLayer(i);
        writeLayer(layer, outputDir, 0, animateConfig);
    }

    // Select an animation at random (all supported animations are in TXT file here)
    List<String> animates = FileUtil.readLines(ResourceUtils.getFile("classpath:animate.txt"), Charset.defaultCharset()).stream().filter(t -> ! t.contains("Out")).collect(Collectors.toList());
    String animate = animates.get(new Random().nextInt(animates.size()));

    int i = 0;
    for (AnimateLayer layer : animateConfig.getLayers()) {
        if (layer.getIndex() > 1) {
            layer.setAnimate(animate);
            layer.setFromMillisecond(i * 150); // Set the animation start time
            layer.setDurationMillisecond(1500); // Set the animation durationi++; }}return animateConfig;
}

// Save the PSD image element and add the image element to the animation configuration
private static void writeLayer(Layer layer, File baseDir, int index, PsdAnimateConfig animateConfig) throws IOException {
    if (layer.getType() == LayerType.FOLDER) {
        for (int i = 0; i < layer.getLayersCount(); i++) { Layer subLayer = layer.getLayer(i); writeLayer(subLayer, baseDir, index++, animateConfig); }}if (layer.getType() == LayerType.NORMAL) {
        String path = layer.toString();
        File outFile = new File(baseDir, path + ".png");
        outFile.getParentFile().mkdirs();
        if(layer.getImage() ! =null) {
            if(! outFile.exists()) { ImageIO.write(layer.getImage(),"png", outFile);
            }
            AnimateLayer animateLayer = new AnimateLayer();
            animateLayer.setX(layer.getX());
            animateLayer.setY(layer.getY());
            animateLayer.setWidth(layer.getWidth());
            animateLayer.setHeight(layer.getHeight());
            animateLayer.setName(path + ".png"); animateLayer.setIndex(index); animateConfig.getLayers().add(animateLayer); }}}Copy the code

The code is relatively simple, so I won’t go into it here, but let’s see how the front end animates based on the returned configuration.

Create an animated video from a PSD configuration

In the Remotion component, we get the data from the interface and then continue rendering. We get the configuration from the Java server interface and render the component step by step according to the configuration.

import "animate.css";
import {
  continueRender,
  delayRender,
  Sequence,
  useCurrentFrame,
  useVideoConfig,
} from "remotion";
import { Animation } from "remotion-animation";
import { useEffect, useState } from "react";

export const PsdVideo = () = > {
  const [data, setData] = useState(null);
  const [handle] = useState(() = > delayRender());

  const fetchData = async() = > {const response = await fetch("http://127.0.0.1:8080/psd/animateConfig");
    const json = await response.json();
    setData(json);
    continueRender(handle);
  };

  useEffect(() = >{ fetchData(); } []);const videoConfig = useVideoConfig();
  const frame = useCurrentFrame();
  if (data == null) {
    return <div>In the load</div>;
  }

  function getDiv<T> (layer: T) {
    return (
      <div
        style={{
          position: "absolute",
          width: layer.width.height: layer.height.top: layer.y.left: layer.x,}} >
        <img
          src={"http://127.0.0.1:8080/psd/image?name=" + layer.name} width={layer.width} height={layer.height} /> 
    );
  }

  function getAnimate<T> (layer: T) {
    const duration = layer.durationMillisecond / 1000;
    return (
      <> {layer.animate && ( 
        
          {getDiv(layer)} 
         )} {! layer.animate && getDiv(layer)} 
    );
  }

  return (
    relative}} ">{data.layers.map((layer) => { const needPlay = layer.durationMillisecond; // Set the time to return (<>
            {needPlay && (
              <Sequence
                from={parseInt(
                  (videoConfig.fps * layer.fromMillisecond) / 1000
                )}
                durationInFrames={Infinity}
              >
                {getAnimate(layer)}
              </Sequence>)} {! needPlay && getAnimate(layer)}</>
        );
      })}
    </div>
  );
};
Copy the code

conclusion

This article code to achieve the video effect is relatively simple, but it mentions the core concept of generating animation, data from the server to dynamically generate video, etc. Readers can try to create more complex animation and export to video if you are interested.