#define STRVIEW_IMPLEMENTATION
#include "strview.h"
#include "compiler.h"
#include "parser.h"
#include <errno.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static char* read_file(const char* path) {
FILE* file = fopen(path, "rb");
if (file == NULL) {
return NULL;
}
if (fseek(file, 0, SEEK_END) != 0) {
fclose(file);
return NULL;
}
long length = ftell(file);
if (length < 0 || fseek(file, 0, SEEK_SET) != 0) {
fclose(file);
return NULL;
}
char* text = malloc((size_t)length + 1);
if (text == NULL) {
fclose(file);
return NULL;
}
size_t read_count = fread(text, 1, (size_t)length, file);
bool ok = read_count == (size_t)length && !ferror(file);
fclose(file);
if (!ok) {
free(text);
return NULL;
}
text[length] = '\0';
return text;
}
static void print_error(const char* stage, const Error* error) {
if (error->line > 0) {
fprintf(stderr, "%s:%d: %s\n", stage, error->line, error->message);
} else {
fprintf(stderr, "%s: %s\n", stage, error->message);
}
}
static void usage(FILE* stream, const char* name) {
fprintf(stream, "usage: %s [-n] [--ffmpeg-arg ARG ...] PROGRAM OUTPUT [SCRIPT_ARGUMENT ...]\n", name);
fprintf(stream, " -n compile and print without running ffmpeg\n");
fprintf(stream, " --ffmpeg-arg ARG replace default video/audio encoding options; repeat per argument\n");
}
int main(int argc, char** argv) {
if (argc == 4 && sv_equal(sv_from_cstr(argv[1]), svlit("--internal-subtitle-clip"))) {
char* end = NULL;
errno = 0;
double duration = strtod(argv[3], &end);
Error error = {0};
if (errno != 0 || end == argv[3] || *end != '\0' || !isfinite(duration) || duration < 0 ||
!subtitle_clip_file(argv[2], duration, &error)) {
if (error.message[0] == '\0') snprintf(error.message, sizeof(error.message), "invalid duration");
print_error("subtitle clip", &error);
return 1;
}
return 0;
}
if (argc == 5 &&
sv_equal(sv_from_cstr(argv[1]), svlit("--internal-subtitle-concat-list"))) {
Error error = {0};
if (!subtitle_write_concat_list(argv[2], argv[3], argv[4], &error)) {
print_error("subtitle concat", &error);
return 1;
}
return 0;
}
if (argc == 4 && sv_equal(sv_from_cstr(argv[1]), svlit("--internal-write-file"))) {
Error error = {0};
if (!write_generated_file(argv[2], argv[3], &error)) {
print_error("generated metadata", &error);
return 1;
}
return 0;
}
if (argc == 4 && sv_equal(sv_from_cstr(argv[1]), svlit("--internal-copy-file"))) {
Error error = {0};
if (!copy_generated_file(argv[2], argv[3], &error)) {
print_error("font attachment", &error);
return 1;
}
return 0;
}
if (argc == 3 && sv_equal(sv_from_cstr(argv[1]), svlit("--internal-mkdir"))) {
Error error = {0};
if (!ensure_generated_directory(argv[2], &error)) {
print_error("font directory", &error);
return 1;
}
return 0;
}
const char* executable_name = argv[0];
bool dry_run = false;
size_t ffmpeg_argument_count = 0;
int argument = 1;
while (argument < argc) {
if (sv_equal(sv_from_cstr(argv[argument]), svlit("-n"))) {
dry_run = true;
argument++;
} else if (sv_equal(sv_from_cstr(argv[argument]), svlit("--ffmpeg-arg"))) {
if (argument + 1 >= argc) {
fprintf(stderr, "%s: --ffmpeg-arg requires an argument\n", executable_name);
return 2;
}
argv[ffmpeg_argument_count++] = argv[argument + 1];
argument += 2;
} else if (sv_equal(sv_from_cstr(argv[argument]), svlit("--"))) {
argument++;
break;
} else if (argv[argument][0] == '-') {
fprintf(stderr, "%s: unknown option %s\n", executable_name, argv[argument]);
usage(stderr, executable_name);
return 2;
} else {
break;
}
}
if (argc - argument < 2) {
usage(stderr, executable_name);
return 2;
}
const char* program_path = argv[argument];
const char* output_path = argv[argument + 1];
size_t script_argument_count = (size_t)(argc - argument - 2);
const char* const* script_arguments = (const char* const*)(argv + argument + 2);
char* text = read_file(program_path);
if (text == NULL) {
fprintf(stderr, "%s: cannot read %s: %s\n", executable_name, program_path, strerror(errno));
return 1;
}
Error error = {0};
Program program;
TypedProgram typed;
CompiledCommand command;
ProbeCache probes;
probe_cache_init(&probes, NULL, NULL);
if (!parse_program_with_args(text, script_argument_count, script_arguments, &program, &error)) {
print_error(program_path, &error);
free(text);
probe_cache_destroy(&probes);
return 1;
}
free(text);
if (!typecheck_program(&program, &typed, &error)) {
print_error(program_path, &error);
program_destroy(&program);
probe_cache_destroy(&probes);
return 1;
}
if (!compile_program_with_ffmpeg_args(&program, &typed, &probes, output_path,
ffmpeg_argument_count,
(const char* const*)argv,
&command, &error)) {
print_error(program_path, &error);
typed_program_destroy(&typed);
program_destroy(&program);
probe_cache_destroy(&probes);
return 1;
}
printf("%s\n", command.shell_command);
int exit_code = 0;
if (!dry_run && !execute_command(&command, &exit_code, &error)) {
print_error("ffmpeg", &error);
exit_code = 1;
}
compiled_command_destroy(&command);
typed_program_destroy(&typed);
program_destroy(&program);
probe_cache_destroy(&probes);
return exit_code;
}