854 lines
26 KiB
C
854 lines
26 KiB
C
#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;
|
|
}
|