view mrjunejune/generate_hls_sample.c @ 256:30c2196d03d4

[site] Integrate Zenbu themes and components Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Tue, 04 Aug 2026 16:49:01 -0700
parents 543df0fe7168
children
line wrap: on
line source

#include "third_party/ffmpeg/ffmpeg_cli.h"

#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

static int remove_flat_directory(const char *directory)
{
  DIR *entries = opendir(directory);
  if (!entries)
    return errno == ENOENT ? 0 : -1;

  struct dirent *entry;
  while ((entry = readdir(entries)) != NULL)
  {
    if (strcmp(entry->d_name, ".") == 0 ||
        strcmp(entry->d_name, "..") == 0)
      continue;
    char path[1024];
    int length = snprintf(path, sizeof(path), "%s/%s", directory, entry->d_name);
    if (length < 0 || (size_t)length >= sizeof(path) || unlink(path) != 0)
    {
      closedir(entries);
      return -1;
    }
  }
  closedir(entries);
  return rmdir(directory);
}

static int write_master_playlist(const char *directory)
{
  char path[1024];
  int length = snprintf(path, sizeof(path), "%s/master.m3u8", directory);
  if (length < 0 || (size_t)length >= sizeof(path))
    return -1;

  FILE *playlist = fopen(path, "wb");
  if (!playlist)
    return -1;
  const char *content =
      "#EXTM3U\n"
      "#EXT-X-VERSION:7\n"
      "#EXT-X-STREAM-INF:BANDWIDTH=1200000,AVERAGE-BANDWIDTH=1000000,"
      "CODECS=\"avc1.4d401f,mp4a.40.2\"\n"
      "h264-stream.m3u8\n"
      "#EXT-X-STREAM-INF:BANDWIDTH=900000,AVERAGE-BANDWIDTH=750000,"
      "CODECS=\"vp09.00.10.08,opus\"\n"
      "vp9-stream.m3u8\n";
  size_t content_length = strlen(content);
  int result = fwrite(content, 1, content_length, playlist) == content_length
      ? 0
      : -1;
  fclose(playlist);
  return result;
}

static const char *workspace_path(
    const char *path,
    char *buffer,
    size_t buffer_size)
{
  const char *workspace = getenv("BUILD_WORKSPACE_DIRECTORY");
  if (!workspace || path[0] == '/')
    return path;
  int length = snprintf(buffer, buffer_size, "%s/%s", workspace, path);
  return length < 0 || (size_t)length >= buffer_size ? NULL : buffer;
}

int main(int argc, char **argv)
{
  if (argc != 3)
  {
    fprintf(stderr, "Usage: %s <input.mp4> <output-directory>\n", argv[0]);
    return 2;
  }

  char final_buffer[1024];
  const char *final_directory = workspace_path(
      argv[2],
      final_buffer,
      sizeof(final_buffer));
  if (!final_directory)
  {
    fprintf(stderr, "HLS output path is too long.\n");
    return 1;
  }

  char stage_template[1100];
  int stage_length = snprintf(
      stage_template,
      sizeof(stage_template),
      "%s.stage-XXXXXX",
      final_directory);
  if (stage_length < 0 || (size_t)stage_length >= sizeof(stage_template) ||
      !mkdtemp(stage_template))
  {
    fprintf(stderr, "Unable to create the HLS staging directory.\n");
    return 1;
  }

  Fmp4HlsOpts options = ffmpeg_fmp4_hls_opts_default();
  options.codec = FFMPEG_HLS_VP9_OPUS;
  FfmpegResult result = ffmpeg_video_to_fmp4_hls(
      argv[1],
      stage_template,
      "vp9",
      &options);
  if (result == FFMPEG_OK)
  {
    options.codec = FFMPEG_HLS_H264_AAC;
    options.video_bitrate = 1200;
    options.audio_bitrate = 128;
    result = ffmpeg_video_to_fmp4_hls(
        argv[1],
        stage_template,
        "h264",
        &options);
  }
  if (result == FFMPEG_OK)
  {
    options.use_mpeg_ts = true;
    result = ffmpeg_video_to_fmp4_hls(
        argv[1],
        stage_template,
        "h264-ts",
        &options);
  }
  if (result != FFMPEG_OK || write_master_playlist(stage_template) != 0)
  {
    fprintf(stderr, "%s\n", ffmpeg_last_error());
    remove_flat_directory(stage_template);
    return 1;
  }

  char backup_directory[1100];
  int backup_length = snprintf(
      backup_directory,
      sizeof(backup_directory),
      "%s.backup",
      final_directory);
  if (backup_length < 0 || (size_t)backup_length >= sizeof(backup_directory))
  {
    remove_flat_directory(stage_template);
    return 1;
  }
  remove_flat_directory(backup_directory);

  bool had_previous = rename(final_directory, backup_directory) == 0;
  if (!had_previous && errno != ENOENT)
  {
    remove_flat_directory(stage_template);
    return 1;
  }
  if (rename(stage_template, final_directory) != 0)
  {
    if (had_previous)
      rename(backup_directory, final_directory);
    remove_flat_directory(stage_template);
    return 1;
  }
  if (had_previous && remove_flat_directory(backup_directory) != 0)
  {
    fprintf(stderr, "Generated HLS sample, but could not remove its backup.\n");
    return 1;
  }

  printf("Generated fMP4 and MPEG-TS HLS samples from %s in %s\n", argv[1], final_directory);
  return 0;
}