Mercurial
view third_party/ffmpeg/ffmpeg_cli.c @ 217:7ef4c9d2a72d hg-web
[DO NOT PUSH] Random values.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Sun, 25 Jan 2026 10:44:04 -0800 |
| parents | |
| children |
line wrap: on
line source
#include "ffmpeg_cli.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdarg.h> #include <sys/stat.h> #include <sys/wait.h> #include <errno.h> #define MAX_CMD_LEN 4096 #define MAX_ERROR_LEN 512 static char last_error[MAX_ERROR_LEN] = {0}; static void set_error(const char* fmt, ...) { va_list args; va_start(args, fmt); vsnprintf(last_error, MAX_ERROR_LEN, fmt, args); va_end(args); } static bool file_exists(const char* path) { struct stat st; return stat(path, &st) == 0; } static bool ensure_dir(const char* path) { struct stat st; if (stat(path, &st) == 0) { return S_ISDIR(st.st_mode); } return mkdir(path, 0755) == 0; } static int run_command(const char* cmd) { int ret = system(cmd); if (ret == -1) { set_error("Failed to execute command: %s", strerror(errno)); return -1; } return WEXITSTATUS(ret); } ImageConvertOpts ffmpeg_image_opts_default(void) { return (ImageConvertOpts){ .quality = 80, .max_width = 0, .max_height = 0, .preserve_aspect = true, }; } HlsConvertOpts ffmpeg_hls_opts_default(void) { return (HlsConvertOpts){ .segment_duration = 6, .video_bitrate = 0, .audio_bitrate = 128, .max_width = 0, .max_height = 0, .generate_thumbnail = true, }; } bool ffmpeg_is_available(void) { return system("ffmpeg -version > /dev/null 2>&1") == 0; } const char* ffmpeg_last_error(void) { return last_error; } FfmpegResult ffmpeg_image_to_webp(const char* input_path, const char* output_path, ImageConvertOpts* opts) { if (!input_path || !output_path) { set_error("Invalid arguments: input_path and output_path 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; } ImageConvertOpts default_opts = ffmpeg_image_opts_default(); if (!opts) opts = &default_opts; char cmd[MAX_CMD_LEN]; char scale_filter[256] = ""; // Build scale filter if resizing if (opts->max_width > 0 || opts->max_height > 0) { int w = opts->max_width > 0 ? opts->max_width : -1; int h = opts->max_height > 0 ? opts->max_height : -1; if (opts->preserve_aspect) { if (w > 0 && h > 0) { snprintf(scale_filter, sizeof(scale_filter), "-vf \"scale='min(%d,iw)':min'(%d,ih)':force_original_aspect_ratio=decrease\"", w, h); } else if (w > 0) { snprintf(scale_filter, sizeof(scale_filter), "-vf \"scale='min(%d,iw)':-1\"", w); } else { snprintf(scale_filter, sizeof(scale_filter), "-vf \"scale=-1:'min(%d,ih)'\"", h); } } else { snprintf(scale_filter, sizeof(scale_filter), "-vf \"scale=%d:%d\"", w, h); } } snprintf(cmd, sizeof(cmd), "ffmpeg -y -i \"%s\" %s -quality %d \"%s\" 2>/dev/null", input_path, scale_filter, opts->quality, output_path); int ret = run_command(cmd); if (ret != 0) { set_error("FFmpeg conversion failed with code %d", ret); return FFMPEG_ERR_PROCESS_FAILED; } if (!file_exists(output_path)) { set_error("Output file was not created"); return FFMPEG_ERR_OUTPUT_FAILED; } return FFMPEG_OK; } FfmpegResult ffmpeg_video_to_hls(const char* input_path, const char* output_dir, const char* name, HlsConvertOpts* opts) { if (!input_path || !output_dir || !name) { set_error("Invalid arguments: input_path, output_dir, and name 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_dir(output_dir)) { set_error("Failed to create output directory: %s", output_dir); return FFMPEG_ERR_OUTPUT_FAILED; } HlsConvertOpts default_opts = ffmpeg_hls_opts_default(); if (!opts) opts = &default_opts; char cmd[MAX_CMD_LEN]; char scale_filter[256] = ""; char video_bitrate[64] = ""; char audio_bitrate[64] = ""; // Build scale filter if resizing if (opts->max_width > 0 || opts->max_height > 0) { int w = opts->max_width > 0 ? opts->max_width : -2; int h = opts->max_height > 0 ? opts->max_height : -2; snprintf(scale_filter, sizeof(scale_filter), "-vf \"scale=%d:%d\"", w, h); } // Video bitrate if (opts->video_bitrate > 0) { snprintf(video_bitrate, sizeof(video_bitrate), "-b:v %dk", opts->video_bitrate); } // Audio bitrate snprintf(audio_bitrate, sizeof(audio_bitrate), "-b:a %dk", opts->audio_bitrate > 0 ? opts->audio_bitrate : 128); char playlist_path[1024]; char segment_pattern[1024]; snprintf(playlist_path, sizeof(playlist_path), "%s/%s.m3u8", output_dir, name); snprintf(segment_pattern, sizeof(segment_pattern), "%s/%s_%%03d.ts", output_dir, name); // Main HLS conversion command snprintf(cmd, sizeof(cmd), "ffmpeg -y -i \"%s\" " "-c:v libx264 -preset fast -crf 22 %s %s " "-c:a aac %s " "-f hls " "-hls_time %d " "-hls_list_size 0 " "-hls_segment_filename \"%s\" " "\"%s\" 2>/dev/null", input_path, scale_filter, video_bitrate, audio_bitrate, opts->segment_duration, segment_pattern, playlist_path); int ret = run_command(cmd); if (ret != 0) { set_error("FFmpeg HLS conversion failed with code %d", ret); return FFMPEG_ERR_PROCESS_FAILED; } // Generate thumbnail if requested if (opts->generate_thumbnail) { char thumb_path[1024]; snprintf(thumb_path, sizeof(thumb_path), "%s/%s_thumb.jpg", output_dir, name); ffmpeg_video_thumbnail(input_path, thumb_path, 1); } return FFMPEG_OK; } FfmpegResult ffmpeg_video_thumbnail(const char* input_path, const char* output_path, int timestamp_seconds) { if (!input_path || !output_path) { set_error("Invalid arguments: input_path and output_path 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; } char cmd[MAX_CMD_LEN]; snprintf(cmd, sizeof(cmd), "ffmpeg -y -i \"%s\" -ss %d -vframes 1 -q:v 2 \"%s\" 2>/dev/null", input_path, timestamp_seconds, output_path); int ret = run_command(cmd); if (ret != 0) { set_error("FFmpeg thumbnail generation failed with code %d", ret); return FFMPEG_ERR_PROCESS_FAILED; } return FFMPEG_OK; } FfmpegResult ffmpeg_get_duration(const char* input_path, double* duration_out) { if (!input_path || !duration_out) { set_error("Invalid arguments"); 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; } char cmd[MAX_CMD_LEN]; snprintf(cmd, sizeof(cmd), "ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 \"%s\" 2>/dev/null", input_path); FILE* fp = popen(cmd, "r"); if (!fp) { set_error("Failed to run ffprobe"); return FFMPEG_ERR_PROCESS_FAILED; } char output[64]; if (fgets(output, sizeof(output), fp) != NULL) { *duration_out = atof(output); } else { *duration_out = 0; } pclose(fp); return FFMPEG_OK; }