/* FilePresto older-video engine.
 * A thin wrapper round FFmpeg's libavformat, libavcodec, libswscale and libswresample (GNU LGPL 2.1 or later),
 * built for the browser with Emscripten. It reads a file on the visitor's own device and hands back either
 * decoded pictures and sound, or unchanged packets, for the browser's own encoders to turn into an MP4.
 * It contains no video encoder. The only encoder is FFmpeg's own ALAC (Apple Lossless) encoder, for FLAC to ALAC.
 * This file is part of FilePresto's published build material and may be used under the same LGPL terms. */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <emscripten.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavcodec/bsf.h>
#include <libavutil/avutil.h>
#include <libavutil/display.h>
#include <libavutil/opt.h>
#include <libavutil/channel_layout.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>

EM_JS(void, fp_report_js, (double f), { if (Module.onProgress) Module.onProgress(f); });
/* Progress is reported at most every half per cent, so the page is not flooded with messages. */
static double last_report = -1;
static void fp_report(double f) { if (f >= 1.0 || f < last_report || f - last_report >= 0.005) { last_report = f; fp_report_js(f); } }

static char json[16384];
static AVFormatContext *fmt = NULL;
static AVCodecContext *vdec = NULL, *adec = NULL;
static AVBSFContext *absf = NULL;
static struct SwsContext *sws = NULL;
static SwrContext *swr = NULL;
static AVPacket *pkt = NULL;
static AVFrame *frm = NULL;
static int vi = -1, ai = -1, vmode = 0, amode = 0;
static int outw = 0, outh = 0, swsw = 0, swsh = 0, swsfmt = -1;
static int out_rate = 0, out_ch = 0;
static int pending = 0;          /* 1 = video decoder may hold frames, 2 = audio decoder, 3 = audio bsf */
static int eof = 0, vflushed = 0, aflushed = 0, bflushed = 0, read_error = 0, bad_packets = 0;
/* AVI files store no presentation times, only the order of frames, so with B-frames the decoder's own timing guess
 * runs one frame late. For AVI, each picture instead takes the earliest frame slot still waiting. */
static int is_avi = 0; static int64_t slots[64]; static int nslots = 0;
static void slot_push(int64_t t) { if (nslots == 64) { memmove(slots, slots + 1, 63 * sizeof(int64_t)); nslots--; } int i = nslots++; while (i > 0 && slots[i - 1] > t) { slots[i] = slots[i - 1]; i--; } slots[i] = t; }
static int64_t slot_pop(void) { int64_t t = slots[0]; memmove(slots, slots + 1, (--nslots) * sizeof(int64_t)); return t; }

/* The item handed back to JavaScript by fp_next. */
static uint8_t *item = NULL; static int item_cap = 0, item_len = 0;
static double item_pts = 0, item_dur = 0; static int item_key = 0, item_samples = 0;

static int ensure(int n) {
  if (n <= item_cap) return 0;
  uint8_t *p = realloc(item, n); if (!p) return AVERROR(ENOMEM);
  item = p; item_cap = n; return 0;
}

EMSCRIPTEN_KEEPALIVE uint8_t *fp_item_ptr(void) { return item; }
EMSCRIPTEN_KEEPALIVE int fp_item_len(void) { return item_len; }
EMSCRIPTEN_KEEPALIVE double fp_item_pts(void) { return item_pts; }
EMSCRIPTEN_KEEPALIVE double fp_item_dur(void) { return item_dur; }
EMSCRIPTEN_KEEPALIVE int fp_item_key(void) { return item_key; }
EMSCRIPTEN_KEEPALIVE int fp_item_samples(void) { return item_samples; }
EMSCRIPTEN_KEEPALIVE int fp_out_w(void) { return outw; }
EMSCRIPTEN_KEEPALIVE int fp_out_h(void) { return outh; }
EMSCRIPTEN_KEEPALIVE int fp_out_rate(void) { return out_rate; }
EMSCRIPTEN_KEEPALIVE int fp_out_ch(void) { return out_ch; }

static double to_us(int64_t t, AVRational tb) { return t == AV_NOPTS_VALUE ? -1 : (double)t * tb.num * 1e6 / tb.den; }

EMSCRIPTEN_KEEPALIVE void fp_close(void) {
  if (vdec) avcodec_free_context(&vdec);
  if (adec) avcodec_free_context(&adec);
  if (absf) av_bsf_free(&absf);
  if (sws) { sws_freeContext(sws); sws = NULL; }
  if (swr) swr_free(&swr);
  if (fmt) avformat_close_input(&fmt);
  if (pkt) av_packet_free(&pkt);
  if (frm) av_frame_free(&frm);
  vi = ai = -1; vmode = amode = 0; pending = 0; eof = vflushed = aflushed = bflushed = 0; read_error = 0; bad_packets = 0; swsfmt = -1; is_avi = 0; nslots = 0;
}

static void jesc(char *dst, size_t n, const char *s) {
  size_t j = 0; for (; s && *s && j + 2 < n; s++) { if (*s == '"' || *s == '\\') dst[j++] = '\\'; if ((unsigned char)*s >= 32) dst[j++] = *s; }
  dst[j] = 0;
}

EMSCRIPTEN_KEEPALIVE int fp_open(const char *path) {
  fp_close();
  av_log_set_level(AV_LOG_ERROR);
  int r = avformat_open_input(&fmt, path, NULL, NULL);
  if (r < 0) return r;
  fmt->probesize = 20 * 1024 * 1024; fmt->max_analyze_duration = 10 * AV_TIME_BASE;
  r = avformat_find_stream_info(fmt, NULL);
  if (r < 0) return r;
  return 0;
}

/* A description of the file as JSON: container, length, and each stream's codec and shape. */
EMSCRIPTEN_KEEPALIVE const char *fp_info(void) {
  if (!fmt) return "{}";
  char *p = json; size_t left = sizeof json; int n;
  n = snprintf(p, left, "{\"format\":\"%s\",\"duration\":%.3f,\"streams\":[", fmt->iformat->name,
               fmt->duration == AV_NOPTS_VALUE ? 0.0 : fmt->duration / 1e6); p += n; left -= n;
  for (unsigned i = 0; i < fmt->nb_streams && left > 600; i++) {
    AVStream *s = fmt->streams[i]; AVCodecParameters *c = s->codecpar;
    const char *type = c->codec_type == AVMEDIA_TYPE_VIDEO ? "video" : c->codec_type == AVMEDIA_TYPE_AUDIO ? "audio" :
                       c->codec_type == AVMEDIA_TYPE_SUBTITLE ? "subtitle" : "other";
    char tag[40] = ""; if (c->codec_tag) { char t[AV_FOURCC_MAX_STRING_SIZE]; av_fourcc_make_string(t, c->codec_tag); jesc(tag, sizeof tag, t); }
    double dur = s->duration == AV_NOPTS_VALUE ? -1 : to_us(s->duration, s->time_base) / 1e6;
    int attached = (s->disposition & AV_DISPOSITION_ATTACHED_PIC) ? 1 : 0;
    AVRational fr = s->avg_frame_rate.num ? s->avg_frame_rate : s->r_frame_rate;
    n = snprintf(p, left, "%s{\"i\":%u,\"type\":\"%s\",\"codec\":\"%s\",\"tag\":\"%s\",\"profile\":%d,\"w\":%d,\"h\":%d,"
                 "\"sarN\":%d,\"sarD\":%d,\"fpsN\":%d,\"fpsD\":%d,\"rate\":%d,\"ch\":%d,\"bits\":%d,\"extra\":%d,\"dur\":%.3f,\"pic\":%d,\"bitrate\":%lld,\"start\":%.6f}",
                 i ? "," : "", i, type, avcodec_get_name(c->codec_id), tag, c->profile, c->width, c->height,
                 c->sample_aspect_ratio.num, c->sample_aspect_ratio.den, fr.num, fr.den,
                 c->sample_rate, c->ch_layout.nb_channels, c->bits_per_raw_sample ? c->bits_per_raw_sample : c->bits_per_coded_sample,
                 c->extradata_size, dur, attached, (long long)c->bit_rate, s->start_time == AV_NOPTS_VALUE ? -1.0 : to_us(s->start_time, s->time_base) / 1e6);
    p += n; left -= n;
  }
  snprintf(p, left, "]}");
  return json;
}

EMSCRIPTEN_KEEPALIVE uint8_t *fp_extra_ptr(int i) { return fmt && i >= 0 && i < (int)fmt->nb_streams ? fmt->streams[i]->codecpar->extradata : NULL; }
EMSCRIPTEN_KEEPALIVE int fp_extra_len(int i) { return fmt && i >= 0 && i < (int)fmt->nb_streams ? fmt->streams[i]->codecpar->extradata_size : 0; }
/* After fp_start, the audio stream's extradata as it will be delivered (after ADTS headers are removed). */
EMSCRIPTEN_KEEPALIVE uint8_t *fp_aextra_ptr(void) { return absf ? absf->par_out->extradata : fp_extra_ptr(ai); }
EMSCRIPTEN_KEEPALIVE int fp_aextra_len(void) { return absf ? absf->par_out->extradata_size : fp_extra_len(ai); }

static int open_dec(AVCodecContext **ctx, AVStream *s) {
  const AVCodec *codec = avcodec_find_decoder(s->codecpar->codec_id);
  if (!codec) return AVERROR_DECODER_NOT_FOUND;
  *ctx = avcodec_alloc_context3(codec); if (!*ctx) return AVERROR(ENOMEM);
  int r = avcodec_parameters_to_context(*ctx, s->codecpar); if (r < 0) return r;
  (*ctx)->pkt_timebase = s->time_base;
  (*ctx)->thread_count = 1;
  return avcodec_open2(*ctx, codec, NULL);
}

/* vmode/amode: 0 = leave out, 1 = decode, 2 = hand over the packets unchanged. */
EMSCRIPTEN_KEEPALIVE int fp_start(int v, int vm, int a, int am, int max_w) {
  if (!fmt) return AVERROR(EINVAL);
  vi = v; ai = a; vmode = vm; amode = am; int r;
  for (unsigned i = 0; i < fmt->nb_streams; i++)
    fmt->streams[i]->discard = ((int)i == vi && vmode) || ((int)i == ai && amode) ? AVDISCARD_DEFAULT : AVDISCARD_ALL;
  if (vi >= 0 && vmode == 1) {
    if ((r = open_dec(&vdec, fmt->streams[vi])) < 0) return r;
    AVCodecParameters *c = fmt->streams[vi]->codecpar;
    AVRational sar = c->sample_aspect_ratio;
    int w = c->width, h = c->height;
    /* Square the pixels, so that a DVD's 720 by 576 widescreen picture becomes 1024 by 576. */
    if (sar.num > 0 && sar.den > 0 && sar.num != sar.den) {
      if (sar.num > sar.den) w = (int)((double)w * sar.num / sar.den + 0.5); else h = (int)((double)h * sar.den / sar.num + 0.5);
    }
    if (max_w > 0 && w > max_w) { h = (int)((double)h * max_w / w + 0.5); w = max_w; }
    outw = w & ~1; outh = h & ~1;
    if (outw < 2 || outh < 2) return AVERROR_INVALIDDATA;
  }
  if (ai >= 0 && amode == 1) {
    if ((r = open_dec(&adec, fmt->streams[ai])) < 0) return r;
    int in_rate = adec->sample_rate ? adec->sample_rate : fmt->streams[ai]->codecpar->sample_rate;
    out_rate = (in_rate == 44100 || in_rate == 48000) ? in_rate : 48000;
    int in_ch = adec->ch_layout.nb_channels ? adec->ch_layout.nb_channels : fmt->streams[ai]->codecpar->ch_layout.nb_channels;
    out_ch = in_ch == 1 ? 1 : 2;
  }
  if (ai >= 0 && amode == 2 && fmt->streams[ai]->codecpar->codec_id == AV_CODEC_ID_AAC && fmt->streams[ai]->codecpar->extradata_size == 0) {
    const AVBitStreamFilter *f = av_bsf_get_by_name("aac_adtstoasc");
    if (!f || (r = av_bsf_alloc(f, &absf)) < 0) return AVERROR_BSF_NOT_FOUND;
    avcodec_parameters_copy(absf->par_in, fmt->streams[ai]->codecpar);
    absf->time_base_in = fmt->streams[ai]->time_base;
    if ((r = av_bsf_init(absf)) < 0) return r;
  }
  is_avi = !strcmp(fmt->iformat->name, "avi"); nslots = 0;
  pkt = av_packet_alloc(); frm = av_frame_alloc();
  return (pkt && frm) ? 0 : AVERROR(ENOMEM);
}

/* A simple, fast way to remove the combing of interlaced video: each line becomes a blend with its neighbours. */
static void blend_plane(uint8_t *p, int w, int h, int stride) {
  if (h < 3) return;
  uint8_t *prev = malloc(w), *cur = malloc(w); if (!prev || !cur) { free(prev); free(cur); return; }
  memcpy(prev, p, w);
  for (int y = 1; y < h - 1; y++) {
    uint8_t *row = p + y * stride, *next = p + (y + 1) * stride;
    memcpy(cur, row, w);
    for (int x = 0; x < w; x++) row[x] = (prev[x] + 2 * cur[x] + next[x] + 2) >> 2;
    memcpy(prev, cur, w);
  }
  free(prev); free(cur);
}

static int emit_video(AVFrame *f) {
  if (!sws || f->width != swsw || f->height != swsh || f->format != swsfmt) {
    if (sws) sws_freeContext(sws);
    sws = sws_getContext(f->width, f->height, f->format, outw, outh, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
    if (!sws) return AVERROR(EINVAL);
    swsw = f->width; swsh = f->height; swsfmt = f->format;
  }
  int cw = outw / 2, ch = outh / 2, size = outw * outh + 2 * cw * ch, r;
  if ((r = ensure(size)) < 0) return r;
  uint8_t *dst[4] = { item, item + outw * outh, item + outw * outh + cw * ch, NULL };
  int ls[4] = { outw, cw, cw, 0 };
  sws_scale(sws, (const uint8_t * const *)f->data, f->linesize, 0, f->height, dst, ls);
  if (f->flags & AV_FRAME_FLAG_INTERLACED) { blend_plane(dst[0], outw, outh, outw); blend_plane(dst[1], cw, ch, cw); blend_plane(dst[2], cw, ch, cw); }
  item_len = size;
  AVRational tb = fmt->streams[vi]->time_base;
  int64_t t = f->best_effort_timestamp;
  if (is_avi && nslots > 0) {
    /* More slots waiting than the decoder can hold back means pictures were lost: skip their slots. */
    while (nslots > vdec->has_b_frames + 1) slot_pop();
    t = slot_pop();
  }
  item_pts = to_us(t, tb);
  item_dur = f->duration > 0 ? to_us(f->duration, tb) : 0;
  item_key = (f->flags & AV_FRAME_FLAG_KEY) ? 1 : 0;
  return 1;
}

static int swr_rate = 0, swr_fmt = -1, swr_nch = 0; static AVChannelLayout swr_layout;
static int emit_audio(AVFrame *f) {
  int r;
  /* Some files change their sound's sample rate or channels part-way through; start the resampler again if so. */
  if (swr && (f->sample_rate != swr_rate || f->format != swr_fmt || f->ch_layout.nb_channels != swr_nch || av_channel_layout_compare(&f->ch_layout, &swr_layout))) swr_free(&swr);
  if (!swr) {
    swr_rate = f->sample_rate; swr_fmt = f->format; swr_nch = f->ch_layout.nb_channels;
    av_channel_layout_uninit(&swr_layout); av_channel_layout_copy(&swr_layout, &f->ch_layout);
    AVChannelLayout out_layout; av_channel_layout_default(&out_layout, out_ch);
    AVChannelLayout in_layout;
    if (f->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC || !f->ch_layout.nb_channels) av_channel_layout_default(&in_layout, f->ch_layout.nb_channels ? f->ch_layout.nb_channels : 2);
    else av_channel_layout_copy(&in_layout, &f->ch_layout);
    r = swr_alloc_set_opts2(&swr, &out_layout, AV_SAMPLE_FMT_FLTP, out_rate, &in_layout, f->format, f->sample_rate, 0, NULL);
    av_channel_layout_uninit(&in_layout);
    if (r < 0 || (r = swr_init(swr)) < 0) return r;
  }
  int max = swr_get_out_samples(swr, f->nb_samples) + 32;
  if ((r = ensure(max * out_ch * 4)) < 0) return r;
  uint8_t *planes[8]; for (int c = 0; c < out_ch; c++) planes[c] = item + (size_t)c * max * 4;
  int got = swr_convert(swr, planes, max, (const uint8_t **)f->extended_data, f->nb_samples);
  if (got < 0) return got;
  /* Pack the channels tightly: channel 1's samples, then channel 2's. */
  if (out_ch == 2 && got < max) memmove(item + (size_t)got * 4, planes[1], (size_t)got * 4);
  item_samples = got; item_len = got * out_ch * 4;
  AVRational tb = fmt->streams[ai]->time_base;
  item_pts = to_us(f->best_effort_timestamp, tb);
  item_dur = (double)got * 1e6 / out_rate;
  return got > 0 ? 2 : 0;
}

static int emit_packet(AVPacket *p, int kind) {
  int r; if ((r = ensure(p->size)) < 0) return r;
  memcpy(item, p->data, p->size); item_len = p->size;
  AVRational tb = kind == 3 ? fmt->streams[vi]->time_base : (absf ? absf->time_base_out : fmt->streams[ai]->time_base);
  item_pts = to_us(p->pts != AV_NOPTS_VALUE ? p->pts : p->dts, tb);
  item_dur = p->duration > 0 ? to_us(p->duration, tb) : 0;
  item_key = (p->flags & AV_PKT_FLAG_KEY) ? 1 : 0;
  return kind;
}

/* Returns the next item: 1 a picture (I420), 2 sound (32-bit float, one channel after another), 3 a video packet,
 * 4 a sound packet, 0 the end, or a negative FFmpeg error. */
EMSCRIPTEN_KEEPALIVE int fp_next(void) {
  int r;
  for (;;) {
    if (pending == 1) {
      r = avcodec_receive_frame(vdec, frm);
      if (r >= 0) { r = emit_video(frm); av_frame_unref(frm); if (r < 0) { if (sws) { sws_freeContext(sws); sws = NULL; } continue; } return r; }
      pending = 0; if (r != AVERROR(EAGAIN) && r != AVERROR_EOF) bad_packets++;
    } else if (pending == 2) {
      r = avcodec_receive_frame(adec, frm);
      if (r >= 0) {
        r = emit_audio(frm); av_frame_unref(frm);
        /* A piece of sound that cannot be converted is left out (the page fills the gap with silence), rather than
         * stopping the whole file. */
        if (r < 0) { if (swr) swr_free(&swr); continue; }
        if (r) return r; continue;
      }
      pending = 0; if (r != AVERROR(EAGAIN) && r != AVERROR_EOF) bad_packets++;
    } else if (pending == 3) {
      r = av_bsf_receive_packet(absf, pkt);
      if (r >= 0) { r = emit_packet(pkt, 4); av_packet_unref(pkt); return r; }
      pending = 0; if (r != AVERROR(EAGAIN) && r != AVERROR_EOF) return r;
    }
    if (eof) {
      /* Drain whatever the decoders still hold, once each. */
      if (vdec && !vflushed) { vflushed = 1; avcodec_send_packet(vdec, NULL); pending = 1; continue; }
      if (adec && !aflushed) { aflushed = 1; avcodec_send_packet(adec, NULL); pending = 2; continue; }
      if (absf && !bflushed) { bflushed = 1; av_bsf_send_packet(absf, NULL); pending = 3; continue; }
      if (swr) { /* Sound still inside the resampler. */
        int max = 4096; if ((r = ensure(max * out_ch * 4)) < 0) return r;
        uint8_t *planes[8]; for (int c = 0; c < out_ch; c++) planes[c] = item + (size_t)c * max * 4;
        int got = swr_convert(swr, planes, max, NULL, 0);
        if (got > 0) { if (out_ch == 2) memmove(item + (size_t)got * 4, planes[1], (size_t)got * 4); item_samples = got; item_len = got * out_ch * 4; item_pts = -2; item_dur = (double)got * 1e6 / out_rate; return 2; }
        swr_free(&swr);
      }
      return 0;
    }
    r = av_read_frame(fmt, pkt);
    if (r == AVERROR_EOF || (r < 0 && avio_feof(fmt->pb))) { eof = 1; continue; }
    /* As FFmpeg's own converter does, a read error is treated as the end of the file; the page is told. */
    if (r < 0) { read_error = r; eof = 1; continue; }
    if (pkt->stream_index == vi && vmode == 1) {
      int64_t slot = pkt->pts != AV_NOPTS_VALUE ? pkt->pts : pkt->dts; int size = pkt->size;
      r = avcodec_send_packet(vdec, pkt); av_packet_unref(pkt);
      if (r >= 0 && size > 0 && is_avi && slot != AV_NOPTS_VALUE) slot_push(slot);
      if (r < 0) bad_packets++; /* a damaged piece is skipped, as FFmpeg itself does */
      pending = 1; continue;
    }
    if (pkt->stream_index == ai && amode == 1) {
      r = avcodec_send_packet(adec, pkt); av_packet_unref(pkt);
      if (r < 0) bad_packets++;
      pending = 2; continue;
    }
    if (pkt->stream_index == vi && vmode == 2) { r = emit_packet(pkt, 3); av_packet_unref(pkt); return r; }
    if (pkt->stream_index == ai && amode == 2) {
      if (absf) { r = av_bsf_send_packet(absf, pkt); if (r < 0) { av_packet_unref(pkt); return r; } pending = 3; continue; }
      r = emit_packet(pkt, 4); av_packet_unref(pkt); return r;
    }
    av_packet_unref(pkt);
  }
}

EMSCRIPTEN_KEEPALIVE int fp_read_error(void) { return read_error; }
EMSCRIPTEN_KEEPALIVE int fp_bad_packets(void) { return bad_packets; }
EMSCRIPTEN_KEEPALIVE double fp_start_time(void) { return fmt && fmt->start_time != AV_NOPTS_VALUE ? (double)fmt->start_time : 0; }

EMSCRIPTEN_KEEPALIVE int fp_pos_percent(void) {
  if (!fmt || !fmt->pb) return 0;
  int64_t size = avio_size(fmt->pb), pos = avio_tell(fmt->pb);
  return size > 0 ? (int)(pos * 1000 / size) : 0;
}

/* ---- A sound decoder on its own, for sound inside files the browser can otherwise read (Dolby Digital in MTS and
 * MKV files). Packets in, 32-bit float sound out. ---- */
static AVCodecContext *sdec = NULL; static SwrContext *sswr = NULL; static int s_rate = 0, s_ch = 0;
static AVPacket *spkt = NULL; static AVFrame *sfrm = NULL;

EMSCRIPTEN_KEEPALIVE int fp_sdec_open(const char *name, int rate, int ch) {
  if (sdec) avcodec_free_context(&sdec); if (sswr) swr_free(&sswr);
  const AVCodec *c = avcodec_find_decoder_by_name(name); if (!c) return AVERROR_DECODER_NOT_FOUND;
  sdec = avcodec_alloc_context3(c); if (!sdec) return AVERROR(ENOMEM);
  sdec->sample_rate = rate; av_channel_layout_default(&sdec->ch_layout, ch > 0 ? ch : 2);
  s_rate = (rate == 44100 || rate == 48000) ? rate : 48000; s_ch = ch == 1 ? 1 : 2;
  if (!sfrm) sfrm = av_frame_alloc(); if (!spkt) spkt = av_packet_alloc();
  if (!sfrm || !spkt) return AVERROR(ENOMEM);
  return avcodec_open2(sdec, c, NULL);
}
EMSCRIPTEN_KEEPALIVE int fp_sdec_rate(void) { return s_rate; }
EMSCRIPTEN_KEEPALIVE int fp_sdec_ch(void) { return s_ch; }
EMSCRIPTEN_KEEPALIVE uint8_t *fp_alloc(int n) { return av_malloc(n); }
EMSCRIPTEN_KEEPALIVE void fp_free(uint8_t *p) { av_free(p); }

/* Sends one packet (data may be NULL to flush) and returns the number of samples decoded into the item buffer. */
EMSCRIPTEN_KEEPALIVE int fp_sdec_decode(uint8_t *data, int len) {
  if (!sdec) return AVERROR(EINVAL);
  int r, total = 0;
  if (data) { av_packet_unref(spkt); if ((r = av_new_packet(spkt, len)) < 0) return r; memcpy(spkt->data, data, len); r = avcodec_send_packet(sdec, spkt); }
  else r = avcodec_send_packet(sdec, NULL);
  if (r < 0 && r != AVERROR_EOF && r != AVERROR_INVALIDDATA) return r;
  /* Collect all frames into one planar buffer; frames are small (1536 samples for AC-3). */
  static float *acc[2] = { NULL, NULL }; static int acc_cap = 0;
  while ((r = avcodec_receive_frame(sdec, sfrm)) >= 0) {
    if (!sswr) {
      AVChannelLayout out; av_channel_layout_default(&out, s_ch);
      if ((r = swr_alloc_set_opts2(&sswr, &out, AV_SAMPLE_FMT_FLTP, s_rate, &sfrm->ch_layout, sfrm->format, sfrm->sample_rate, 0, NULL)) < 0 || (r = swr_init(sswr)) < 0) { av_frame_unref(sfrm); return r; }
    }
    int max = swr_get_out_samples(sswr, sfrm->nb_samples) + 32;
    if (total + max > acc_cap) {
      int cap = (total + max) * 2;
      for (int c = 0; c < 2; c++) { float *p = realloc(acc[c], cap * 4); if (!p) { av_frame_unref(sfrm); return AVERROR(ENOMEM); } acc[c] = p; }
      acc_cap = cap;
    }
    uint8_t *planes[2] = { (uint8_t *)(acc[0] + total), (uint8_t *)(acc[1] + total) };
    int got = swr_convert(sswr, planes, max, (const uint8_t **)sfrm->extended_data, sfrm->nb_samples);
    av_frame_unref(sfrm);
    if (got > 0) total += got;
  }
  if (!data && sswr) {
    int max = 4096;
    if (total + max > acc_cap) { int cap = total + max; for (int c = 0; c < 2; c++) { float *p = realloc(acc[c], cap * 4); if (!p) return AVERROR(ENOMEM); acc[c] = p; } acc_cap = cap; }
    uint8_t *planes[2] = { (uint8_t *)(acc[0] + total), (uint8_t *)(acc[1] + total) };
    int got = swr_convert(sswr, planes, max, NULL, 0); if (got > 0) total += got;
  }
  if ((r = ensure(total * s_ch * 4)) < 0) return r;
  for (int c = 0; c < s_ch; c++) memcpy(item + (size_t)c * total * 4, acc[c], (size_t)total * 4);
  item_samples = total; item_len = total * s_ch * 4;
  return total;
}

/* ---- FLAC to ALAC (Apple Lossless) in an M4A file, entirely here. Returns 0 or a negative FFmpeg error. ---- */
EMSCRIPTEN_KEEPALIVE int fp_flac_to_alac(const char *in, const char *out) {
  AVFormatContext *ic = NULL, *oc = NULL; AVCodecContext *dc = NULL, *ec = NULL;
  AVPacket *p = av_packet_alloc(), *op = av_packet_alloc(); AVFrame *f = av_frame_alloc(), *ef = NULL;
  int r, si = -1; int64_t next_pts = 0; last_report = -1;
  if (!p || !op || !f) { r = AVERROR(ENOMEM); goto end; }
  if ((r = avformat_open_input(&ic, in, NULL, NULL)) < 0) goto end;
  if ((r = avformat_find_stream_info(ic, NULL)) < 0) goto end;
  for (unsigned i = 0; i < ic->nb_streams; i++) if (ic->streams[i]->codecpar->codec_id == AV_CODEC_ID_FLAC) { si = i; break; }
  if (si < 0) { r = AVERROR_STREAM_NOT_FOUND; goto end; }
  AVStream *is = ic->streams[si];
  if ((r = open_dec(&dc, is)) < 0) goto end;
  const AVCodec *enc = avcodec_find_encoder(AV_CODEC_ID_ALAC); if (!enc) { r = AVERROR_ENCODER_NOT_FOUND; goto end; }
  if ((r = avformat_alloc_output_context2(&oc, NULL, "ipod", out)) < 0) goto end;
  AVStream *os = avformat_new_stream(oc, NULL); if (!os) { r = AVERROR(ENOMEM); goto end; }
  ec = avcodec_alloc_context3(enc); if (!ec) { r = AVERROR(ENOMEM); goto end; }
  int bits = is->codecpar->bits_per_raw_sample ? is->codecpar->bits_per_raw_sample : 16;
  ec->sample_rate = dc->sample_rate; av_channel_layout_copy(&ec->ch_layout, &dc->ch_layout);
  ec->sample_fmt = bits > 16 ? AV_SAMPLE_FMT_S32P : AV_SAMPLE_FMT_S16P;
  ec->bits_per_raw_sample = bits;
  ec->time_base = (AVRational){ 1, dc->sample_rate };
  if (oc->oformat->flags & AVFMT_GLOBALHEADER) ec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
  if ((r = avcodec_open2(ec, enc, NULL)) < 0) {
    /* ALAC names only certain speaker layouts. If the file's own description is not one of them, keep every
     * channel, in the same order, under the standard layout for that number of channels. */
    avcodec_free_context(&ec);
    ec = avcodec_alloc_context3(enc); if (!ec) { r = AVERROR(ENOMEM); goto end; }
    ec->sample_rate = dc->sample_rate; av_channel_layout_default(&ec->ch_layout, dc->ch_layout.nb_channels);
    ec->sample_fmt = bits > 16 ? AV_SAMPLE_FMT_S32P : AV_SAMPLE_FMT_S16P; ec->bits_per_raw_sample = bits;
    ec->time_base = (AVRational){ 1, dc->sample_rate };
    if (oc->oformat->flags & AVFMT_GLOBALHEADER) ec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    if ((r = avcodec_open2(ec, enc, NULL)) < 0) goto end;
  }
  if ((r = avcodec_parameters_from_context(os->codecpar, ec)) < 0) goto end;
  os->time_base = ec->time_base;
  av_dict_copy(&oc->metadata, ic->metadata, 0);
  av_dict_copy(&oc->metadata, is->metadata, 0);
  if ((r = avio_open(&oc->pb, out, AVIO_FLAG_WRITE)) < 0) goto end;
  if ((r = avformat_write_header(oc, NULL)) < 0) goto end;
  /* ALAC takes fixed blocks of frame_size samples; the decoder's frames are regrouped into blocks here. */
  int fs = ec->frame_size, ch = dc->ch_layout.nb_channels, bps = bits > 16 ? 4 : 2, fill = 0;
  ef = av_frame_alloc(); ef->nb_samples = fs; ef->format = ec->sample_fmt; av_channel_layout_copy(&ef->ch_layout, &ec->ch_layout); ef->sample_rate = ec->sample_rate;
  if ((r = av_frame_get_buffer(ef, 0)) < 0) goto end;
  int64_t total = ic->duration > 0 ? ic->duration : 0, done_us = 0; int flushing = 0;
  for (;;) {
    int got_input = 0;
    if (!flushing) {
      r = av_read_frame(ic, p);
      if (r == AVERROR_EOF) { flushing = 1; avcodec_send_packet(dc, NULL); }
      else if (r < 0) goto end;
      else { if (p->stream_index == si) { r = avcodec_send_packet(dc, p); if (r < 0 && r != AVERROR_INVALIDDATA) { av_packet_unref(p); goto end; } } av_packet_unref(p); }
    }
    while ((r = avcodec_receive_frame(dc, f)) >= 0) {
      got_input = 1;
      if (f->format != AV_SAMPLE_FMT_S16 && f->format != AV_SAMPLE_FMT_S32 && f->format != AV_SAMPLE_FMT_S16P && f->format != AV_SAMPLE_FMT_S32P) { r = AVERROR_PATCHWELCOME; av_frame_unref(f); goto end; }
      int planar = av_sample_fmt_is_planar(f->format), inb = av_get_bytes_per_sample(f->format);
      for (int s = 0; s < f->nb_samples; s++) {
        for (int c = 0; c < ch; c++) {
          const uint8_t *src = planar ? f->extended_data[c] + (size_t)s * inb : f->extended_data[0] + ((size_t)s * ch + c) * inb;
          uint8_t *dst = ef->extended_data[c] + (size_t)fill * bps;
          if (bps == 2) { int16_t v = inb == 2 ? *(const int16_t *)src : (int16_t)(*(const int32_t *)src >> 16); memcpy(dst, &v, 2); }
          else { int32_t v = inb == 4 ? *(const int32_t *)src : (int32_t)(*(const int16_t *)src) << 16; memcpy(dst, &v, 4); }
        }
        if (++fill == fs) {
          ef->pts = next_pts; next_pts += fs; ef->nb_samples = fs;
          if ((r = avcodec_send_frame(ec, ef)) < 0) { av_frame_unref(f); goto end; }
          while (avcodec_receive_packet(ec, op) >= 0) { av_packet_rescale_ts(op, ec->time_base, os->time_base); op->stream_index = 0; av_interleaved_write_frame(oc, op); }
          if ((r = av_frame_make_writable(ef)) < 0) { av_frame_unref(f); goto end; }
          fill = 0;
        }
      }
      if (f->pts != AV_NOPTS_VALUE) done_us = (int64_t)to_us(f->pts, is->time_base);
      av_frame_unref(f);
    }
    if (total > 0) fp_report((double)done_us / total);
    if (r != AVERROR(EAGAIN) && r != AVERROR_EOF) goto end;
    if (flushing && r == AVERROR_EOF) break;
    (void)got_input;
  }
  if (fill > 0) {
    ef->nb_samples = fill; ef->pts = next_pts; next_pts += fill;
    if ((r = avcodec_send_frame(ec, ef)) < 0) goto end;
  }
  avcodec_send_frame(ec, NULL);
  while (avcodec_receive_packet(ec, op) >= 0) { av_packet_rescale_ts(op, ec->time_base, os->time_base); op->stream_index = 0; av_interleaved_write_frame(oc, op); }
  r = av_write_trailer(oc);
  fp_report(1.0);
end:
  if (oc) { if (oc->pb) avio_closep(&oc->pb); avformat_free_context(oc); }
  if (ic) avformat_close_input(&ic);
  if (dc) avcodec_free_context(&dc);
  if (ec) avcodec_free_context(&ec);
  av_packet_free(&p); av_packet_free(&op); av_frame_free(&f); av_frame_free(&ef);
  return r < 0 ? r : 0;
}

EMSCRIPTEN_KEEPALIVE const char *fp_err(int e) { static char buf[128]; av_strerror(e, buf, sizeof buf); return buf; }
EMSCRIPTEN_KEEPALIVE const char *fp_version(void) { return av_version_info(); }
