Java

[Java] FFmpeg로 gif파일 mp4로 변환하기 - 2

에드박 2021. 9. 30. 02:42

해당 글은 Ubuntu 운영체제에서 제대로 동작하지 않는것을 발견했습니다 .  참고하고 읽어주세요!

지난 글에서 FFmpeg를 사용해서 gif 파일을 mp4로 변환하는것을 진행했습니다.

 

파일 변환은 잘 되지만 환경설정에 있어서 너무 불편한 부분이 있었습니다.

  • FFmpeg가 서버내에 설치돼있어야 합니다.
  • FFmpeg의 설치경로를 사용자가 직접 설정해줘야합니다. -> 테스트가 힘든 환경입니다.
    • MacOS에서 ffmpeg를 설치하면 기본 경로는 /usr/local/bin/ffmpeg 입니다. (homebrew 사용)
    • ubuntu에서 ffmpeg를 설치하면 기본 경로는 /usr/bin/ffmpeg 입니다. (apt 사용)

이로 인해서 ffmpeg를 깜빡하고 설치를 안하거나 경로 설정을 제대로 안해주면 애플리케이션 실행이 안되고 테스트도 전부 실패해버리는 이슈가 있었습니다.

이 부분은 애플리케이션 실행을 위한 추가적인 행위가 필요하다는 것으로 굉장히 불편합니다.

문서화해서 알려주는 방법도 있겠지만 어쩔 수 없을 때 하는 최후의 방법이라 생각합니다.

 

불편 해소를 위해 다음과 같은 목표를 세웠습니다.

누군가 git clone을 했을 때 바로 실행할 수 있는 상태였으면 좋겠다.

목표를 실현하기 위한 방법은 뭐가 있을까 생각해봤습니다.

  • ffmpeg를 애플리케이션 내부에 포함시키자 (설치와 경로 설정을 모두 애플리케이션이 관리하도록)
  • ffmpeg 설치는 해야하지만 애플리케이션 내부의 경로를 사용할  있는 방법 (설치는 사용자가 직접, 경로는 애플리케이션이 찾도록)

여기서 가장 이상적인 실현방법인 'ffmpeg를 애플리케이션 내부에 포함시키자' 를 찾아봤습니다.

최종적으로 세가지 주요 라이브러리를 찾았습니다.

  • org.bytedeco.javacpp : JNI 코드를 생성하고 C++ 컴파일러에게 전달하여 네이티브 라이브러리를 빌드합니다.
  • org.bytedeco.ffmpeg : 실행할 수 있는 ffmpeg를 추출할 수 있습니다.
  • org.bytedeco.javacv-platform : OpenCV, FFmpeg 등의 자주 사용하는 라이브러리들을 다양한 플랫폼(운영체제)에 적절한 jar를 제공합니다.

아래는 org.bytedeco.javacv-platform의 ffmpeg.platform 내부 코드입니다.

 

역시나 gradle의 간편한 의존성추가 혜택을 누려서 다음과 같이 의존성을 추가합니다.

implementation 'org.bytedeco:ffmpeg:4.4-1.5.6'
implementation 'org.bytedeco:javacpp:1.5.6'
implementation 'org.bytedeco:javacv-platform:1.5.6'

 

이전에 사용하던 코드를 먼저 살펴보겠습니다. (ffmpeg-cli-wrapper 만 사용하던 코드)

@SpringBootTest
class FfmpegTest {

    @Value("${ffmpeg.path}")
    private String ffmpegPath; // 설정파일에 정의한 ffmpeg의 경로를 가져옴

    @Value("${ffprobe.path}")
    private String ffprovePath;

    private static final String GIF_FILENAME = "static/image/jjv1FK.gif";

    @Test
    void test() throws IOException {
        System.out.println(ffmpegPath);
        System.out.println(ffprovePath);

        URL resource = getClass().getClassLoader().getResource(GIF_FILENAME);
        File gifFile = new File(resource.getPath());
        File mp4File = new File(gifFile.getPath().replace(".gif", ".mp4"));

        final FFmpeg fFmpeg = new FFmpeg(ffmpegPath);
        final FFprobe fFprobe = new FFprobe(ffprovePath);
        System.out.println("initComplete!!!");

        FFmpegBuilder builder = new FFmpegBuilder()
                .overrideOutputFiles(true)
                .addExtraArgs("-r", "10")
                .setInput(gifFile.getPath())
                .addOutput(mp4File.getPath())
                .addExtraArgs("-an")
                .setVideoPixelFormat("yuv420p")
                .setVideoMovFlags("faststart")
                .setVideoWidth(240)
                .setVideoHeight(182)
                .setVideoFilter("scale=trunc(iw/2)*2:trunc(ih/2)*2")
                .done();

        FFmpegExecutor executor = new FFmpegExecutor(fFmpeg, fFprobe);
        FFmpegJob job = executor.createJob(builder);
        System.out.println("before run job state!!! is " + job.getState());
        assertThat(job.getState()).isEqualTo(FFmpegJob.State.WAITING);
        job.run();
        System.out.println("after run job state!!! is " + job.getState());
        assertThat(job.getState()).isEqualTo(FFmpegJob.State.FINISHED);        
    }
}

필드를 보시면 ffmpeg의 path를 설정파일에서 가져오고 있습니다. (편의성을 위해서 SpringBoot를 사용했습니다.)

개발환경에 따라 ffmpeg의 경로가 달라지면 경로를 설정파일에 맞게 맞춰주거나 설정파일에 값을 변경해야합니다.

 

이것을 개선하기위해 위의 의존성을 활용한 로직으로 변경합니다.

import org.bytedeco.javacpp.Loader;
import org.junit.jupiter.api.Test;

import java.io.IOException;

class JavaCvFFmpegTest {

    private static final String GIF_FILENAME = "static/image/jjv1FK.gif";

    // org.bytedeco.ffmpeg 라이브러리에 있는 ffmpeg 프로그램을 불러옵니다.
    String ffmpegPath = Loader.load(org.bytedeco.ffmpeg.ffmpeg.class);

    @Test
    void test() throws IOException {
        URL resource = getClass().getClassLoader().getResource(GIF_FILENAME);
        File gifFile = new File(resource.getPath());
        File mp4File = new File(gifFile.getPath().replace(".gif", ".mp4"));

        ProcessBuilder pb = new ProcessBuilder(
                    ffmpeg,
                    "-y",
                    "-v",
                    "error",
                    "-r",
                    "10",
                    "-i",
                    gifFile.getPath(),
                    "-pix_fmt",
                    "yuv420p",
                    "-movflags",
                    "faststart",
                    "-s",
                    "240x182",
                    "-vf",
                    "scale=trunc(iw/2)*2:trunc(ih/2)*2",
                    "-an",
                    mp4File.getPath()
            );

        pb.redirectErrorStream(true);
        Process process = pb.start();
        process.waitFor();
    }
}

ffmpegPath 필드를 보면 javaccp의 Loader 클래스를 이용해 org.bytedeco.ffmpeg의 ffmpeg를 불러오는것을 확인할 수 있습니다.

이제 ffmpeg를 설치하지 않고 application.yml 같은 설정파일에 경로를 설정하지 않아도 ffmpeg를 사용할 수 있게됐습니다.

 

하지만 코드가 많이 지저분합니다. 이대로는 사용하고있는 ffmpeg 명령어를 알아보는게 매우 힘듭니다.

그래서 이전에 ffmpeg-cli-wrapper의 FFmpegBuilder를 함께 활용합니다.

 

import net.bramp.ffmpeg.FFmpeg;
import net.bramp.ffmpeg.FFmpegExecutor;
import net.bramp.ffmpeg.builder.FFmpegBuilder;
import net.bramp.ffmpeg.job.FFmpegJob;
import org.bytedeco.javacpp.Loader;
import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.IOException;
import java.net.URL;

class JavaCvFFmpegTest {

    private static final String GIF_FILENAME = "static/image/jjv1FK.gif";

    String ffmpegPath = Loader.load(org.bytedeco.ffmpeg.ffmpeg.class);

    @Test
    void test() throws IOException {
        URL resource = getClass().getClassLoader().getResource(GIF_FILENAME);
        File gifFile = new File(resource.getPath());
        File mp4File = new File(gifFile.getPath().replace(".gif", ".mp4"));

        FFmpegBuilder builder = new FFmpegBuilder()
                .overrideOutputFiles(true)
                .addExtraArgs("-r", "10")
                .setInput(gifFile.getPath())
                .addOutput(mp4File.getPath())
                .addExtraArgs("-an")
                .setVideoPixelFormat("yuv420p")
                .setVideoMovFlags("faststart")
                .setVideoWidth(240)
                .setVideoHeight(182)
                .setVideoFilter("scale=trunc(iw/2)*2:trunc(ih/2)*2")
                .done();

        FFmpegExecutor executor = new FFmpegExecutor(new FFmpeg(ffmpegPath));
        FFmpegJob job = executor.createJob(builder);
        job.run();
    }
}

이제 ffmpeg의 명령어 설정을 메서드로 나타내어 가독성도 좋아졌습니다.

 


 

  • 2021-11-04
    • Ubuntu 환경에서는 ffmpeg를 직접 설치해야하는 이슈가 있습니다.(경로 설정은 따로 할 필요없음)