view third_party/ffmpeg/ffmpeg_cli.c @ 266:efaf4c63cc94

fix clean production bundle staging Recreate deployment staging before copying the Bazel bundle and verify required inference runtime paths before promoting it. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Fri, 07 Aug 2026 12:52:30 -0700
parents 543df0fe7168
children
line wrap: on
line source

#include "third_party/ffmpeg/ffmpeg_cli.h"

#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#define MAX_ERROR_LENGTH 512
#define MAX_PATH_LENGTH 1024

static char g_last_error[MAX_ERROR_LENGTH];

static void set_error(const char *format, ...)
{
  va_list arguments;
  va_start(arguments, format);
  vsnprintf(g_last_error, sizeof(g_last_error), format, arguments);
  va_end(arguments);
}

static bool file_exists(const char *path)
{
  struct stat status;
  return path && stat(path, &status) == 0 && S_ISREG(status.st_mode);
}

static bool ensure_directory(const char *path)
{
  struct stat status;
  if (stat(path, &status) == 0)
    return S_ISDIR(status.st_mode);
  return mkdir(path, 0755) == 0;
}

static int run_process(char *const arguments[])
{
  pid_t child = fork();
  if (child < 0)
  {
    set_error("fork failed: %s", strerror(errno));
    return -1;
  }
  if (child == 0)
  {
    execvp(arguments[0], arguments);
    _exit(127);
  }

  int status = 0;
  while (waitpid(child, &status, 0) < 0)
  {
    if (errno == EINTR)
      continue;
    set_error("waitpid failed: %s", strerror(errno));
    return -1;
  }
  if (!WIFEXITED(status))
    return -1;
  return WEXITSTATUS(status);
}

Fmp4HlsOpts ffmpeg_fmp4_hls_opts_default(void)
{
  return (Fmp4HlsOpts){
    .segment_duration = 2,
    .video_bitrate = 900,
    .audio_bitrate = 96,
    .max_width = 960,
    .max_height = 720,
    .include_audio = true,
    .use_mpeg_ts = false,
    .codec = FFMPEG_HLS_VP9_OPUS,
  };
}

bool ffmpeg_is_available(void)
{
  char *const arguments[] = {
    "ffmpeg", "-version", NULL,
  };
  return run_process(arguments) == 0;
}

const char *ffmpeg_last_error(void)
{
  return g_last_error;
}

FfmpegResult ffmpeg_video_to_fmp4_hls(
    const char *input_path,
    const char *output_dir,
    const char *name,
    const Fmp4HlsOpts *options)
{
  if (!input_path || !output_dir || !name || name[0] == '\0')
  {
    set_error("input path, output directory, and name are required");
    return FFMPEG_ERR_INVALID_ARGS;
  }
  if (!file_exists(input_path))
  {
    set_error("input file not found: %s", input_path);
    return FFMPEG_ERR_INPUT_NOT_FOUND;
  }
  if (!ensure_directory(output_dir))
  {
    set_error("unable to create output directory: %s", output_dir);
    return FFMPEG_ERR_OUTPUT_FAILED;
  }

  Fmp4HlsOpts defaults = ffmpeg_fmp4_hls_opts_default();
  const Fmp4HlsOpts *opts = options ? options : &defaults;
  if (opts->segment_duration <= 0 ||
      opts->video_bitrate <= 0 ||
      opts->max_width <= 0 ||
      opts->max_height <= 0)
  {
    set_error("invalid fMP4 HLS options");
    return FFMPEG_ERR_INVALID_ARGS;
  }

  char playlist_path[MAX_PATH_LENGTH];
  char init_name[256];
  char segment_pattern[MAX_PATH_LENGTH];
  char segment_duration[16];
  char video_bitrate[32];
  char audio_bitrate[32];
  char keyframe_expression[32];
  char scale_filter[256];

  snprintf(
      playlist_path,
      sizeof(playlist_path),
      "%s/%s-stream.m3u8",
      output_dir,
      name);
  snprintf(init_name, sizeof(init_name), "%s-init.mp4", name);
  snprintf(
      segment_pattern,
      sizeof(segment_pattern),
      "%s/%s-segment%%03d.%s",
      output_dir,
      name,
      opts->use_mpeg_ts ? "ts" : "m4s");
  snprintf(segment_duration, sizeof(segment_duration), "%d", opts->segment_duration);
  snprintf(video_bitrate, sizeof(video_bitrate), "%dk", opts->video_bitrate);
  snprintf(audio_bitrate, sizeof(audio_bitrate), "%dk", opts->audio_bitrate);
  snprintf(
      keyframe_expression,
      sizeof(keyframe_expression),
      "expr:gte(t,n_forced*%d)",
      opts->segment_duration);
  snprintf(
      scale_filter,
      sizeof(scale_filter),
      "scale=w='min(iw,%d)':h='min(ih,%d)':force_original_aspect_ratio=decrease,"
      "scale=trunc(iw/2)*2:trunc(ih/2)*2",
      opts->max_width,
      opts->max_height);

  char *arguments[48];
  size_t count = 0;
#define ADD_ARGUMENT(value) arguments[count++] = (char *)(value)
  ADD_ARGUMENT("ffmpeg");
  ADD_ARGUMENT("-hide_banner");
  ADD_ARGUMENT("-loglevel");
  ADD_ARGUMENT("error");
  ADD_ARGUMENT("-nostdin");
  ADD_ARGUMENT("-y");
  ADD_ARGUMENT("-i");
  ADD_ARGUMENT(input_path);
  ADD_ARGUMENT("-map");
  ADD_ARGUMENT("0:v:0");
  if (opts->include_audio)
  {
    ADD_ARGUMENT("-map");
    ADD_ARGUMENT("0:a:0?");
  }
  ADD_ARGUMENT("-c:v");
  if (opts->codec == FFMPEG_HLS_H264_AAC)
  {
    ADD_ARGUMENT("libx264");
    ADD_ARGUMENT("-preset");
    ADD_ARGUMENT("medium");
    ADD_ARGUMENT("-profile:v");
    ADD_ARGUMENT("main");
    ADD_ARGUMENT("-level:v");
    ADD_ARGUMENT("3.1");
  }
  else
  {
    ADD_ARGUMENT("libvpx-vp9");
    ADD_ARGUMENT("-deadline");
    ADD_ARGUMENT("good");
    ADD_ARGUMENT("-cpu-used");
    ADD_ARGUMENT("4");
  }
  ADD_ARGUMENT("-pix_fmt");
  ADD_ARGUMENT("yuv420p");
  ADD_ARGUMENT("-b:v");
  ADD_ARGUMENT(video_bitrate);
  ADD_ARGUMENT("-vf");
  ADD_ARGUMENT(scale_filter);
  ADD_ARGUMENT("-force_key_frames");
  ADD_ARGUMENT(keyframe_expression);
  if (opts->include_audio)
  {
    ADD_ARGUMENT("-c:a");
    ADD_ARGUMENT(
        opts->codec == FFMPEG_HLS_H264_AAC
            ? "aac"
            : "libopus");
    ADD_ARGUMENT("-b:a");
    ADD_ARGUMENT(audio_bitrate);
  }
  else
  {
    ADD_ARGUMENT("-an");
  }
  ADD_ARGUMENT("-f");
  ADD_ARGUMENT("hls");
  ADD_ARGUMENT("-hls_time");
  ADD_ARGUMENT(segment_duration);
  ADD_ARGUMENT("-hls_playlist_type");
  ADD_ARGUMENT("vod");
  if (!opts->use_mpeg_ts)
  {
    ADD_ARGUMENT("-hls_segment_type");
    ADD_ARGUMENT("fmp4");
    ADD_ARGUMENT("-hls_fmp4_init_filename");
    ADD_ARGUMENT(init_name);
  }
  ADD_ARGUMENT("-hls_segment_filename");
  ADD_ARGUMENT(segment_pattern);
  ADD_ARGUMENT(playlist_path);
  arguments[count] = NULL;
#undef ADD_ARGUMENT

  int result = run_process(arguments);
  if (result != 0)
  {
    set_error("ffmpeg HLS conversion failed with exit code %d", result);
    return FFMPEG_ERR_PROCESS_FAILED;
  }
  if (!file_exists(playlist_path))
  {
    set_error("playlist was not created: %s", playlist_path);
    return FFMPEG_ERR_OUTPUT_FAILED;
  }

  return FFMPEG_OK;
}