Add Avisynth video encoding workflow

This commit is contained in:
ajp_anton
2026-07-21 00:41:00 +00:00
parent f50f465465
commit 49296d6f45
37 changed files with 9487 additions and 76 deletions
+853
View File
@@ -0,0 +1,853 @@
#ifdef _WIN32
#define AVSC_NO_DECLSPEC
#include <fcntl.h>
#include <io.h>
#include <windows.h>
#endif
#include <avisynth_c.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifdef _WIN32
static AVS_Library* avs_library = NULL;
#define avs_clip_get_error avs_library->avs_clip_get_error
#define avs_create_script_environment avs_library->avs_create_script_environment
#define avs_delete_script_environment avs_library->avs_delete_script_environment
#define avs_get_audio avs_library->avs_get_audio
#define avs_get_frame avs_library->avs_get_frame
#define avs_get_frame_props_ro avs_library->avs_get_frame_props_ro
#define avs_get_height_p avs_library->avs_get_height_p
#define avs_get_pitch_p avs_library->avs_get_pitch_p
#define avs_get_read_ptr_p avs_library->avs_get_read_ptr_p
#define avs_get_row_size_p avs_library->avs_get_row_size_p
#define avs_get_video_info avs_library->avs_get_video_info
#define avs_invoke avs_library->avs_invoke
#define avs_bits_per_component avs_library->avs_bits_per_component
#define avs_is_420 avs_library->avs_is_420
#define avs_is_422 avs_library->avs_is_422
#define avs_is_444 avs_library->avs_is_444
#define avs_is_yv12 avs_library->avs_is_yv12
#define avs_is_yv16 avs_library->avs_is_yv16
#define avs_is_yv24 avs_library->avs_is_yv24
#define avs_prop_get_int avs_library->avs_prop_get_int
#define avs_release_clip avs_library->avs_release_clip
#define avs_release_value avs_library->avs_release_value
#define avs_release_video_frame avs_library->avs_release_video_frame
#define avs_set_var avs_library->avs_set_var
#define avs_take_clip avs_library->avs_take_clip
#endif
enum RunnerMode {
MODE_VALIDATE,
MODE_VALIDATE_REAL,
MODE_Y4M,
MODE_Y4M_SKIP_DROP,
MODE_WAV,
MODE_CHECK_SOURCE,
};
#define MBT_AVISYNTH_ENV_VERSION 8
static void usage(void) {
fprintf(stderr, "usage: mbt_avs_runner --validate script.avs [frames.csv]\n");
fprintf(stderr, " mbt_avs_runner --validate-real script.avs [frames.csv]\n");
fprintf(stderr, " mbt_avs_runner --check-source expected_source_id script.avs\n");
fprintf(stderr, " mbt_avs_runner --y4m script.avs\n");
fprintf(stderr, " mbt_avs_runner --y4m-skip-drop fps_num fps_den script.avs\n");
fprintf(stderr, " mbt_avs_runner --wav script.avs\n");
}
static int is_validation_mode(enum RunnerMode mode) {
return mode == MODE_VALIDATE || mode == MODE_VALIDATE_REAL;
}
static int is_y4m_mode(enum RunnerMode mode) {
return mode == MODE_Y4M || mode == MODE_Y4M_SKIP_DROP;
}
static int configure_binary_stdout(enum RunnerMode mode) {
#ifdef _WIN32
if (
(mode == MODE_Y4M || mode == MODE_Y4M_SKIP_DROP || mode == MODE_WAV)
&& _setmode(_fileno(stdout), _O_BINARY) == -1
) {
fprintf(stderr, "failed to set binary stdout mode\n");
return 0;
}
#else
(void)mode;
#endif
return 1;
}
static int set_validation_blank_source(
AVS_ScriptEnvironment* env,
enum RunnerMode mode
) {
if (!is_validation_mode(mode)) {
return 1;
}
AVS_Value value = avs_new_value_bool(mode == MODE_VALIDATE);
if (!avs_set_var(env, "mbt_validation_blank", value)) {
fprintf(stderr, "failed to set validation source mode\n");
return 0;
}
return 1;
}
static double now_seconds(void) {
#ifdef _WIN32
static LARGE_INTEGER frequency;
LARGE_INTEGER counter;
if (frequency.QuadPart == 0) {
QueryPerformanceFrequency(&frequency);
}
QueryPerformanceCounter(&counter);
return (double)counter.QuadPart / (double)frequency.QuadPart;
#else
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (double)ts.tv_sec + (double)ts.tv_nsec / 1000000000.0;
#endif
}
static void write_progress(
const char* kind,
int processed,
int total,
double elapsed,
double eta
) {
double percent = total > 0 ? (100.0 * (double)processed / (double)total) : 100.0;
fprintf(
stderr,
"%s,%d,%d,%.2f,%.3f,%.3f\n",
kind,
processed,
total,
percent,
elapsed,
eta
);
fflush(stderr);
}
static FILE* open_validation_log(const char* validation_csv_path) {
if (!validation_csv_path) {
return 0;
}
size_t length = strlen(validation_csv_path) + strlen(".runner.log") + 1;
char* path = (char*)malloc(length);
if (!path) {
return 0;
}
snprintf(path, length, "%s.runner.log", validation_csv_path);
FILE* log = fopen(path, "wb");
free(path);
return log;
}
static void log_checkpoint(FILE* log, const char* message) {
if (!log) {
return;
}
fputs(message, log);
fputc('\n', log);
fflush(log);
}
static void log_checkpoint_int(FILE* log, const char* message, int value) {
if (!log) {
return;
}
fprintf(log, "%s%d\n", message, value);
fflush(log);
}
static void log_runner_arch(FILE* log) {
#if defined(_WIN64)
log_checkpoint(log, "runner: windows 64-bit");
#elif defined(_WIN32)
log_checkpoint(log, "runner: windows 32-bit");
#else
log_checkpoint(log, "runner: non-windows");
#endif
}
static AVS_Value initialized_string_value(const char* value) {
AVS_Value avs_value = avs_void;
avs_value.type = 's';
avs_value.array_size = 0;
avs_value.d.string = value;
return avs_value;
}
#ifdef _WIN32
static void log_loaded_dll_path(FILE* log, const char* dll_name) {
if (!log) {
return;
}
HMODULE module = GetModuleHandleA(dll_name);
if (!module) {
fprintf(log, "dll: %s loaded, path unavailable\n", dll_name);
fflush(log);
return;
}
char path[MAX_PATH];
DWORD length = GetModuleFileNameA(module, path, sizeof(path));
if (length == 0 || length >= sizeof(path)) {
fprintf(log, "dll: %s loaded, path unavailable\n", dll_name);
fflush(log);
return;
}
fprintf(log, "dll: %s\n", path);
fflush(log);
}
#endif
static int require_frame_property_api(FILE* log) {
#ifdef _WIN32
if (
avs_get_frame_props_ro
&& avs_prop_get_int
) {
return 1;
}
log_checkpoint(log, "failed: frame property API unavailable");
fprintf(stderr, "loaded AviSynth does not expose the frame property C API required by media-batch-tools\n");
return 0;
#else
(void)log;
return 1;
#endif
}
static void write_plane(const AVS_VideoFrame* frame, int plane) {
const unsigned char* ptr = avs_get_read_ptr_p(frame, plane);
int pitch = avs_get_pitch_p(frame, plane);
int row_size = avs_get_row_size_p(frame, plane);
int height = avs_get_height_p(frame, plane);
for (int y = 0; y < height; y++) {
fwrite(ptr + y * pitch, 1, row_size, stdout);
}
}
static int video_bit_depth(const AVS_VideoInfo* vi) {
#ifdef _WIN32
if (!avs_bits_per_component) {
return 8;
}
#endif
int depth = avs_bits_per_component(vi);
return depth > 0 ? depth : 8;
}
static const char* y4m_chroma_tag(int family, int bit_depth) {
if (family == 420) {
if (bit_depth <= 8) return "C420jpeg";
if (bit_depth == 10) return "C420p10";
if (bit_depth == 12) return "C420p12";
if (bit_depth == 14) return "C420p14";
if (bit_depth == 16) return "C420p16";
}
if (family == 422) {
if (bit_depth <= 8) return "C422";
if (bit_depth == 10) return "C422p10";
if (bit_depth == 12) return "C422p12";
if (bit_depth == 14) return "C422p14";
if (bit_depth == 16) return "C422p16";
}
if (family == 444) {
if (bit_depth <= 8) return "C444";
if (bit_depth == 10) return "C444p10";
if (bit_depth == 12) return "C444p12";
if (bit_depth == 14) return "C444p14";
if (bit_depth == 16) return "C444p16";
}
return 0;
}
static const char* chroma_tag(const AVS_VideoInfo* vi) {
int depth = video_bit_depth(vi);
#ifdef _WIN32
if (avs_is_420 && avs_is_420(vi)) return y4m_chroma_tag(420, depth);
if (avs_is_422 && avs_is_422(vi)) return y4m_chroma_tag(422, depth);
if (avs_is_444 && avs_is_444(vi)) return y4m_chroma_tag(444, depth);
#else
if (avs_is_420(vi)) return y4m_chroma_tag(420, depth);
if (avs_is_422(vi)) return y4m_chroma_tag(422, depth);
if (avs_is_444(vi)) return y4m_chroma_tag(444, depth);
#endif
if (avs_is_yv12(vi)) return y4m_chroma_tag(420, 8);
if (avs_is_yv16(vi)) return y4m_chroma_tag(422, 8);
if (avs_is_yv24(vi)) return y4m_chroma_tag(444, 8);
return 0;
}
static void write_le16(uint16_t value) {
unsigned char bytes[2];
bytes[0] = (unsigned char)(value & 0xff);
bytes[1] = (unsigned char)((value >> 8) & 0xff);
fwrite(bytes, 1, 2, stdout);
}
static void write_le32(uint32_t value) {
unsigned char bytes[4];
bytes[0] = (unsigned char)(value & 0xff);
bytes[1] = (unsigned char)((value >> 8) & 0xff);
bytes[2] = (unsigned char)((value >> 16) & 0xff);
bytes[3] = (unsigned char)((value >> 24) & 0xff);
fwrite(bytes, 1, 4, stdout);
}
static int wav_format_tag(const AVS_VideoInfo* vi) {
return avs_sample_type(vi) == AVS_SAMPLE_FLOAT ? 3 : 1;
}
static int wav_bits_per_sample(const AVS_VideoInfo* vi) {
return avs_bytes_per_channel_sample(vi) * 8;
}
static int read_int_frame_prop(
AVS_ScriptEnvironment* env,
const AVS_VideoFrame* frame,
const char* name,
int64_t* value_out
);
static int write_wav(AVS_Clip* clip, const AVS_VideoInfo* vi) {
if (!avs_has_audio(vi)) {
fprintf(stderr, "script has no audio\n");
return 6;
}
if (
avs_sample_type(vi) != AVS_SAMPLE_INT16
&& avs_sample_type(vi) != AVS_SAMPLE_INT24
&& avs_sample_type(vi) != AVS_SAMPLE_INT32
&& avs_sample_type(vi) != AVS_SAMPLE_FLOAT
) {
fprintf(stderr, "unsupported Avisynth audio sample type for WAV output: %d\n", avs_sample_type(vi));
return 7;
}
int channels = avs_audio_channels(vi);
int sample_rate = avs_samples_per_second(vi);
int bytes_per_sample = avs_bytes_per_audio_sample(vi);
int bits_per_sample = wav_bits_per_sample(vi);
int64_t total_samples = vi->num_audio_samples;
int64_t data_size_64 = total_samples * bytes_per_sample;
if (data_size_64 > 0xffffffffLL - 36) {
fprintf(stderr, "WAV output would exceed RIFF size limit\n");
return 8;
}
uint32_t data_size = (uint32_t)data_size_64;
uint32_t byte_rate = (uint32_t)(sample_rate * bytes_per_sample);
uint16_t block_align = (uint16_t)bytes_per_sample;
fwrite("RIFF", 1, 4, stdout);
write_le32(36 + data_size);
fwrite("WAVE", 1, 4, stdout);
fwrite("fmt ", 1, 4, stdout);
write_le32(16);
write_le16((uint16_t)wav_format_tag(vi));
write_le16((uint16_t)channels);
write_le32((uint32_t)sample_rate);
write_le32(byte_rate);
write_le16(block_align);
write_le16((uint16_t)bits_per_sample);
fwrite("data", 1, 4, stdout);
write_le32(data_size);
const int64_t chunk_samples = 65536;
unsigned char* buffer = (unsigned char*)malloc((size_t)(chunk_samples * bytes_per_sample));
if (!buffer) {
fprintf(stderr, "failed to allocate audio buffer\n");
return 10;
}
for (int64_t start = 0; start < total_samples; start += chunk_samples) {
int64_t count = total_samples - start;
if (count > chunk_samples) {
count = chunk_samples;
}
int byte_count = (int)(count * bytes_per_sample);
int error = avs_get_audio(clip, buffer, start, count);
if (error) {
const char* err = avs_clip_get_error(clip);
fprintf(stderr, "failed to get audio at sample %lld%s%s\n", (long long)start, err ? ": " : "", err ? err : "");
free(buffer);
return 9;
}
fwrite(buffer, 1, byte_count, stdout);
}
free(buffer);
return 0;
}
static int frame_is_marked_drop(AVS_ScriptEnvironment* env, const AVS_VideoFrame* frame) {
int64_t value = 0;
return read_int_frame_prop(env, frame, "mbt_drop_frame", &value) == 1 && value != 0;
}
static int check_source_id(
AVS_ScriptEnvironment* env,
AVS_Clip* clip,
const AVS_VideoInfo* vi,
int64_t expected_source_id
) {
if (vi->num_frames <= 0) {
fprintf(stderr, "script has no video frames\n");
return 13;
}
AVS_VideoFrame* frame = avs_get_frame(clip, 0);
if (!frame) {
const char* err = avs_clip_get_error(clip);
fprintf(stderr, "failed to get frame 0%s%s\n", err ? ": " : "", err ? err : "");
return 5;
}
int64_t source_id = 0;
int status = read_int_frame_prop(env, frame, "mbt_source_id", &source_id);
avs_release_video_frame(frame);
if (status < 0) {
fprintf(stderr, "failed to read mbt_source_id at frame 0\n");
return 12;
}
if (status == 0) {
fprintf(stderr, "frame 0 is missing mbt_source_id\n");
return 14;
}
if (source_id != expected_source_id) {
fprintf(
stderr,
"frame 0 has mbt_source_id %lld, expected %lld\n",
(long long)source_id,
(long long)expected_source_id
);
return 15;
}
printf("source_id=%lld\n", (long long)source_id);
fflush(stdout);
return 0;
}
static int read_int_frame_prop(
AVS_ScriptEnvironment* env,
const AVS_VideoFrame* frame,
const char* name,
int64_t* value_out
) {
const AVS_Map* props = avs_get_frame_props_ro(env, frame);
if (!props) {
return 0;
}
int error = AVS_GETPROPERROR_SUCCESS;
int64_t value = avs_prop_get_int(env, props, name, 0, &error);
if (error == AVS_GETPROPERROR_UNSET || error == AVS_GETPROPERROR_INDEX) {
return 0;
}
if (error != AVS_GETPROPERROR_SUCCESS) {
return -1;
}
*value_out = value;
return 1;
}
static int write_validation_row(
FILE* csv,
AVS_ScriptEnvironment* env,
const AVS_VideoFrame* frame,
int output_frame
) {
int64_t source_id = 0;
int64_t source_frame = 0;
int64_t drop_frame = 0;
int64_t matrix = 0;
int source_id_status = read_int_frame_prop(env, frame, "mbt_source_id", &source_id);
int source_frame_status = read_int_frame_prop(env, frame, "mbt_source_frame", &source_frame);
int drop_frame_status = read_int_frame_prop(env, frame, "mbt_drop_frame", &drop_frame);
int matrix_status = read_int_frame_prop(env, frame, "_Matrix", &matrix);
if (source_id_status < 0 || source_frame_status < 0 || drop_frame_status < 0 || matrix_status < 0) {
fprintf(stderr, "failed to read media-batch-tools frame properties at frame %d\n", output_frame);
return 12;
}
fprintf(csv, "%d,", output_frame);
if (source_id_status > 0) {
fprintf(csv, "%lld", (long long)source_id);
}
fputc(',', csv);
if (source_frame_status > 0) {
fprintf(csv, "%lld", (long long)source_frame);
}
fprintf(csv, ",%lld,", drop_frame_status > 0 ? (long long)drop_frame : 0LL);
if (matrix_status > 0) {
fprintf(csv, "%lld", (long long)matrix);
}
fputc('\n', csv);
return 0;
}
static int render_clip(
AVS_ScriptEnvironment* env,
AVS_Clip* clip,
const AVS_VideoInfo* vi,
enum RunnerMode mode,
unsigned int y4m_fps_num,
unsigned int y4m_fps_den,
const char* validation_csv_path,
FILE* diagnostic_log
) {
if (mode == MODE_WAV) {
return write_wav(clip, vi);
}
const char* chroma = 0;
FILE* validation_csv = 0;
double* progress_times = 0;
double progress_start = 0.0;
double last_progress = 0.0;
int report_progress = is_validation_mode(mode) || is_y4m_mode(mode);
const char* progress_kind = is_validation_mode(mode)
? "mbt_progress"
: "mbt_render";
if (is_validation_mode(mode)) {
const char* clip_chroma = chroma_tag(vi);
log_checkpoint(diagnostic_log, "render: clip_info");
printf(
"clip_info,width=%d,height=%d,chroma=%s,bit_depth=%d,frames=%d,fps_num=%u,fps_den=%u,has_audio=%d,audio_rate=%d,audio_channels=%d,audio_samples=%lld\n",
vi->width,
vi->height,
clip_chroma ? clip_chroma : "unknown",
video_bit_depth(vi),
vi->num_frames,
vi->fps_numerator,
vi->fps_denominator,
avs_has_audio(vi) ? 1 : 0,
vi->audio_samples_per_second,
vi->nchannels,
(long long)vi->num_audio_samples
);
fflush(stdout);
if (validation_csv_path) {
log_checkpoint(diagnostic_log, "render: open validation csv");
validation_csv = fopen(validation_csv_path, "wb");
if (!validation_csv) {
fprintf(stderr, "failed to open validation CSV for writing: %s\n", validation_csv_path);
return 11;
}
fputs("output_frame,source_id,source_frame,drop_frame,matrix\n", validation_csv);
fflush(validation_csv);
}
}
if (report_progress) {
progress_times = (double*)calloc((size_t)vi->num_frames + 1, sizeof(double));
progress_start = now_seconds();
last_progress = progress_start;
if (progress_times) {
progress_times[0] = progress_start;
}
write_progress(progress_kind, 0, vi->num_frames, 0.0, 0.0);
}
if (mode == MODE_Y4M || mode == MODE_Y4M_SKIP_DROP) {
chroma = chroma_tag(vi);
if (!chroma) {
fprintf(stderr, "unsupported Avisynth pixel format for Y4M output: %d\n", vi->pixel_type);
free(progress_times);
return 4;
}
fprintf(
stdout,
"YUV4MPEG2 W%d H%d F%u:%u Ip A0:0 %s\n",
vi->width,
vi->height,
y4m_fps_num,
y4m_fps_den,
chroma
);
fflush(stdout);
}
for (int n = 0; n < vi->num_frames; n++) {
if (is_validation_mode(mode) && (n == 0 || n == vi->num_frames - 1 || n % 1000 == 0)) {
log_checkpoint_int(diagnostic_log, "render: frame ", n);
}
AVS_VideoFrame* frame = avs_get_frame(clip, n);
if (!frame) {
const char* err = avs_clip_get_error(clip);
fprintf(stderr, "failed to get frame %d%s%s\n", n, err ? ": " : "", err ? err : "");
return 5;
}
int skip_output = mode == MODE_Y4M_SKIP_DROP && frame_is_marked_drop(env, frame);
if (!skip_output && is_y4m_mode(mode)) {
fputs("FRAME\n", stdout);
write_plane(frame, AVS_PLANAR_Y);
write_plane(frame, AVS_PLANAR_U);
write_plane(frame, AVS_PLANAR_V);
}
if (is_validation_mode(mode) && validation_csv) {
int csv_status = write_validation_row(validation_csv, env, frame, n);
if (csv_status != 0) {
fclose(validation_csv);
free(progress_times);
avs_release_video_frame(frame);
return csv_status;
}
}
avs_release_video_frame(frame);
if (report_progress) {
int processed = n + 1;
double current_time = now_seconds();
if (progress_times) {
progress_times[processed] = current_time;
}
if (processed == vi->num_frames || current_time - last_progress >= 0.5) {
int window = processed / 2;
int window_start = processed - window;
double window_elapsed = 0.0;
double eta = 0.0;
if (window > 0 && progress_times) {
window_elapsed = current_time - progress_times[window_start];
eta = window_elapsed * (double)(vi->num_frames - processed) / (double)window;
} else if (processed > 0) {
window_elapsed = current_time - progress_start;
eta = window_elapsed * (double)(vi->num_frames - processed) / (double)processed;
}
write_progress(
progress_kind,
processed,
vi->num_frames,
current_time - progress_start,
eta
);
last_progress = current_time;
}
}
}
if (is_validation_mode(mode)) {
if (validation_csv) {
log_checkpoint(diagnostic_log, "render: close validation csv");
fclose(validation_csv);
}
log_checkpoint(diagnostic_log, "render: complete");
printf("rendered=%d\n", vi->num_frames);
fflush(stdout);
}
free(progress_times);
return 0;
}
int main(int argc, char** argv) {
enum RunnerMode mode;
const char* script_path;
const char* validation_csv_path = 0;
FILE* diagnostic_log = 0;
unsigned int y4m_fps_num = 0;
unsigned int y4m_fps_den = 0;
int64_t expected_source_id = 0;
if (argc != 3 && argc != 4 && argc != 5) {
usage();
return 2;
}
if (strcmp(argv[1], "--validate") == 0) {
if (argc != 3 && argc != 4) {
usage();
return 2;
}
mode = MODE_VALIDATE;
if (argc == 4) {
validation_csv_path = argv[3];
}
} else if (strcmp(argv[1], "--validate-real") == 0) {
if (argc != 3 && argc != 4) {
usage();
return 2;
}
mode = MODE_VALIDATE_REAL;
if (argc == 4) {
validation_csv_path = argv[3];
}
} else if (strcmp(argv[1], "--check-source") == 0) {
if (argc != 4) {
usage();
return 2;
}
mode = MODE_CHECK_SOURCE;
expected_source_id = (int64_t)strtoll(argv[2], 0, 10);
} else if (strcmp(argv[1], "--y4m") == 0) {
if (argc != 3) {
usage();
return 2;
}
mode = MODE_Y4M;
} else if (strcmp(argv[1], "--y4m-skip-drop") == 0) {
if (argc != 5) {
usage();
return 2;
}
mode = MODE_Y4M_SKIP_DROP;
y4m_fps_num = (unsigned int)strtoul(argv[2], 0, 10);
y4m_fps_den = (unsigned int)strtoul(argv[3], 0, 10);
if (y4m_fps_num == 0 || y4m_fps_den == 0) {
fprintf(stderr, "Y4M fps numerator and denominator must be positive\n");
return 2;
}
} else if (strcmp(argv[1], "--wav") == 0) {
if (argc != 3) {
usage();
return 2;
}
mode = MODE_WAV;
} else {
usage();
return 2;
}
if (!configure_binary_stdout(mode)) {
return 2;
}
script_path = is_validation_mode(mode) ? argv[2] : argv[argc - 1];
diagnostic_log = open_validation_log(validation_csv_path);
log_checkpoint(diagnostic_log, "start");
log_runner_arch(diagnostic_log);
log_checkpoint(diagnostic_log, script_path);
#ifdef _WIN32
log_checkpoint(diagnostic_log, "load: AviSynth.dll");
avs_library = avs_load_library();
if (!avs_library) {
log_checkpoint(diagnostic_log, "failed: AviSynth.dll");
fprintf(stderr, "failed to load AviSynth.dll. Install 64-bit AviSynth+ and make sure AviSynth.dll can be found.\n");
if (diagnostic_log) {
fclose(diagnostic_log);
}
return 2;
}
log_loaded_dll_path(diagnostic_log, "AviSynth.dll");
#endif
log_checkpoint_int(diagnostic_log, "create: script environment v", MBT_AVISYNTH_ENV_VERSION);
AVS_ScriptEnvironment* env = avs_create_script_environment(MBT_AVISYNTH_ENV_VERSION);
if (!env) {
log_checkpoint(diagnostic_log, "failed: script environment");
fprintf(stderr, "failed to create Avisynth environment\n");
#ifdef _WIN32
avs_free_library(avs_library);
#endif
if (diagnostic_log) {
fclose(diagnostic_log);
}
return 2;
}
if (!require_frame_property_api(diagnostic_log)) {
avs_delete_script_environment(env);
#ifdef _WIN32
avs_free_library(avs_library);
#endif
if (diagnostic_log) {
fclose(diagnostic_log);
}
return 2;
}
if (!set_validation_blank_source(env, mode)) {
avs_delete_script_environment(env);
#ifdef _WIN32
avs_free_library(avs_library);
#endif
if (diagnostic_log) {
fclose(diagnostic_log);
}
return 2;
}
log_checkpoint(diagnostic_log, "import: script");
AVS_Value import_arg = initialized_string_value(script_path);
AVS_Value result = avs_invoke(env, "Import", import_arg, 0);
if (avs_is_error(result)) {
log_checkpoint(diagnostic_log, "failed: import");
fprintf(stderr, "%s\n", avs_as_string(result));
avs_release_value(result);
avs_delete_script_environment(env);
#ifdef _WIN32
avs_free_library(avs_library);
#endif
if (diagnostic_log) {
fclose(diagnostic_log);
}
return 1;
}
if (!avs_is_clip(result)) {
log_checkpoint(diagnostic_log, "failed: script did not return clip");
fprintf(stderr, "script did not return a clip\n");
avs_release_value(result);
avs_delete_script_environment(env);
#ifdef _WIN32
avs_free_library(avs_library);
#endif
if (diagnostic_log) {
fclose(diagnostic_log);
}
return 3;
}
log_checkpoint(diagnostic_log, "take: clip");
AVS_Clip* clip = avs_take_clip(result, env);
avs_release_value(result);
log_checkpoint(diagnostic_log, "get: video info");
const AVS_VideoInfo* vi = avs_get_video_info(clip);
log_checkpoint_int(diagnostic_log, "info: frames ", vi->num_frames);
if (y4m_fps_num == 0) {
y4m_fps_num = vi->fps_numerator;
}
if (y4m_fps_den == 0) {
y4m_fps_den = vi->fps_denominator;
}
int status = mode == MODE_CHECK_SOURCE
? check_source_id(env, clip, vi, expected_source_id)
: render_clip(
env,
clip,
vi,
mode,
y4m_fps_num,
y4m_fps_den,
validation_csv_path,
diagnostic_log
);
#ifdef _WIN32
/*
* Some Windows AviSynth/plugin combinations can crash during final
* teardown after all frames have rendered. Let the OS reclaim process
* resources so validation/encoding results are not lost to cleanup.
*/
fflush(stdout);
fflush(stderr);
if (diagnostic_log) {
log_checkpoint_int(diagnostic_log, "exit: ", status);
fclose(diagnostic_log);
}
ExitProcess((UINT)status);
#else
avs_release_clip(clip);
avs_delete_script_environment(env);
#endif
if (diagnostic_log) {
log_checkpoint_int(diagnostic_log, "exit: ", status);
fclose(diagnostic_log);
}
return status;
}