Mercurial
view third_party/ffmpeg/ffmpeg_cli.c @ 268:f7b1188d5fb1
support repo rules Copilot bundle path
Match both +_repo_rules2+ and +http_archive+ Copilot CLI repository layouts during deployment and production startup.
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Fri, 07 Aug 2026 13:02:41 -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; }