/* encsvtav1.c

   Copyright (c) 2003-2026 HandBrake Team
   This file is part of the HandBrake source code
   partially based on FFmpeg libsvtav1.c
   Homepage: <http://handbrake.fr/>.
   It may be used under the terms of the GNU General Public License v2.
   For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
 */

#include "handbrake/handbrake.h"
#include "handbrake/hb_dict.h"
#include "handbrake/av1_common.h"
#include "handbrake/hdr10plus.h"
#include "handbrake/dovi_common.h"
#include "handbrake/extradata.h"

#include "libavutil/avutil.h"
#include "svt-av1/EbSvtAv1ErrorCodes.h"
#include "svt-av1/EbSvtAv1Enc.h"
#include "svt-av1/EbSvtAv1Metadata.h"

int  encsvtInit(hb_work_object_t *, hb_job_t *);
int  encsvtWork(hb_work_object_t *, hb_buffer_t **, hb_buffer_t **);
void encsvtClose(hb_work_object_t *);

#define FRAME_INFO_SIZE 2048
#define FRAME_INFO_MASK (FRAME_INFO_SIZE - 1)

#define MAX_QP_VALUE 63

hb_work_object_t hb_encsvtav1 =
{
    WORK_ENCSVTAV1,
    "AV1 encoder (SVT)",
    encsvtInit,
    encsvtWork,
    encsvtClose
};

struct hb_work_private_s
{
    hb_job_t           *job;
    hb_chapter_queue_t *chapter_queue;

    EbSvtAv1EncConfiguration    enc_params;
    EbComponentType            *svt_handle;
    EbBufferHeaderType         *in_buf;

    struct {
        int64_t duration;
    } frame_info[FRAME_INFO_SIZE];

    int frameno_in;
    int frameno_out;
};

static void save_frame_duration(hb_work_private_t *pv, hb_buffer_t *in)
{
    int i = pv->frameno_in & FRAME_INFO_MASK;
    pv->frameno_in++;
    pv->frame_info[i].duration = in->s.stop - in->s.start;
}

static int64_t get_frame_duration(hb_work_private_t *pv)
{
    int i = pv->frameno_out & FRAME_INFO_MASK;
    pv->frameno_out++;
    return pv->frame_info[i].duration;
}

static int alloc_buffer(EbSvtAv1EncConfiguration *config, hb_work_private_t *pv)
{
    EbSvtIOFormat *in_data;

    // allocate buffer for in and out
    pv->in_buf = av_mallocz(sizeof(*pv->in_buf));
    if (pv->in_buf == NULL)
    {
        return 1;
    }

    pv->in_buf->p_buffer = av_mallocz(sizeof(*in_data));
    if (pv->in_buf->p_buffer == NULL)
    {
        return 1;
    }

    pv->in_buf->size = sizeof(*pv->in_buf);

    return 0;
}

int encsvtInit(hb_work_object_t *w, hb_job_t *job)
{
    hb_work_private_t *pv = calloc(1, sizeof(hb_work_private_t));
    w->private_data = pv;
    pv->job = job;
    pv->chapter_queue = hb_chapter_queue_init();

    EbErrorType svt_ret;
    int ret;

    svt_ret = svt_av1_enc_init_handle(&pv->svt_handle, &pv->enc_params);
    if (svt_ret != EB_ErrorNone)
    {
        hb_error("encsvtav1: error initializing encoder handle");
        return 1;
    }

    EbSvtAv1EncConfiguration *param = &pv->enc_params;

    if (job->encoder_preset == NULL)
    {
        param->enc_mode = 5;
    }
    else
    {
        param->enc_mode = atoi(job->encoder_preset);
    }

    if (job->vquality <= HB_INVALID_VIDEO_QUALITY)
    {
        param->target_bit_rate   = 1000 * job->vbitrate;
        param->rate_control_mode = SVT_AV1_RC_MODE_VBR;
    }
    else
    {
        param->qp                         = fmin(job->vquality, MAX_QP_VALUE); // truncated 
        param->extended_crf_qindex_offset = (job->vquality - param->qp) * 4;
        param->rate_control_mode = SVT_AV1_RC_MODE_CQP_OR_CRF;
        param->force_key_frames = 1;
    }

    param->color_primaries          = hb_output_color_prim(job);
    param->transfer_characteristics = hb_output_color_transfer(job);
    param->matrix_coefficients      = hb_output_color_matrix(job);

    switch (job->color_range)
    {
        case AVCOL_RANGE_MPEG:
            param->color_range = EB_CR_STUDIO_RANGE;
            break;
        case AVCOL_RANGE_JPEG:
            param->color_range = EB_CR_FULL_RANGE;
            break;
        default:
            break;
    }

    switch (job->chroma_location)
    {
        case AVCHROMA_LOC_LEFT:
            param->chroma_sample_position = EB_CSP_VERTICAL;
            break;
        case AVCHROMA_LOC_TOPLEFT:
            param->chroma_sample_position = EB_CSP_COLOCATED;
            break;
        default:
            break;
    }

    if (job->encoder_profile != NULL && *job->encoder_profile)
    {
        if (!strcasecmp(job->encoder_profile, "main"))
        {
            param->profile = MAIN_PROFILE;
        }
        else if (!strcasecmp(job->encoder_profile, "high"))
        {
            param->profile = HIGH_PROFILE;
        }
        else if (!strcasecmp(job->encoder_profile, "professional"))
        {
            param->profile = PROFESSIONAL_PROFILE;
        }
    }

    if (job->encoder_level != NULL && *job->encoder_level)
    {
        for (int i = 1; hb_av1_level_names[i]; i++)
        {
            if (!strcasecmp(job->encoder_level, hb_av1_level_names[i]))
            {
                param->level = hb_av1_level_values[i];
            }
        }
    }

    if (job->encoder_tune != NULL && strstr(job->encoder_tune, "ms-ssim") != NULL)
    {
        param->tune = 4;
    }
    else if (job->encoder_tune != NULL && strstr(job->encoder_tune, "iq") != NULL)
    {
        param->tune = 3;
    }
    else if (job->encoder_tune != NULL && strstr(job->encoder_tune, "ssim") != NULL)
    {
        param->tune = 2;
    }
    else if (job->encoder_tune != NULL && strstr(job->encoder_tune, "psnr") != NULL)
    {
        param->tune = 1;
    }
    else
    {
        param->tune = 0;
    }

    if (job->encoder_tune != NULL && strstr(job->encoder_tune, "fastdecode") != NULL)
    {
        param->fast_decode = 1;
    }
    else
    {
        param->fast_decode = 0;
    }

    // VFR isn't supported, the rate control will ignore
    // the frames timestamps and use the values below
    param->frame_rate_numerator = job->orig_vrate.num;
    param->frame_rate_denominator = job->orig_vrate.den;

    // HDR10 Static metadata
    if (job->color_transfer == HB_COLR_TRA_SMPTEST2084)
    {
        // Mastering display metadata
        if (job->mastering.has_primaries && job->mastering.has_luminance)
        {
            char mastering_display_color_volume[256];
            snprintf(mastering_display_color_volume, sizeof(mastering_display_color_volume),
                     "G(%5.4f,%5.4f)B(%5.4f,%5.4f)R(%5.4f,%5.4f)WP(%5.4f,%5.4f)L(%5.4f,%5.4f)",
                     hb_q2d(job->mastering.display_primaries[1][0]),
                     hb_q2d(job->mastering.display_primaries[1][1]),
                     hb_q2d(job->mastering.display_primaries[2][0]),
                     hb_q2d(job->mastering.display_primaries[2][1]),
                     hb_q2d(job->mastering.display_primaries[0][0]),
                     hb_q2d(job->mastering.display_primaries[0][1]),
                     hb_q2d(job->mastering.white_point[0]),
                     hb_q2d(job->mastering.white_point[1]),
                     hb_q2d(job->mastering.max_luminance),
                     hb_q2d(job->mastering.min_luminance));

            EbErrorType ret = svt_av1_enc_parse_parameter(param, "mastering-display", mastering_display_color_volume);
            if (ret != EB_ErrorNone)
            {
                hb_log("encsvtav1: error parsing option mastering-display: %s", mastering_display_color_volume);
            }
        }

        //  Content light level
        if (job->coll.max_cll && job->coll.max_fall)
        {
            char content_light_level[256];
            snprintf(content_light_level, sizeof(content_light_level),
                     "%u,%u", job->coll.max_cll, job->coll.max_fall);

            EbErrorType ret = svt_av1_enc_parse_parameter(param, "content-light", content_light_level);
            if (ret != EB_ErrorNone)
            {
                hb_log("encsvtav1: error parsing option content-light: %s", content_light_level);
            }
        }
    }

    hb_dict_t *encoder_options = NULL;
    if (job->encoder_options != NULL && *job->encoder_options)
    {
        encoder_options = hb_encopts_to_dict(job->encoder_options, job->vcodec);
    }
    // Iterate through encoder_options and parse the options
    hb_dict_iter_t iter;
    for (iter  = hb_dict_iter_init(encoder_options);
         iter != HB_DICT_ITER_DONE;
         iter  = hb_dict_iter_next(encoder_options, iter))
    {
        const char *key = hb_dict_iter_key(iter);
        hb_value_t *value = hb_dict_iter_value(iter);
        char *str = hb_value_get_string_xform(value);

        // Here's where the strings are passed to svt-av1 for parsing
        EbErrorType ret = svt_av1_enc_parse_parameter(param, key, str);
        if (ret != EB_ErrorNone)
        {
            hb_log("encsvtav1: error parsing option %s: %s", key, str);
        }
        free(str);
    }
    hb_dict_free(&encoder_options);

    // Reload colorimetry settings in case custom values were set
    // in the encoder_options string
    job->color_prim_override     = param->color_primaries;
    job->color_transfer_override = param->transfer_characteristics;
    job->color_matrix_override   = param->matrix_coefficients;

    param->source_width  = job->width;
    param->source_height = job->height;
    param->encoder_bit_depth = hb_get_bit_depth(job->output_pix_fmt);
    param->encoder_color_format = EB_YUV420;

    if (job->pass_id == HB_PASS_ENCODE_ANALYSIS ||
        job->pass_id == HB_PASS_ENCODE_FINAL)
    {
        hb_interjob_t *interjob = hb_interjob_get(job->h);
        param->rc_stats_buffer.buf = interjob->context;
        param->rc_stats_buffer.sz  = interjob->context_size;
        param->pass = job->pass_id == HB_PASS_ENCODE_ANALYSIS ? 1 : 2;
    }

    svt_ret = svt_av1_enc_set_parameter(pv->svt_handle, &pv->enc_params);
    if (svt_ret != EB_ErrorNone)
    {
        hb_error("encsvtav1: error setting encoder parameters");
        return 1;
    }

    svt_ret = svt_av1_enc_init(pv->svt_handle);
    if (svt_ret != EB_ErrorNone)
    {
        hb_error("encsvtav1: error initializing encoder");
        return 1;
    }

    EbBufferHeaderType *headerPtr = NULL;

    svt_ret = svt_av1_enc_stream_header(pv->svt_handle, &headerPtr);
    if (svt_ret != EB_ErrorNone)
    {
        hb_error("encsvtav1: error building stream header");
        return 1;
    }

    if (hb_set_extradata(w->extradata, headerPtr->p_buffer, headerPtr->n_filled_len))
    {
        hb_error("encsvtav1: error setting extradata");
        return 1;
    }

    // Update and set Dolby Vision level
    if (job->passthru_dynamic_hdr_metadata & HB_HDR_DYNAMIC_METADATA_DOVI)
    {
        int level_idx, high_tier;
        hb_parse_av1_extradata(*w->extradata, &level_idx, &high_tier);

        int pps = (double)job->width * job->height * (job->vrate.num / job->vrate.den);
        int bitrate = job->vquality == HB_INVALID_VIDEO_QUALITY ? job->vbitrate : -1;

        // Dolby Vision requires VBV settings to enable HRD
        // but SVT-AV1 supports max-bit-rate only in CFR mode
        // so that the Dolby Vision level to something comparable
        // to the current AV1 level
        if (param->max_bit_rate == 0)
        {
            int max_rate = hb_dovi_max_rate(job->vcodec, job->width, pps, bitrate,
                                            level_idx, high_tier);
            param->max_bit_rate = max_rate * 1000;
        }

        job->dovi.dv_level = hb_dovi_level(job->width, pps, param->max_bit_rate / 1000, high_tier);
    }

    svt_ret = svt_av1_enc_stream_header_release(headerPtr);
    if (svt_ret != EB_ErrorNone)
    {
        hb_error("encsvtav1: error freeing stream header");
        return 1;
    }

    ret = alloc_buffer(&pv->enc_params, pv);
    if (ret)
    {
        hb_error("encsvtav1: couldn't allocate buffer");
        return 1;
    }

    return 0;
}

void encsvtClose(hb_work_object_t *w)
{
    hb_work_private_t *pv = w->private_data;

    if (pv == NULL)
    {
        return;
    }

    hb_chapter_queue_close(&pv->chapter_queue);

    if (pv->svt_handle)
    {
        svt_av1_enc_deinit(pv->svt_handle);
        svt_av1_enc_deinit_handle(pv->svt_handle);
    }
    if (pv->in_buf)
    {
        if (pv->in_buf->metadata)
        {
            svt_metadata_array_free(&pv->in_buf->metadata);
        }
        av_free(pv->in_buf->p_buffer);
        av_freep(&pv->in_buf);
    }
    if (pv->job->pass_id == HB_PASS_ENCODE_FINAL || *pv->job->die)
    {
        hb_interjob_t *interjob = hb_interjob_get(pv->job->h);
        av_freep(&interjob->context);
    }

    free(pv);
    w->private_data = NULL;
}

static int read_in_data(EbSvtAv1EncConfiguration *param, const hb_buffer_t *in, EbBufferHeaderType *header_ptr)
{
    EbSvtIOFormat *in_data = (EbSvtIOFormat *)header_ptr->p_buffer;
    ptrdiff_t linesizes[4];
    size_t sizes[4];
    int bytes_shift = param->encoder_bit_depth > 8 ? 1 : 0;
    int ret, frame_size;

    for (int i = 0; i < 4; i++)
    {
        linesizes[i] = in->plane[i].stride;
    }

    ret = av_image_fill_plane_sizes(sizes, in->f.fmt, in->f.height, linesizes);
    if (ret < 0)
    {
        return ret;
    }

    frame_size = 0;
    for (int i = 0; i < 4; i++)
    {
        if (sizes[i] > INT_MAX - frame_size)
        {
            return 1;
        }
        frame_size += sizes[i];
    }

    in_data->luma = in->plane[0].data;
    in_data->cb   = in->plane[1].data;
    in_data->cr   = in->plane[2].data;

    in_data->y_stride  = AV_CEIL_RSHIFT(in->plane[0].stride, bytes_shift);
    in_data->cb_stride = AV_CEIL_RSHIFT(in->plane[1].stride, bytes_shift);
    in_data->cr_stride = AV_CEIL_RSHIFT(in->plane[2].stride, bytes_shift);

    header_ptr->n_filled_len = frame_size;

    return 0;
}

static int send(hb_work_object_t *w, hb_buffer_t *in)
{
    hb_work_private_t *pv = w->private_data;
    hb_job_t *job         = pv->job;

    EbBufferHeaderType *headerPtr = pv->in_buf;
    int ret;

    if (in->s.flags & HB_BUF_FLAG_EOF)
    {
        EbBufferHeaderType headerPtrLast;

        memset(&headerPtrLast, 0, sizeof(headerPtrLast));
        headerPtrLast.pic_type = EB_AV1_INVALID_PICTURE;
        headerPtrLast.flags    = EB_BUFFERFLAG_EOS;

        svt_av1_enc_send_picture(pv->svt_handle, &headerPtrLast);
        return 0;
    }

    ret = read_in_data(&pv->enc_params, in, headerPtr);
    if (ret < 0)
    {
        return ret;
    }

    headerPtr->flags         = 0;
    headerPtr->p_app_private = NULL;
    headerPtr->pts           = in->s.start;
    save_frame_duration(pv, in);

    if (headerPtr->metadata)
    {
        svt_metadata_array_free(&headerPtr->metadata);
    }

    if (job->passthru_dynamic_hdr_metadata)
    {
        for (int i = 0; i < in->nb_side_data; i++)
        {
            const AVFrameSideData *side_data = in->side_data[i];
            if (job->passthru_dynamic_hdr_metadata & HB_HDR_DYNAMIC_METADATA_HDR10PLUS &&
                side_data->type == AV_FRAME_DATA_DYNAMIC_HDR_PLUS)
            {
                uint8_t *payload = NULL;
                uint32_t playload_size = 0;

                hb_dynamic_hdr10_plus_to_itu_t_t35((AVDynamicHDRPlus *)side_data->data, &payload, &playload_size);
                if (!playload_size)
                {
                    continue;
                }

                svt_add_metadata(headerPtr, EB_AV1_METADATA_TYPE_ITUT_T35, payload, playload_size);
                av_freep(&payload);
            }
            else if (job->passthru_dynamic_hdr_metadata & HB_HDR_DYNAMIC_METADATA_DOVI &&
                     side_data->type == AV_FRAME_DATA_DOVI_RPU_BUFFER_T35)
            {
                svt_add_metadata(headerPtr, EB_AV1_METADATA_TYPE_ITUT_T35, side_data->data, side_data->size);
            }

        }
    }

    if (in->s.new_chap > 0 && pv->job->chapter_markers)
    {
        if (pv->enc_params.force_key_frames)
        {
            headerPtr->pic_type = EB_AV1_KEY_PICTURE;
        }
        hb_chapter_enqueue(pv->chapter_queue, in);
    }
    else
    {
        headerPtr->pic_type = EB_AV1_INVALID_PICTURE;
    }

    svt_av1_enc_send_picture(pv->svt_handle, headerPtr);

    return 0;
}

static int receive(hb_work_object_t *w, hb_buffer_t **out, int done)
{
    hb_work_private_t  *pv = w->private_data;
    EbBufferHeaderType *headerPtr;
    EbErrorType svt_ret;
    hb_buffer_t *buf;

    svt_ret = svt_av1_enc_get_packet(pv->svt_handle, &headerPtr, done);
    if (svt_ret == EB_NoErrorEmptyQueue)
    {
        *out = NULL;
        return 1;
    }

    if (headerPtr->flags & EB_BUFFERFLAG_EOS)
    {
        svt_av1_enc_release_out_buffer(&headerPtr);
        *out = NULL;
        return 2;
    }

    buf = hb_buffer_init(headerPtr->n_filled_len);
    if (buf == NULL)
    {
        hb_error("encsvtav1: failed to allocate output packet");
        svt_av1_enc_release_out_buffer(&headerPtr);
        *out = NULL;
        return 2;
    }

    memcpy(buf->data, headerPtr->p_buffer, headerPtr->n_filled_len);

    buf->size            = headerPtr->n_filled_len;
    buf->s.start         = headerPtr->pts;
    buf->s.duration      = get_frame_duration(pv);
    buf->s.stop          = buf->s.start + buf->s.duration;
    buf->s.renderOffset  = headerPtr->dts;

    // SVT-AV1 doesn't always respect forced keyframes,
    // so always check for chapters
    hb_chapter_dequeue(pv->chapter_queue, buf);

    switch (headerPtr->pic_type)
    {
        case EB_AV1_KEY_PICTURE:
            buf->s.flags |= HB_FLAG_FRAMETYPE_KEY;
            // fall-through
        case EB_AV1_INTRA_ONLY_PICTURE:
            buf->s.frametype = HB_FRAME_IDR;
            break;
        default:
            buf->s.frametype = HB_FRAME_P;
            break;
    }

    buf->s.flags |= HB_FLAG_FRAMETYPE_REF;

    svt_av1_enc_release_out_buffer(&headerPtr);

    *out = buf;
    return 0;
}

static void encode(hb_work_object_t *w, hb_buffer_t *in, hb_buffer_list_t *list)
{
    send(w, in);

    hb_buffer_t *out;
    while (receive(w, &out, 0) == 0)
    {
        hb_buffer_list_append(list, out);
    }
}

static void flush(hb_work_object_t *w, hb_buffer_t *in, hb_buffer_list_t *list)
{
    hb_work_private_t  *pv = w->private_data;
    hb_job_t *job = pv->job;
    hb_interjob_t *interjob = hb_interjob_get(job->h);

    send(w, in);

    hb_buffer_t *out = NULL;
    while (receive(w, &out, 1) == 0)
    {
        hb_buffer_list_append(list, out);
    }

    // Store the first pass stats for the next
    if (job->pass_id == HB_PASS_ENCODE_ANALYSIS && interjob->context == NULL)
    {
        SvtAv1FixedBuf first_pass_stat;
        EbErrorType ret = svt_av1_enc_get_stream_info(pv->svt_handle,
                                                      SVT_AV1_STREAM_INFO_FIRST_PASS_STATS_OUT,
                                                      &first_pass_stat);
        if (ret == EB_ErrorNone)
        {
            interjob->context = av_malloc(first_pass_stat.sz);
            if (interjob->context)
            {
                memcpy(interjob->context, first_pass_stat.buf, first_pass_stat.sz);
                interjob->context_size = first_pass_stat.sz;
            }
        }
    }
}

int encsvtWork(hb_work_object_t *w, hb_buffer_t **buf_in,
               hb_buffer_t **buf_out)
{
    hb_buffer_t      *in = *buf_in;
    hb_buffer_list_t  list;

    *buf_out = NULL;
    hb_buffer_list_clear(&list);

    if (in->s.flags & HB_BUF_FLAG_EOF)
    {
        // EOF on input. Flush any frames still in the decoder then
        // send the EOF downstream to tell the muxer we're done.
        flush(w, in, &list);
        hb_buffer_list_append(&list, hb_buffer_eof_init());

        *buf_out = hb_buffer_list_clear(&list);
        return HB_WORK_DONE;
    }

    // Not EOF - encode the packet
    encode(w, in, &list);
    *buf_out = hb_buffer_list_clear(&list);

    return HB_WORK_OK;
}
