#ifdef _WIN32 #define AVSC_NO_DECLSPEC #include #include #include #endif #include #include #include #include #include #include #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; }