본문 바로가기
오픈소스

[bramp/ffmpeg-cli-wrapper] invalid time 'N/A' 예외 발생 원인 파악

by Sondho 2024. 10. 20.

들어가기 전

  • 원인을 읽기 전에 정리를 먼저 본 다음 보시는 걸 추천한다.

문제 발생

실행한 코드

private String encodingToHls(File file) throws IOException {
        String uploadedFileUrl = uploadPath + "/master.m3u8";

        FFmpegProbeResult probeResult = fFprobe.probe(file.getAbsolutePath());
        FFmpegBuilder fFmpegBuilder = new FFmpegBuilder()
            .setInput(file.getAbsolutePath())
            .overrideOutputFiles(true)
            .addOutput(uploadedFileUrl)
            .setFormat("hls")
            .addExtraArgs("-hls_time", "10")
            .addExtraArgs("-hls_list_size", "0")
            .addExtraArgs("-hls_segment_filename", uploadPath + File.separator + "master_%08d.ts")
            .done();

        FFmpegExecutor executor = new FFmpegExecutor(fFmpeg, fFprobe);
        FFmpegJob job = executor
            .createJob(fFmpegBuilder, progress -> {
                log.info("progress ==> {}", progress);
                if (progress.status.equals(Progress.Status.END)) {
                    log.info("===== JOB FINISHED =====");
                }
            });
        job.run();
        if (job.getState() == FFmpegJob.State.FINISHED) {
            log.info("FINISH!!");
        }
        return uploadedFileUrl;

예외 발생

2024-10-17T17:05:43.016+09:00  INFO 27071 --- [nio-8080-exec-1] net.bramp.ffmpeg.RunProcessFunction      : /opt/homebrew/bin/ffmpeg -y -v error -progress tcp://127.0.0.1:60771 -i /temp/테스트_동영상_202410171704165790.mov -f hls -hls_time 10 -hls_list_size 0 -hls_segment_filename /media/master_%08d.ts /media/master.m3u8
Exception in thread "TcpProgressParser(tcp://127.0.0.1:60771)" java.lang.IllegalArgumentException: invalid time 'N/A'
    at net.bramp.ffmpeg.FFmpegUtils.fromTimecode(FFmpegUtils.java:83)
    at net.bramp.ffmpeg.progress.Progress.parseLine(Progress.java:164)
    at net.bramp.ffmpeg.progress.StreamProgressParser.processReader(StreamProgressParser.java:41)
    at net.bramp.ffmpeg.progress.StreamProgressParser.processStream(StreamProgressParser.java:32)
    at net.bramp.ffmpeg.progress.TcpProgressParserRunnable.run(TcpProgressParserRunnable.java:35)
    at java.base/java.lang.Thread.run(Thread.java:840)

시도

  • ffmpeg-ss 옵션을 추가해보자
  • ⇒ ffmpeg 문제가 아니었음
  • 디버깅해서 하나씩 따라가보기
    • FFmpegUtils.fromTimecode(String time)에서 입력으로 들어오는 time이 “N/A” 이기 때문에 예외처리가 되버림


원인

ffmpeg-cli-wrapper 라이브러리에서는 내가 코드로 추가한 ffmpeg 옵션 이외에도 아래의 옵션이 자동으로 입력된다.

  • -y
    • 묻지 않고 출력 파일을 덮어쓴다.
  • -v [flags+]
    • 라이브러리에서 사용하는 로깅 수준과 플래그를 설정한다.
    • quiet, error, warning, info, debug 등
  • -progress url
    • 프로그램 진행 상황 정보를 url로 보낸다.
    • 진행률 정보는 주기적으로 그리고 인코딩 프로세스가 끝날 때 작성된다.
    • “key=value” 줄로 구성되어 있고, 키는 영숫자로만 구성된다.
    • 진행률 정보 시퀀스의 마지막 키는 “progress”이다. 값은 “continue”와 “end”가 있다.

*그 외에 ffmpeg 옵션을 궁금하다면 ffmpeg Documentation 참고 (https://www.ffmpeg.org/ffmpeg-all.html)

 

 

ffmepg-cli-wrapper의 개발자들은 ffmpeg가 실행되는 동안 출력을 분석하고 진행 상황을 출력해주는 기능을 추가하기 위해 아래와 같은 의견을 나누었다.

ffmpeg의 출력은 -progress /dev/stdout 옵션을 통해 현재 터미널에서 진행상황을 출력해보면 아래와 같은 결과가 출력된다.

https://github.com/bramp/ffmpeg-cli-wrapper/issues/21#issuecomment-226985803

 

(짧게 이해해보기로는) 출력을 분석하고 진행상황을 출력하는 기능이 동작하는 방식은 아래와 같다. (실제로는 엄청나게 많은 과정을 거친다. 고작 이것이 자바다를 1회독 한 내 자바 실력으로는 제대로 이해하기에는 어림도 없다.. 오픈소스 개발자들은 대단하다)

  1. Runnable을 상속받은 TcpProgressParserRunnable을 실행시킨다. (스레드 A)
  2. -progress 옵션을 통해 스레드A에 출력 결과를 보낸다.
  3. StreamProgressParser.processReader()를 통해 출력 결과를 읽어온다.
  4. Progress.parserLine()으로 출력 결과가 없을 때까지 읽으면서 파싱을 진행한다.
at net.bramp.ffmpeg.FFmpegUtils.fromTimecode(FFmpegUtils.java:83)
at net.bramp.ffmpeg.progress.Progress.parseLine(Progress.java:164)
at net.bramp.ffmpeg.progress.StreamProgressParser.processReader(StreamProgressParser.java:41)
at net.bramp.ffmpeg.progress.StreamProgressParser.processStream(StreamProgressParser.java:32)
at net.bramp.ffmpeg.progress.TcpProgressParserRunnable.run(TcpProgressParserRunnable.java:35)
at java.base/java.lang.Thread.run(Thread.java:840)

 

여기에서 진행상황 중에 out_time 이라는 키가 포함되어 있는데, 왜인지 모르겠지만 그 값이 out_time=N/A로 들어온다. 여기서 문제가 발생하는데 bitrate나 total_size는 값이 N/A인 경우, -1L을 저장하게 되어 있는데 out_time은 그런게 없이 냅다 Exception을 반환해버린다. 그렇기 때문에 Exception in thread "TcpProgressParser(tcp://127.0.0.1:60771)" java.lang.IllegalArgumentException: invalid time 'N/A' 라는 예외가 발생해버린 것이다.

out_time=N/A

 

아래는 Progress의 parseLine 메서드이다.

bitrate와 total_size는 N/A일 때, 따로 처리가 되어있다.

 

아래는 FFmpegUtils의 fromTimecode 메서드이다.

이 부분에서 문제가 생기는데 Mathcher를 통해 time을 분석하고, find()를 통해 값을 정규식에 일치하는 값이 있는지 확인하는데 out_time=N/A로 입력이 들어왔기 때문에 예외를 발생시킨다.

throw new IllegalArgumentException("invalid time '" + time + "'");

 

 

원인을 찾았으니 누군가 관련된 이슈를 올렸는지 깃허브 레포지토리에서 확인해봤는데, 이미 관련된 주제로 이슈가 올라와있었고 해당 이슈가 closed가 된 상태였다.

게다가 해당 이슈에 연결된 PR이 있고, Merged가 되어있었고, time이 N/A인 경우, -1을 반환하게끔 코드도 변경되어 있었다.

깃허브에는 업데이트가 되어있다. (https://github.com/bramp/ffmpeg-cli-wrapper/pull/315/files)


정리

정리하자면 ffmepg-cli-wrapper의 개발자들이 ffmepg가 실행되는 동안 출력을 파싱하고 진행 상황을 출력하는 기능을 추가했다. 이때, 파싱하는 과정에서 특정 부분(out_time)에 N/A인 상태가 되어있고, out_time=N/A가 입력됐을 때, 별도의 처리가 안된 상태라서 예외가 발생하는 것이다.

 

이 문제는 이미 이슈화 됐고, 코드 변경도 되었지만 maven central repository에는 배포가 안된 상태였기 때문에 발생한 문제였다. 

 

아래 스크린샷에서도 확인할 수 있듯이 spring boot의 의존성에 가장 최신 버전인 'net.bramp.ffmpeg:ffmpeg:0.8.0'를 추가해봐도 관련 코드는 업데이트가 되어있지 않다.

(2024.11.7 기준) https://mvnrepository.com/artifact/net.bramp.ffmpeg/ffmpeg
업데이트 되어 있지 않다.
ffmpeg-0.8.0 버전

 

 

해결 방법

두 가지 방법을 생각할 수 있겠다.

  1. Progress 클래스 관련된 코드를 지우고 사용하는 방법 (우선,이 방법으로 진행할 예정)
  2. 깃허브 코드를 직접 가져오는 방법 (사용해보지 않았다)

실제로 createJob을 할 때, listener 부분을 비우고 사용하면 해당 예외가 발생하지 않는다. 대신 진행사항을 로깅할 수 없다.

 

아래는 변경된 코드이다.

private String encodingToHls(File file) throws IOException {
        File destinationUrl = new File(uploadPath);
        if (!destinationUrl.exists()) {
            destinationUrl.mkdir();
        }
        String uploadedFileUrl = uploadPath + "/master.m3u8";
        FFmpegProbeResult probeResult = fFprobe.probe(file.getAbsolutePath());
        FFmpegBuilder fFmpegBuilder = new FFmpegBuilder()
            .setInput(file.getAbsolutePath())
            .overrideOutputFiles(true)
            .addOutput(uploadedFileUrl)
            .setFormat("hls")
            .addExtraArgs("-hls_time", "10")
            .addExtraArgs("-hls_list_size", "0")
            .addExtraArgs("-hls_segment_filename", uploadPath + File.separator + "master_%08d.ts")
            .done();

        FFmpegExecutor executor = new FFmpegExecutor(fFmpeg, fFprobe);
        FFmpegJob job = executor
            .createJob(fFmpegBuilder);
        job.run();
        return uploadedFileUrl;
    }

추가

아래는 -progress 옵션과 -v 옵션을 제외하고 실행했을 때, 출력되는 결과이다.

ffmpeg version 7.1 Copyright (c) 2000-2024 the FFmpeg developers
  built with Apple clang version 15.0.0 (clang-1500.3.9.4)
  configuration: --prefix=/opt/homebrew/Cellar/ffmpeg/7.1 --enable-shared --enable-pthreads --enable-version3 --cc=clang --host-cflags= --host-ldflags='-Wl,-ld_classic' --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libaribb24 --enable-libbluray --enable-libdav1d --enable-libharfbuzz --enable-libjxl --enable-libmp3lame --enable-libopus --enable-librav1e --enable-librist --enable-librubberband --enable-libsnappy --enable-libsrt --enable-libssh --enable-libsvtav1 --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libspeex --enable-libsoxr --enable-libzmq --enable-libzimg --disable-libjack --disable-indev=jack --enable-videotoolbox --enable-audiotoolbox --enable-neon
  libavutil      59. 39.100 / 59. 39.100
  libavcodec     61. 19.100 / 61. 19.100
  libavformat    61.  7.100 / 61.  7.100
  libavdevice    61.  3.100 / 61.  3.100
  libavfilter    10.  4.100 / 10.  4.100
  libswscale      8.  3.100 /  8.  3.100
  libswresample   5.  3.100 /  5.  3.100
  libpostproc    58.  3.100 / 58.  3.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '/temp/테스트_동영상_202410192131057090.mov':
  Metadata:
    major_brand     : qt
    minor_version   : 0
    compatible_brands: qt
    creation_time   : 2024-10-17T03:20:12.000000Z
    com.apple.quicktime.make: Apple
    com.apple.quicktime.model: MacBookAir10,1
    com.apple.quicktime.software: macOS 14.6.1 (23G93)
    com.apple.quicktime.creationdate: 2024-10-17T12:20:10+0900
  Duration: 00:00:28.32, start: 0.000000, bitrate: 48577 kb/s
  Stream #0:0[0x1](und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p(tv, bt709, progressive), 2880x1800, 47025 kb/s, 60 fps, 60 tbr, 6k tbn (default)
      Metadata:
        creation_time   : 2024-10-17T03:20:12.000000Z
        handler_name    : Core Media Video
        vendor_id       : [0][0][0][0]
        encoder         : H.264
  Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 248 kb/s (default)
      Metadata:
        creation_time   : 2024-10-17T03:20:12.000000Z
        handler_name    : Core Media Audio
        vendor_id       : [0][0][0][0]
Stream mapping:
  Stream #0:0 -> #0:0 (h264 (native) -> h264 (libx264))
  Stream #0:1 -> #0:1 (aac (native) -> aac (native))
Press [q] to stop, [?] for help
[libx264 @ 0x14070fba0] using cpu capabilities: ARMv8 NEON
[libx264 @ 0x14070fba0] profile High, level 5.2, 4:2:0, 8-bit
[libx264 @ 0x14070fba0] 264 - core 164 r3108 31e19f9 - H.264/MPEG-4 AVC codec - Copyleft 2003-2023 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=12 lookahead_threads=2 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=250 keyint_min=25 scenecut=40 intra_refresh=0 rc_lookahead=40 rc=crf mbtree=1 crf=23.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00
Output #0, hls, to '/media/master.m3u8':
  Metadata:
    major_brand     : qt
    minor_version   : 0
    compatible_brands: qt
    com.apple.quicktime.creationdate: 2024-10-17T12:20:10+0900
    com.apple.quicktime.make: Apple
    com.apple.quicktime.model: MacBookAir10,1
    com.apple.quicktime.software: macOS 14.6.1 (23G93)
    encoder         : Lavf61.7.100
  Stream #0:0(und): Video: h264, yuv420p(tv, bt709, progressive), 2880x1800, q=2-31, 60 fps, 90k tbn (default)
      Metadata:
        creation_time   : 2024-10-17T03:20:12.000000Z
        handler_name    : Core Media Video
        vendor_id       : [0][0][0][0]
        encoder         : Lavc61.19.100 libx264
      Side data:
        cpb: bitrate max/min/avg: 0/0/0 buffer size: 0 vbv_delay: N/A
  Stream #0:1(und): Audio: aac (LC), 48000 Hz, stereo, fltp, 128 kb/s (default)
      Metadata:
        creation_time   : 2024-10-17T03:20:12.000000Z
        handler_name    : Core Media Audio
        vendor_id       : [0][0][0][0]
        encoder         : Lavc61.19.100 aac
[hls @ 0x14070f2f0] Opening '/media/master_00000000.ts' for writing
[hls @ 0x14070f2f0] Opening '/media/master.m3u8.tmp' for writing
[hls @ 0x14070f2f0] Opening '/media/master_00000001.ts' for writing
[hls @ 0x14070f2f0] Opening '/media/master.m3u8.tmp' for writing
[hls @ 0x14070f2f0] Opening '/media/master_00000002.ts' for writing
[hls @ 0x14070f2f0] Opening '/media/master.m3u8.tmp' for writing
[out#0/hls @ 0x600000620300] video:12403KiB audio:444KiB subtitle:0KiB other streams:0KiB global headers:0KiB muxing overhead: unknown
frame= 1699 fps= 51 q=-1.0 Lsize=N/A time=00:00:28.28 bitrate=N/A speed=0.842x
[libx264 @ 0x14070fba0] frame I:7     Avg QP:18.96  size:435551
[libx264 @ 0x14070fba0] frame P:434   Avg QP:22.52  size: 15884
[libx264 @ 0x14070fba0] frame B:1258  Avg QP:25.27  size:  2192
[libx264 @ 0x14070fba0] consecutive B-frames:  0.8%  1.3%  0.7% 97.2%
[libx264 @ 0x14070fba0] mb I  I16..4: 22.1% 44.5% 33.3%
[libx264 @ 0x14070fba0] mb P  I16..4:  0.2%  0.8%  0.3%  P16..4:  6.5%  1.9%  1.3%  0.0%  0.0%    skip:89.0%
[libx264 @ 0x14070fba0] mb B  I16..4:  0.0%  0.1%  0.0%  B16..8:  6.8%  0.4%  0.1%  direct: 0.2%  skip:92.5%  L0:53.0% L1:46.4% BI: 0.6%
[libx264 @ 0x14070fba0] 8x8 transform intra:52.9% inter:47.3%
[libx264 @ 0x14070fba0] coded y,uvDC,uvAC intra: 37.9% 30.2% 17.2% inter: 1.0% 0.5% 0.0%
[libx264 @ 0x14070fba0] i16 v,h,dc,p: 40% 48%  9%  3%
[libx264 @ 0x14070fba0] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 27% 14% 42%  2%  4%  3%  3%  2%  3%
[libx264 @ 0x14070fba0] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 29% 23% 23%  3%  5%  5%  5%  3%  4%
[libx264 @ 0x14070fba0] i8c dc,h,v,p: 65% 20% 12%  2%
[libx264 @ 0x14070fba0] Weighted P-Frames: Y:0.0% UV:0.0%
[libx264 @ 0x14070fba0] ref P L0: 66.1%  8.7% 16.5%  8.7%
[libx264 @ 0x14070fba0] ref B L0: 90.1%  8.8%  1.2%
[libx264 @ 0x14070fba0] ref B L1: 95.5%  4.5%
[libx264 @ 0x14070fba0] kb/s:3587.99
[aac @ 0x140757250] Qavg: 513.980

참고 자료

댓글