diff mrjunejune/generate_hls_sample.c @ 242:543df0fe7168

[tools] Add full HLS player support
author MrJuneJune <me@mrjunejune.com>
date Mon, 03 Aug 2026 13:14:41 -0700
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mrjunejune/generate_hls_sample.c	Mon Aug 03 13:14:41 2026 -0700
@@ -0,0 +1,174 @@
+#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;
+}