bytedeco / javacv

Java interface to OpenCV, FFmpeg, and more
Other
7.42k stars 1.57k forks source link

GIF File to GIF File #2057

Closed LemuriaX closed 11 months ago

LemuriaX commented 1 year ago

Hello, I want to use FFmpeg to split a GIF into frames and splicing it into GIF again (although it sounds pointless, but it is important to me), for this, I refer to these documents.

https://github.com/yokra9/JavaCV-Movie2Gif-example/blob/main/src/Movie2Gif.java

https://github.com/bytedeco/javacv/issues/1137

https://github.com/bytedeco/javacv/issues/2045

I read a lot of issues about this project, but my problem is still not solved, so I raised this issue, I hope you can answer it for me.

I have the following code

public class TestA {
    @Test
    public void test() throws FFmpegFrameGrabber.Exception, FFmpegFrameFilter.Exception, FFmpegFrameRecorder.Exception {
        String input = "C:\\Users\\Yuki\\Desktop\\in.gif";
        String output = "C:\\Users\\Yuki\\Desktop\\out.gif";
        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(input);
        grabber.setImageMode(FrameGrabber.ImageMode.RAW);
        grabber.start();
        FFmpegFrameFilter filter = new FFmpegFrameFilter("split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse", grabber.getImageWidth(), grabber.getImageHeight());
        FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(output, grabber.getImageWidth(), grabber.getImageHeight());
        filter.setPixelFormat(avutil.AV_PIX_FMT_PAL8);
        filter.setImageHeight(grabber.getImageHeight());
        filter.setImageWidth(grabber.getImageWidth());
        filter.setFrameRate(grabber.getFrameRate());
        filter.start();

        // specify supported pixel format.
        recorder.setPixelFormat(avutil.AV_PIX_FMT_PAL8);
        recorder.setVideoCodec(avcodec.AV_CODEC_ID_GIF);
        recorder.setImageWidth(grabber.getImageWidth());
        recorder.setImageHeight(grabber.getImageHeight());
        recorder.setFrameRate(grabber.getFrameRate());
        recorder.start();

        Frame frame, filteredFrame;
        while (Objects.nonNull((frame = grabber.grabFrame(false, true, true, false)))) {

            if (Objects.nonNull(frame.image) || Objects.nonNull(frame.samples)) {
                filter.push(frame);
            }

            if (Objects.nonNull((filteredFrame = filter.pull()))) {
                if (Objects.nonNull(filteredFrame.image) || Objects.nonNull(filteredFrame.samples)) {
                    recorder.record(filteredFrame, avutil.AV_PIX_FMT_PAL8);
                }
            }

        }
        grabber.stop();
        grabber.release();
        filter.stop();
        filter.release();
        recorder.stop();
        recorder.release();
    }

}

In this code, after filter.push(frame), the return value of filter.pull() is null.

Here is the output of my program。

Input #0, gif, from 'C:\Users\Yuki\Desktop\in.gif':
  Metadata:
    comment         : Made with ScreenToGif
  Duration: 00:00:12.72, start: 0.000000, bitrate: 818 kb/s
  Stream #0:0: Video: gif, bgra, 879x355, 6.92 fps, 14.42 tbr, 100 tbn
Output #0, gif, to 'C:\Users\Yuki\Desktop\out.gif':
  Metadata:
    encoder         : Lavf60.3.100
  Stream #0:0: Video: gif, pal8, 880x355, q=2-31, 400 kb/s, 6.92 fps, 100 tbn
[in @ 000000002fe2b000] filter context - w: 879 h: 355 fmt: 11, incoming frame - w: 879 h: 355 fmt: 28 pts_time: 0
[in @ 000000002fe2b000] Changing video frame properties on the fly is not supported by all filters.
[in @ 000000002fe2b000] filter context - w: 879 h: 355 fmt: 11, incoming frame - w: 879 h: 355 fmt: 28 pts_time: 0.144578
[in @ 000000002fe2b000] Changing video frame properties on the fly is not supported by all filters.
[in @ 000000002fe2b000] filter context - w: 879 h: 355 fmt: 11, incoming frame - w: 879 h: 355 fmt: 28 pts_time: 0.289157
[in @ 000000002fe2b000] Changing video frame properties on the fly is not supported by all filters.
[in @ 000000002fe2b000] filter context - w: 879 h: 355 fmt: 11, incoming frame - w: 879 h: 355 fmt: 28 pts_time: 1.59036
[in @ 000000002fe2b000] Changing video frame properties on the fly is not supported by all filters.
[in @ 000000002fe2b000] filter context - w: 879 h: 355 fmt: 11, incoming frame - w: 879 h: 355 fmt: 28 pts_time: 1.59036
[in @ 000000002fe2b000] Changing video frame properties on the fly is not supported by all filters.
[in @ 000000002fe2b000] filter context - w: 879 h: 355 fmt: 11, incoming frame - w: 879 h: 355 fmt: 28 pts_time: 1.73494
[in @ 000000002fe2b000] Changing video frame properties on the fly is not supported by all filters.
[in @ 000000002fe2b000] filter context - w: 879 h: 355 fmt: 11, incoming frame - w: 879 h: 355 fmt: 28 pts_time: 1.73494
[in @ 000000002fe2b000] Changing video frame properties on the fly is not supported by all filters.
[in @ 000000002fe2b000] filter context - w: 879 h: 355 fmt: 11, incoming frame - w: 879 h: 355 fmt: 28 pts_time: 1.87952
[in @ 000000002fe2b000] Changing video frame properties on the fly is not supported by all filters.
[in @ 000000002fe2b000] filter context - w: 879 h: 355 fmt: 11, incoming frame - w: 879 h: 355 fmt: 28 pts_time: 1.87952
[in @ 000000002fe2b000] Changing video frame properties on the fly is not supported by all filters.
[in @ 000000002fe2b000] filter context - w: 879 h: 355 fmt: 11, incoming frame - w: 879 h: 355 fmt: 28 pts_time: 2.0241
[in @ 000000002fe2b000] Changing video frame properties on the fly is not supported by all filters.
[in @ 000000002fe2b000] filter context - w: 879 h: 355 fmt: 11, incoming frame - w: 879 h: 355 fmt: 28 pts_time: 2.0241
[in @ 000000002fe2b000] Changing video frame properties on the fly is not supported by all filters.
[in @ 000000002fe2b000] filter context - w: 879 h: 355 fmt: 11, incoming frame - w: 879 h: 355 fmt: 28 pts_time: 2.16867
[in @ 000000002fe2b000] Changing video frame properties on the fly is not supported by all filters.
[in @ 000000002fe2b000] filter context - w: 879 h: 355 fmt: 11, incoming frame - w: 879 h: 355 fmt: 28 pts_time: 2.16867
[in @ 000000002fe2b000] Changing video frame properties on the fly is not supported by all filters.
[in @ 000000002fe2b000] filter context - w: 879 h: 355 fmt: 11, incoming frame - w: 879 h: 355 fmt: 28 pts_time: 2.31325
[in @ 000000002fe2b000] Changing video frame properties on the fly is not supported by all filters.
[in @ 000000002fe2b000] filter context - w: 879 h: 355 fmt: 11, incoming frame - w: 879 h: 355 fmt: 28 pts_time: 2.31325
[in @ 000000002fe2b000] Changing video frame properties on the fly is not supported by all filters.
[in @ 000000002fe2b000] filter context - w: 879 h: 355 fmt: 11, incoming frame - w: 879 h: 355 fmt: 28 pts_time: 2.45783
[in @ 000000002fe2b000] Changing video frame properties on the fly is not supported by all filters.
[in @ 000000002fe2b000] filter context - w: 879 h: 355 fmt: 11, incoming frame - w: 879 h: 355 fmt: 28 pts_time: 2.45783

If I set the pixel encoding to AV_PIX_FMT_BGR8, it works fine, but the output image quality is poor. I've been stuck with this problem for a week, don't know if I missed something, hope you can help me to see what's wrong in my code, thank you very much.

LemuriaX commented 1 year ago

This is the gif I used for testing https://github.com/LemuriaX/img/blob/main/in.gif

LemuriaX commented 1 year ago

Hello, I have a new progress now. I set filter.push(frame, AV_PIX_FMT_PAL8) and now it shows no error. But pull() still returns null

LemuriaX commented 1 year ago

I read this issus https://github.com/bytedeco/javacv/issues/287

Called filter.push(null) before pull, and now it can read the frame, but there is still a problem with recorder.record(filteredFrame, avutil.AV_PIX_FMT_PAL8), I opened the debug, FFmpegLogCallback.set(), it prompts Me, pal8 is not supported as output pixel format.

sws_getCachedContext() error: Cannot initialize the conversion context.

saudet commented 1 year ago

We can easily accomplish this with the ffmpeg program: http://bytedeco.org/javacpp-presets/ffmpeg/apidocs/org/bytedeco/ffmpeg/ffmpeg.html

LemuriaX commented 1 year ago

我们可以使用 ffmpeg 程序轻松完成此操作: http://bytedeco.org/javacpp-presets/ffmpeg/apidocs/org/bytedeco/ffmpeg/ffmpeg.html

thank you for your reply. Yes, I did the process using ProcessBuilder. Besides, is there anything wrong with my code above? I don't expect to use ProcessBuilder directly, I would rather use FFmpegFrameRecorder and FFmpegFrameFilter.

LemuriaX commented 1 year ago

I found the problem. The resolution of the gif I used for testing will change, causing video_c.width() to return a different value from the initial value. How should I deal with this situation? I noticed the description of video_c.width(), can I set this value by myself? Or can I fix the resolution to a value?

picture width / height. \note Those fields may not match the values of the last AVFrame output by avcodec_receive_frame() due frame reordering. - encoding: MUST be set by user. - decoding: May be set by the user before opening the decoder if known e.g. from the container. Some decoders will require the dimensions to be set by the caller. During decoding, the decoder may overwrite those values as required while parsing the data.

saudet commented 11 months ago

JavaCV probably needs to be enhanced to support all that you need here...