/*****************************************************************
 * gmerlin-encoders - encoder plugins for gmerlin
 *
 * Copyright (c) 2001 - 2024 Members of the Gmerlin project
 * http://github.com/bplaum
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * *****************************************************************/



#include <config.h>

#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <gmerlin/plugin.h>
#include <gmerlin/pluginfuncs.h>

#if LIBAVCODEC_VERSION_MAJOR >= 61
#include <libavcodec/codec.h>
#endif


#ifdef HAVE_LIBAVCORE_AVCORE_H
#include <libavcore/avcore.h>
#endif

#if LIBAVCODEC_VERSION_MAJOR >= 53
#define guess_format(a, b, c) av_guess_format(a, b, c)
#endif


#define FLAG_CONSTANT_FRAMERATE (1<<0)
#define FLAG_INTRA_ONLY         (1<<1)
#define FLAG_B_FRAMES           (1<<2)
#define FLAG_PIPE               (1<<3) // Format can be written savely to pipes
#define FLAG_EXTRADATA          (1<<4) // Encoder has extradata

typedef struct
  {
  const char * name;
  const char * long_name;
  enum AVCodecID id;
  const bg_parameter_info_t * parameters;

  int flags;
  const bg_encoder_framerate_t * framerates;
  } ffmpeg_codec_info_t;

typedef struct
  {
  char * label;
  const char * name;
  const char * ffmpeg_name;

  const char * extension;
  
  int max_audio_streams;
  int max_video_streams;
  
  const enum AVCodecID * audio_codecs;
  const enum AVCodecID * video_codecs;
  
  int flags;
  
  } ffmpeg_format_info_t;

/* codecs.c */

const  AVOutputFormat * bg_ffmpeg_guess_format(const ffmpeg_format_info_t * format);

bg_parameter_info_t *
bg_ffmpeg_create_audio_parameters(const ffmpeg_format_info_t * format_info);

bg_parameter_info_t *
bg_ffmpeg_create_video_parameters(const ffmpeg_format_info_t * format_info);

bg_parameter_info_t *
bg_ffmpeg_create_parameters(const ffmpeg_format_info_t * format_info);

void
bg_ffmpeg_set_codec_parameter(AVCodecContext * ctx,
                              AVDictionary ** options,
                              const char * name,
                              const gavl_value_t * val);

enum AVCodecID
bg_ffmpeg_find_audio_encoder(const ffmpeg_format_info_t * format,
                             const char * name);

enum AVCodecID
bg_ffmpeg_find_video_encoder(const ffmpeg_format_info_t * format,
                             const char * name);

const char *
bg_ffmpeg_get_codec_name(enum AVCodecID id);

const bg_parameter_info_t *
bg_ffmpeg_get_codec_parameters(enum AVCodecID id, int type);

const ffmpeg_codec_info_t *
bg_ffmpeg_get_codec_info(enum AVCodecID id, int type);


/*
 *  Standalone codecs
 */

/*
 *  Create a codec context.
 *  If avctx is NULL, it will be created and destroyed.
 *  If id is CODEC_ID_NONE, a "codec" parameter will be supported
 *
 *  Type is one of CODEC_TYPE_VIDEO or CODEC_TYPE_AUDIO
 */

typedef struct bg_ffmpeg_codec_context_s bg_ffmpeg_codec_context_t;

struct bg_ffmpeg_codec_context_s
  {
  const AVCodec * codec;
  
  AVCodecContext * avctx;
  
  gavl_packet_sink_t * psink;
  gavl_audio_sink_t  * asink;
  gavl_video_sink_t  * vsink;
  AVDictionary * options;

  gavl_packet_t gp;
  AVPacket * pkt;
  
  int type;
  
  /* Multipass stuff */

  char * stats_filename;
  int pass;
  int total_passes;
  FILE * stats_file;
  
  /* Only non-null within the format writer */
  const ffmpeg_format_info_t * format;

  enum AVCodecID id;

  int flags;
  
  gavl_audio_format_t afmt;
  gavl_video_format_t vfmt;

  /*
   * ffmpeg frame (same for audio and video)
   */

  AVFrame * frame;

  /* Audio frame to encode */
  gavl_audio_frame_t * aframe;
  int block_align;
  
  /*
   * Video frame to encode.
   * Used only when we need to convert formats.
   */
  
  gavl_video_frame_t * vframe;
  
  int64_t in_pts;
  int64_t out_pts;

  bg_encoder_framerate_t fr;
  
  bg_encoder_pts_cache_t * pc;

  /* Trivial pixelformat conversions because
     we are too lazy to support all variants in gavl */
  
  void (*convert_frame)(bg_ffmpeg_codec_context_t * ctx, gavl_video_frame_t * f);
  
  };


void bg_ffmpeg_set_video_dimensions_avctx(AVCodecContext * avctx,
                                          const gavl_video_format_t * fmt);

void bg_ffmpeg_set_video_dimensions_params(AVCodecParameters * avctx,
                                             const gavl_video_format_t * fmt);

int bg_ffmpeg_set_audio_format_avctx(AVCodecContext * avctx,
                                     const AVCodec * codec,
                                     gavl_audio_format_t * fmt);

void bg_ffmpeg_set_audio_format_params(AVCodecParameters * avctx,
                                       gavl_audio_format_t * fmt);

bg_ffmpeg_codec_context_t * bg_ffmpeg_codec_create(int type,
                                                   AVCodecParameters * avctx,
                                                   enum AVCodecID id,
                                                   const ffmpeg_format_info_t * format);

const bg_parameter_info_t * bg_ffmpeg_codec_get_parameters(bg_ffmpeg_codec_context_t * ctx);


void bg_ffmpeg_codec_set_parameter(bg_ffmpeg_codec_context_t * ctx,
                                   const char * name,
                                   const gavl_value_t * val);

int bg_ffmpeg_codec_set_video_pass(bg_ffmpeg_codec_context_t * ctx,
                                   int pass,
                                   int total_passes,
                                   const char * stats_filename);


gavl_audio_sink_t * bg_ffmpeg_codec_open_audio(bg_ffmpeg_codec_context_t * ctx,
                                               gavl_dictionary_t * s);

gavl_video_sink_t * bg_ffmpeg_codec_open_video(bg_ffmpeg_codec_context_t * ctx,
                                               gavl_dictionary_t * s);

void bg_ffmpeg_codec_destroy(bg_ffmpeg_codec_context_t * ctx);

void bg_ffmpeg_codec_set_packet_sink(bg_ffmpeg_codec_context_t * ctx,
                                     gavl_packet_sink_t * psink);

void bg_ffmpeg_codec_flush(bg_ffmpeg_codec_context_t * ctx);

/* ffmpeg_common.c */

typedef struct ffmpeg_priv_s ffmpeg_priv_t;

#define STREAM_ENCODER_INITIALIZED (1<<0)
#define STREAM_IS_COMPRESSED       (1<<1)

typedef struct
  {
  AVStream * stream;
  AVPacket * pkt;
  
  bg_ffmpeg_codec_context_t * codec;
  
  int flags;
  
  gavl_packet_sink_t * psink;
  ffmpeg_priv_t * ffmpeg;
  gavl_compression_info_t ci;

  AVDictionary * options;
  enum AVCodecID codec_id; // Set after initializaiton
  
  gavl_dictionary_t s;
  gavl_dictionary_t * m;
  } bg_ffmpeg_stream_common_t;

typedef struct
  {
  bg_ffmpeg_stream_common_t com;
  gavl_audio_sink_t * sink;
  gavl_audio_format_t * format;

  } bg_ffmpeg_audio_stream_t;

typedef struct
  {
  bg_ffmpeg_stream_common_t com;
  gavl_video_sink_t * sink;
  gavl_video_format_t * format;
  int64_t dts;
  } bg_ffmpeg_video_stream_t;

typedef struct
  {
  bg_ffmpeg_stream_common_t com;
  AVRational time_base;
  AVPacket * pkt;
  } bg_ffmpeg_text_stream_t;

struct ffmpeg_priv_s
  {
  int num_audio_streams;
  int num_video_streams;
  int num_text_streams;
  
  bg_ffmpeg_audio_stream_t * audio_streams;
  bg_ffmpeg_video_stream_t * video_streams;
  bg_ffmpeg_text_stream_t * text_streams;
  
  AVFormatContext * ctx;
  
  bg_parameter_info_t * audio_parameters;
  bg_parameter_info_t * video_parameters;
  bg_parameter_info_t * parameters;
  
  const ffmpeg_format_info_t * format;

  int initialized;
  int got_error;

  // Needed when we write compressed video packets with B-frames
  int need_pts_offset;
  
  bg_encoder_callbacks_t * cb;
  
  gavl_io_t * io;
  unsigned char * io_buffer;
  };

extern const bg_encoder_framerate_t
bg_ffmpeg_mpeg_framerates[];

void * bg_ffmpeg_create(const ffmpeg_format_info_t * format);

void bg_ffmpeg_destroy(void*);

void bg_ffmpeg_set_callbacks(void * data,
                             bg_encoder_callbacks_t * cb);


const bg_parameter_info_t * bg_ffmpeg_get_parameters(void * data);

void bg_ffmpeg_set_parameter(void * data, const char * name,
                             const gavl_value_t * v);

int bg_ffmpeg_open(void * data, const char * filename,
                   const gavl_dictionary_t * metadata);

int bg_ffmpeg_open_io(void * data, gavl_io_t * io,
                      const gavl_dictionary_t * metadata);


const bg_parameter_info_t * bg_ffmpeg_get_audio_parameters(void * data);
const bg_parameter_info_t * bg_ffmpeg_get_video_parameters(void * data);

int bg_ffmpeg_add_audio_stream(void * data,
                               const gavl_dictionary_t * metadata,
                               const gavl_audio_format_t * format);

int bg_ffmpeg_add_video_stream(void * data,
                               const gavl_dictionary_t * metadata,
                               const gavl_video_format_t * format);

int bg_ffmpeg_add_text_stream(void * data,
                              const gavl_dictionary_t * metadata,
                              uint32_t * timescale);

void bg_ffmpeg_set_audio_parameter(void * data, int stream, const char * name,
                                   const gavl_value_t * v);

void bg_ffmpeg_set_video_parameter(void * data, int stream, const char * name,
                                  const gavl_value_t * v);


int bg_ffmpeg_set_video_pass(void * data, int stream, int pass,
                             int total_passes,
                             const char * stats_file);

int bg_ffmpeg_start(void * data);

void bg_ffmpeg_get_audio_format(void * data, int stream,
                                gavl_audio_format_t*ret);

gavl_audio_sink_t *
bg_ffmpeg_get_audio_sink(void * data, int stream);

gavl_video_sink_t *
bg_ffmpeg_get_video_sink(void * data, int stream);

gavl_packet_sink_t *
bg_ffmpeg_get_audio_packet_sink(void * data, int stream);

gavl_packet_sink_t *
bg_ffmpeg_get_video_packet_sink(void * data, int stream);

gavl_packet_sink_t *
bg_ffmpeg_get_text_packet_sink(void * data, int stream);


int bg_ffmpeg_write_subtitle_text(void * data,const char * text,
                                  int64_t start,
                                  int64_t duration, int stream);

int bg_ffmpeg_close(void * data, int do_delete);

#define CONVERT_ENDIAN (1<<8)
#define CONVERT_OTHER  (1<<9)

void bg_ffmpeg_choose_pixelformat(const enum AVPixelFormat * supported,
                                  enum AVPixelFormat * ffmpeg_fmt,
                                  gavl_pixelformat_t * gavl_fmt, int * do_convert);

gavl_sample_format_t bg_sample_format_ffmpeg_2_gavl(enum AVSampleFormat p,
                                                    gavl_interleave_mode_t * il);

enum AVCodecID bg_codec_id_gavl_2_ffmpeg(gavl_codec_id_t gavl);
gavl_codec_id_t bg_codec_id_ffmpeg_2_gavl(enum AVCodecID ffmpeg);

uint64_t
bg_ffmpeg_get_channel_mask(gavl_audio_format_t * format);


/* Compressed stream support */

int bg_ffmpeg_writes_compressed_audio(void * priv,
                                      const gavl_audio_format_t * format,
                                      const gavl_compression_info_t * info);

int bg_ffmpeg_writes_compressed_video(void * priv,
                                      const gavl_video_format_t * format,
                                      const gavl_compression_info_t * info);

int bg_ffmpeg_add_audio_stream_compressed(void * priv,
                                          const gavl_dictionary_t * metadata,
                                          const gavl_audio_format_t * format,
                                          const gavl_compression_info_t * info);

int bg_ffmpeg_add_video_stream_compressed(void * priv,
                                          const gavl_dictionary_t * metadata,
                                          const gavl_video_format_t * format,
                                          const gavl_compression_info_t * info);
