// File: kallaballa[P]PixelStencil/./CImg.h
// Function: cimg_library::CImgList<unsigned char>::load_ffmpeg(char const*, unsigned int, unsigned int, unsigned int, bool, bool)
CImgList<T>& load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
                             const unsigned int step_frame=1, const bool pixel_format=true, const bool resume=false) {
      if (!filename)
        throw CImgArgumentException(_cimglist_instance
                                    "load_ffmpeg(): Specified filename is (null).",
                                    cimglist_instance);

      const unsigned int
        nfirst_frame = first_frame<last_frame?first_frame:last_frame,
        nlast_frame = first_frame<last_frame?last_frame:first_frame,
        nstep_frame = step_frame?step_frame:1;
      assign();

#ifndef cimg_use_ffmpeg
      if ((nfirst_frame || nlast_frame!=~0U || nstep_frame>1) || (resume && (pixel_format || !pixel_format)))
        throw CImgArgumentException(_cimglist_instance
                                    "load_ffmpeg(): Unable to load sub-frames from file '%s' unless libffmpeg is enabled.",
                                    cimglist_instance,
                                    filename);

      return load_ffmpeg_external(filename);
#else
      const PixelFormat ffmpeg_pixfmt = pixel_format?PIX_FMT_RGB24:PIX_FMT_GRAY8;
      avcodec_register_all();
      av_register_all();
      static AVFormatContext *format_ctx = 0;
      static AVCodecContext *codec_ctx = 0;
      static AVCodec *codec = 0;
      static AVFrame *avframe = avcodec_alloc_frame(), *converted_frame = avcodec_alloc_frame();
      static int vstream = 0;

      if (resume) {
        if (!format_ctx || !codec_ctx || !codec || !avframe || !converted_frame)
          throw CImgArgumentException(_cimglist_instance
                                      "load_ffmpeg(): Failed to resume loading of file '%s', due to unallocated FFMPEG structures.",
                                      cimglist_instance,
                                      filename);
      } else {
        // Open video file, find main video stream and codec.
        if (format_ctx) avformat_close_input(&format_ctx);
        if (avformat_open_input(&format_ctx,filename,0,0)!=0)
          throw CImgIOException(_cimglist_instance
                                "load_ffmpeg(): Failed to open file '%s'.",
                                cimglist_instance,
                                filename);

        if (!avframe || !converted_frame || avformat_find_stream_info(format_ctx,NULL)<0) {
          avformat_close_input(&format_ctx); format_ctx = 0;
          return load_ffmpeg_external(filename);
        }
#if cimg_verbosity>=3
        dump_format(format_ctx,0,0,0);
#endif

        // Special command: Return informations on main video stream.
        // as a vector 1x4 containing: (nb_frames,width,height,fps).
        if (!first_frame && !last_frame && !step_frame) {
          for (vstream = 0; vstream<(int)(format_ctx->nb_streams); ++vstream)
            if (format_ctx->streams[vstream]->codec->codec_type==AVMEDIA_TYPE_VIDEO) break;
          if (vstream==(int)format_ctx->nb_streams) assign();
          else {
            CImgList<doubleT> timestamps;
            int nb_frames;
            AVPacket packet;
            // Count frames and store timestamps.
            for (nb_frames = 0; av_read_frame(format_ctx,&packet)>=0; av_free_packet(&packet))
              if (packet.stream_index==vstream) {
                CImg<doubleT>::vector((double)packet.pts).move_to(timestamps);
                ++nb_frames;
              }
            // Get frame with, height and fps.
            const int
              framew = format_ctx->streams[vstream]->codec->width,
              frameh = format_ctx->streams[vstream]->codec->height;
            const float
              num = (float)(format_ctx->streams[vstream]->r_frame_rate).num,
              den = (float)(format_ctx->streams[vstream]->r_frame_rate).den,
              fps = num/den;
            // Return infos as a list.
            assign(2);
            (*this)[0].assign(1,4).fill((T)nb_frames,(T)framew,(T)frameh,(T)fps);
            (*this)[1] = (timestamps>'y');
          }
          avformat_close_input(&format_ctx); format_ctx = 0;
          return *this;
        }

        for (vstream = 0; vstream<(int)(format_ctx->nb_streams) &&
               format_ctx->streams[vstream]->codec->codec_type!=AVMEDIA_TYPE_VIDEO; ) ++vstream;
        if (vstream==(int)format_ctx->nb_streams) {
          avformat_close_input(&format_ctx); format_ctx = 0;
          return load_ffmpeg_external(filename);
        }
        codec_ctx = format_ctx->streams[vstream]->codec;
        codec = avcodec_find_decoder(codec_ctx->codec_id);
        if (!codec) {
          return load_ffmpeg_external(filename);
        }
        if (avcodec_open2(codec_ctx,codec,NULL)<0) { // Open codec
          return load_ffmpeg_external(filename);
        }
      }

      // Read video frames
      const unsigned int numBytes = avpicture_get_size(ffmpeg_pixfmt,codec_ctx->width,codec_ctx->height);
      uint8_t *const buffer = new uint8_t[numBytes];
      avpicture_fill((AVPicture *)converted_frame,buffer,ffmpeg_pixfmt,codec_ctx->width,codec_ctx->height);
      const T foo = (T)0;
      AVPacket packet;
      for (unsigned int frame = 0, next_frame = nfirst_frame; frame<=nlast_frame && av_read_frame(format_ctx,&packet)>=0; ) {
        if (packet.stream_index==(int)vstream) {
          int decoded = 0;
#if defined(AV_VERSION_INT)
#if LIBAVCODEC_VERSION_INT<AV_VERSION_INT(52,26,0)
          avcodec_decode_video(codec_ctx,avframe,&decoded,packet.data, packet.size);
#else
          avcodec_decode_video2(codec_ctx,avframe,&decoded,&packet);
#endif
#else
          avcodec_decode_video(codec_ctx,avframe,&decoded,packet.data, packet.size);
#endif
          if (decoded) {
            if (frame==next_frame) {
              SwsContext *c = sws_getContext(codec_ctx->width,codec_ctx->height,codec_ctx->pix_fmt,codec_ctx->width,
                                             codec_ctx->height,ffmpeg_pixfmt,1,0,0,0);
              sws_scale(c,avframe->data,avframe->linesize,0,codec_ctx->height,converted_frame->data,converted_frame->linesize);
              if (ffmpeg_pixfmt==PIX_FMT_RGB24) {
                CImg<ucharT> next_image(*converted_frame->data,3,codec_ctx->width,codec_ctx->height,1,true);
                next_image._get_permute_axes("yzcx",foo).move_to(*this);
              } else {
                CImg<ucharT> next_image(*converted_frame->data,1,codec_ctx->width,codec_ctx->height,1,true);
                next_image._get_permute_axes("yzcx",foo).move_to(*this);
              }
              next_frame+=nstep_frame;
            }
            ++frame;
          }
          av_free_packet(&packet);
          if (next_frame>nlast_frame) break;
        }
      }
      delete[] buffer;
#endif
      return *this;
    }

// Assembly:
pushq %rbp
movq %rsp, %rbp
subq $0xb0, %rsp
movl %r9d, %eax
movl %ecx, -0x50(%rbp)
movl %edx, %ecx
movl -0x50(%rbp), %edx
movl %ecx, -0x4c(%rbp)
movq %rsi, %rcx
movl -0x4c(%rbp), %esi
movq %rcx, -0x48(%rbp)
movq %rdi, %r9
movq -0x48(%rbp), %rdi
movb %al, %cl
movb 0x10(%rbp), %al
movq %r9, -0x8(%rbp)
movq %rdi, -0x10(%rbp)
movl %esi, -0x14(%rbp)
movl %edx, -0x18(%rbp)
movl %r8d, -0x1c(%rbp)
andb $0x1, %cl
movb %cl, -0x1d(%rbp)
andb $0x1, %al
movb %al, -0x1e(%rbp)
movq -0x8(%rbp), %rax
movq %rax, -0x40(%rbp)
cmpq $0x0, -0x10(%rbp)
jne 0x3b8f2
movl $0x4008, %edi           # imm = 0x4008
callq 0x4190
movq %rax, %rcx
movq -0x40(%rbp), %rax
movq %rcx, -0x68(%rbp)
movq %rcx, -0x70(%rbp)
movl (%rax), %ecx
movl %ecx, -0x60(%rbp)
movl 0x4(%rax), %ecx
movl %ecx, -0x5c(%rbp)
movq 0x8(%rax), %rax
movq %rax, -0x58(%rbp)
callq 0x276d0
movq -0x68(%rbp), %rdi
movl -0x60(%rbp), %edx
movl -0x5c(%rbp), %ecx
movq -0x58(%rbp), %r8
movq %rax, %r9
leaq 0x9481(%rip), %rsi      # 0x44d39
xorl %eax, %eax
callq 0x8cd0
jmp 0x3b8c1
movq -0x70(%rbp), %rdi
leaq 0x1431c(%rip), %rsi     # 0x4fbe8
leaq -0x32a93(%rip), %rdx    # 0x8e40
callq 0x4600
movq -0x70(%rbp), %rdi
movq %rax, %rcx
movl %edx, %eax
movq %rcx, -0x28(%rbp)
movl %eax, -0x2c(%rbp)
callq 0x4270
jmp 0x3ba3e
movl -0x14(%rbp), %eax
cmpl -0x18(%rbp), %eax
jae 0x3b902
movl -0x14(%rbp), %eax
movl %eax, -0x74(%rbp)
jmp 0x3b908
movl -0x18(%rbp), %eax
movl %eax, -0x74(%rbp)
movl -0x74(%rbp), %eax
movl %eax, -0x30(%rbp)
movl -0x14(%rbp), %eax
cmpl -0x18(%rbp), %eax
jae 0x3b91e
movl -0x18(%rbp), %eax
movl %eax, -0x78(%rbp)
jmp 0x3b924
movl -0x14(%rbp), %eax
movl %eax, -0x78(%rbp)
movl -0x78(%rbp), %eax
movl %eax, -0x34(%rbp)
cmpl $0x0, -0x1c(%rbp)
je 0x3b938
movl -0x1c(%rbp), %eax
movl %eax, -0x7c(%rbp)
jmp 0x3b942
movl $0x1, %eax
movl %eax, -0x7c(%rbp)
jmp 0x3b942
movq -0x40(%rbp), %rdi
movl -0x7c(%rbp), %eax
movl %eax, -0x38(%rbp)
callq 0x27cc0
cmpl $0x0, -0x30(%rbp)
jne 0x3b97d
cmpl $-0x1, -0x34(%rbp)
jne 0x3b97d
cmpl $0x1, -0x38(%rbp)
ja 0x3b97d
testb $0x1, -0x1e(%rbp)
je 0x3ba28
testb $0x1, -0x1d(%rbp)
jne 0x3b97d
testb $0x1, -0x1d(%rbp)
jne 0x3ba28
movl $0x4008, %edi           # imm = 0x4008
callq 0x4190
movq %rax, %rcx
movq -0x40(%rbp), %rax
movq %rcx, -0x98(%rbp)
movq %rcx, -0xa0(%rbp)
movl (%rax), %ecx
movl %ecx, -0x90(%rbp)
movl 0x4(%rax), %ecx
movl %ecx, -0x8c(%rbp)
movq 0x8(%rax), %rax
movq %rax, -0x88(%rbp)
callq 0x276d0
movq -0x98(%rbp), %rdi
movl -0x90(%rbp), %edx
movl -0x8c(%rbp), %ecx
movq -0x88(%rbp), %r8
movq %rax, %r9
movq -0x10(%rbp), %rsi
movq %rsp, %rax
movq %rsi, (%rax)
leaq 0x939e(%rip), %rsi      # 0x44d89
xorl %eax, %eax
callq 0x8cd0
jmp 0x3b9f4
movq -0xa0(%rbp), %rdi
leaq 0x141e6(%rip), %rsi     # 0x4fbe8
leaq -0x32bc9(%rip), %rdx    # 0x8e40
callq 0x4600
movq -0xa0(%rbp), %rdi
movq %rax, %rcx
movl %edx, %eax
movq %rcx, -0x28(%rbp)
movl %eax, -0x2c(%rbp)
callq 0x4270
jmp 0x3ba3e
movq -0x40(%rbp), %rdi
movq -0x10(%rbp), %rsi
callq 0x3ba50
addq $0xb0, %rsp
popq %rbp
retq
movq -0x28(%rbp), %rdi
callq 0x4620
nopw (%rax,%rax)