From f2996b0686a2b1f198152f2628f024373ef35e24 Mon Sep 17 00:00:00 2001 From: seiya-git Date: Tue, 9 Dec 2025 15:06:13 +0300 Subject: [PATCH] Copy video frame data to a new writable frame before ASS rendering --- docs/CHANGELOG.md | 7 ++- src/assrender.c | 20 +++---- src/assrender.h | 54 +++++++++++-------- src/include/avs/capi.h | 10 ++-- src/render.c | 118 +++++++++++++++++++++++++++++++---------- 5 files changed, 139 insertions(+), 70 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 5832751..92d3728 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,8 @@ ## Change log -### 0.36.0-dev.4 (20251205) +### 0.36.0-dev.x (20251209) +* Fix hinting +* Fix ass_set_storage_size not be called * Switch submodule build system to meson * Update avisynth(plus) headers to v12 * Update libass to 0.17.4 @@ -8,6 +10,7 @@ * Add frame size parameters * Add set_default_storage_size boolean * Use frame properties if they available for "YCbCr Matrix: None" +* Work with frame copy instead of just modifying it ### 0.35 (20210304) * Windows MSVC: Update to libass v0.15 @@ -21,7 +24,7 @@ * Fix: possible crash on initializing phase (buffer overread, linux crashed, Windows was just lucky) ### 0.34 (20210301) -* Fix the fix: revert matrix change made in 0.33 +* Fix the fix: revert matrix change made in 0.33 * Fix: Check matrix from .ASS file "YCbCr Matrix:" section besides "Video Colorspace:" Recognized values are "tv.601" and "tv.709" diff --git a/src/assrender.c b/src/assrender.c index 78a75d5..0940de6 100644 --- a/src/assrender.c +++ b/src/assrender.c @@ -57,13 +57,6 @@ static const char* detect_bom(const char* buf, const size_t bufsize) { return "UTF-8"; } -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include -#else -#include -#endif - #ifdef _WIN32 static wchar_t *utf8_to_utf16le(const char *data) { const int out_size = MultiByteToWideChar(CP_UTF8, 0, data, -1, NULL, 0); @@ -216,7 +209,6 @@ AVS_Value AVSC_CC assrender_create(AVS_ScriptEnvironment* env, AVS_Value args, AVS_FilterInfo* fi; AVS_Clip* c = avs_new_c_filter(env, &fi, avs_array_elt(args, 0), 1); const AVS_VideoInfo *vi = &fi->vi; - char e[250]; const char* f = avs_as_string(avs_array_elt(args, 1)); const char* vfr = avs_as_string(avs_array_elt(args, 2)); @@ -318,7 +310,7 @@ AVS_Value AVSC_CC assrender_create(AVS_ScriptEnvironment* env, AVS_Value args, if (!strcasecmp(strrchr(f, '.'), ".srt")) { FILE* fp = open_utf8_filename(f, "r"); if (!fp) { - sprintf(e, "AssRender: input file '%s' does not exist or is not a regular file", f); + const char* e = avs_sprintf(env, "AssRender: input file '%s' does not exist or is not a regular file", f); v = avs_new_value_error(e); avs_release_clip(c); return v; @@ -330,7 +322,7 @@ AVS_Value AVSC_CC assrender_create(AVS_ScriptEnvironment* env, AVS_Value args, fp = open_utf8_filename(f, "rb"); if (!fp) { - sprintf(e, "AssRender: input file '%s' does not exist or is not a regular file", f); + const char* e = avs_sprintf(env, "AssRender: input file '%s' does not exist or is not a regular file", f); v = avs_new_value_error(e); avs_release_clip(c); return v; @@ -338,7 +330,7 @@ AVS_Value AVSC_CC assrender_create(AVS_ScriptEnvironment* env, AVS_Value args, buf = read_file_bytes(fp, &bufsize); if (!buf) { - sprintf(e, "AssRender: unable to read '%s'", f); + const char* e = avs_sprintf(env, "AssRender: unable to read '%s'", f); v = avs_new_value_error(e); avs_release_clip(c); return v; @@ -358,7 +350,7 @@ AVS_Value AVSC_CC assrender_create(AVS_ScriptEnvironment* env, AVS_Value args, } if (!ass) { - sprintf(e, "AssRender: unable to parse '%s'", f); + const char* e = avs_sprintf(env, "AssRender: unable to parse '%s'", f); v = avs_new_value_error(e); avs_release_clip(c); return v; @@ -371,7 +363,7 @@ AVS_Value AVSC_CC assrender_create(AVS_ScriptEnvironment* env, AVS_Value args, FILE* fh = open_utf8_filename(vfr, "r"); if (!fh) { - sprintf(e, "AssRender: could not read timecodes file '%s'", vfr); + const char* e = avs_sprintf(env, "AssRender: could not read timecodes file '%s'", vfr); v = avs_new_value_error(e); avs_release_clip(c); return v; @@ -380,7 +372,7 @@ AVS_Value AVSC_CC assrender_create(AVS_ScriptEnvironment* env, AVS_Value args, data->isvfr = 1; if (fscanf(fh, "# timecode format v%d", &ver) != 1) { - sprintf(e, "AssRender: invalid timecodes file '%s'", vfr); + const char* e = avs_sprintf(env, "AssRender: invalid timecodes file '%s'", vfr); v = avs_new_value_error(e); avs_release_clip(c); return v; diff --git a/src/assrender.h b/src/assrender.h index fa40b7c..a53a722 100644 --- a/src/assrender.h +++ b/src/assrender.h @@ -9,42 +9,50 @@ #include #include #include + +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN +# include +#else +# include +#endif + #include "avs/config.h" #ifdef AVS_WINDOWS -#include "avisynth_c.h" +# include "avisynth_c.h" #else -#include +# include #endif #if defined(_MSC_VER) -#define __NO_ISOCEXT -#define __NO_INLINE__ +# define __NO_ISOCEXT +# define __NO_INLINE__ -#define strcasecmp _stricmp -#define atoll _atoi64 +# define strcasecmp _stricmp +# define atoll _atoi64 #endif typedef struct { - // premultiplied coefficients for integer scaled arithmetics - int y_r, y_g, y_b; - int u_r, u_g, u_b; - int v_r, v_g, v_b; - int offset_y; - bool valid; + // premultiplied coefficients for integer scaled arithmetics + int y_r, y_g, y_b; + int u_r, u_g, u_b; + int v_r, v_g, v_b; + int offset_y; + bool valid; } ConversionMatrix; typedef enum { - MATRIX_NONE = 0, - MATRIX_BT601, - MATRIX_PC601, - MATRIX_BT709, - MATRIX_PC709, - MATRIX_PC2020, - MATRIX_BT2020, - MATRIX_TVFCC, - MATRIX_PCFCC, - MATRIX_TV240M, - MATRIX_PC240M + MATRIX_NONE = 0, + MATRIX_BT601, + MATRIX_PC601, + MATRIX_BT709, + MATRIX_PC709, + MATRIX_PC2020, + MATRIX_BT2020, + MATRIX_TVFCC, + MATRIX_PCFCC, + MATRIX_TV240M, + MATRIX_PC240M } matrix_type; typedef void (* fPixel)(uint8_t** sub_img, uint8_t** data, uint32_t* pitch, uint32_t width, uint32_t height); diff --git a/src/include/avs/capi.h b/src/include/avs/capi.h index 7629296..b598ce9 100644 --- a/src/include/avs/capi.h +++ b/src/include/avs/capi.h @@ -42,10 +42,12 @@ #endif #endif -#ifdef __cplusplus -# define EXTERN_C extern "C" -#else -# define EXTERN_C +#ifndef EXTERN_C +# ifdef __cplusplus +# define EXTERN_C extern "C" +# else +# define EXTERN_C +# endif #endif #ifdef AVS_WINDOWS diff --git a/src/render.c b/src/render.c index d6583b6..5bb2e0f 100644 --- a/src/render.c +++ b/src/render.c @@ -893,58 +893,122 @@ AVS_VideoFrame* AVSC_CC assrender_get_frame(AVS_FilterInfo* p, int n) udata* ud = (udata*)p->user_data; ASS_Image* img; AVS_VideoFrame* src; - + AVS_VideoFrame* dst; int64_t ts; int changed; + // 1) Get upstream frame src = avs_get_frame(p->child, n); - avs_make_writable(p->env, &src); + // 2) Allocate a new writable destination frame + dst = avs_new_video_frame_a(p->env, &p->vi, AVS_FRAME_ALIGN); + if (!dst) { + avs_release_video_frame(src); + return 0; + } + + // 3) Copy src -> dst (real pixel copy), then free src + + if (avs_is_planar(&p->vi)) { + if (avs_is_rgb(&p->vi)) { + // Planar RGB: R, G, B planes + const int planes[3] = { AVS_PLANAR_R, AVS_PLANAR_G, AVS_PLANAR_B }; + for (int i = 0; i < 3; ++i) { + const uint8_t* srcp = avs_get_read_ptr_p(src, planes[i]); + int src_pitch = avs_get_pitch_p(src, planes[i]); + uint8_t* dstp = avs_get_write_ptr_p(dst, planes[i]); + int dst_pitch = avs_get_pitch_p(dst, planes[i]); + int row_size = avs_get_row_size_p(src, planes[i]); + int height = avs_get_height_p(src, planes[i]); + avs_bit_blt(p->env, dstp, dst_pitch, srcp, src_pitch, row_size, height); + } + } + else { + // Planar YUV: Y, U, V planes + const int planes[3] = { AVS_PLANAR_Y, AVS_PLANAR_U, AVS_PLANAR_V }; + for (int i = 0; i < 3; ++i) { + const uint8_t* srcp = avs_get_read_ptr_p(src, planes[i]); + int src_pitch = avs_get_pitch_p(src, planes[i]); + uint8_t* dstp = avs_get_write_ptr_p(dst, planes[i]); + int dst_pitch = avs_get_pitch_p(dst, planes[i]); + int row_size = avs_get_row_size_p(src, planes[i]); + int height = avs_get_height_p(src, planes[i]); + avs_bit_blt(p->env, dstp, dst_pitch, srcp, src_pitch, row_size, height); + } + } + } + else { + // Packed formats (e.g. RGB32, YUY2, etc.) + const uint8_t* srcp = avs_get_read_ptr_p(src, AVS_DEFAULT_PLANE); + int src_pitch = avs_get_pitch_p(src, AVS_DEFAULT_PLANE); + uint8_t* dstp = avs_get_write_ptr_p(dst, AVS_DEFAULT_PLANE); + int dst_pitch = avs_get_pitch_p(dst, AVS_DEFAULT_PLANE); + int row_size = avs_get_row_size_p(src, AVS_DEFAULT_PLANE); + int height = avs_get_height_p(src, AVS_DEFAULT_PLANE); + avs_bit_blt(p->env, dstp, dst_pitch, srcp, src_pitch, row_size, height); + } + // Original frame no longer needed locally + avs_release_video_frame(src); + + // 4) Compute timestamp for libass if (!ud->isvfr) { - // it’s a casting party! + // it's a casting party! ts = (int64_t)n * (int64_t)1000 * (int64_t)p->vi.fps_denominator / (int64_t)p->vi.fps_numerator; - } else { + } + else { ts = ud->timestamp[n]; } + // 5) Render ASS img = ass_render_frame(ud->ass_renderer, ud->ass, ts, &changed); if (img) { - uint32_t height, width, pitch[2]; + uint32_t width = p->vi.width; + uint32_t height = p->vi.height; uint8_t* data[3]; + uint32_t pitch[2]; + // 6) Build write pointers for the copied frame (dst) if (avs_is_planar(&p->vi) && !ud->greyscale) { - if (avs_is_rgb(&p->vi)) { - // planar RGB as 444 - data[0] = avs_get_write_ptr_p(src, AVS_PLANAR_R); - data[1] = avs_get_write_ptr_p(src, AVS_PLANAR_G); - data[2] = avs_get_write_ptr_p(src, AVS_PLANAR_B); - pitch[0] = avs_get_pitch_p(src, AVS_DEFAULT_PLANE); - } - else { - data[0] = avs_get_write_ptr_p(src, AVS_PLANAR_Y); - data[1] = avs_get_write_ptr_p(src, AVS_PLANAR_U); - data[2] = avs_get_write_ptr_p(src, AVS_PLANAR_V); - pitch[0] = avs_get_pitch_p(src, AVS_PLANAR_Y); - pitch[1] = avs_get_pitch_p(src, AVS_PLANAR_U); - } + if (avs_is_rgb(&p->vi)) { + // planar RGB as 4:4:4 + data[0] = avs_get_write_ptr_p(dst, AVS_PLANAR_R); + data[1] = avs_get_write_ptr_p(dst, AVS_PLANAR_G); + data[2] = avs_get_write_ptr_p(dst, AVS_PLANAR_B); + pitch[0] = avs_get_pitch_p(dst, AVS_DEFAULT_PLANE); + } + else { + data[0] = avs_get_write_ptr_p(dst, AVS_PLANAR_Y); + data[1] = avs_get_write_ptr_p(dst, AVS_PLANAR_U); + data[2] = avs_get_write_ptr_p(dst, AVS_PLANAR_V); + pitch[0] = avs_get_pitch_p(dst, AVS_PLANAR_Y); + pitch[1] = avs_get_pitch_p(dst, AVS_PLANAR_U); + } } else { - data[0] = avs_get_write_ptr_p(src, AVS_DEFAULT_PLANE); - pitch[0] = avs_get_pitch_p(src, AVS_DEFAULT_PLANE); + data[0] = avs_get_write_ptr_p(dst, AVS_DEFAULT_PLANE); + pitch[0] = avs_get_pitch_p(dst, AVS_DEFAULT_PLANE); } - height = p->vi.height; - width = p->vi.width; - + // 7) Rebuild cached subtitle bitmap if changed if (changed) { - memset(ud->sub_img[0], 0x00, height * width * ud->pixelsize); - ud->f_make_sub_img(img, ud->sub_img, width, ud->bits_per_pixel, ud->rgb_fullscale, &ud->mx); + memset(ud->sub_img[0], 0, height * width * ud->pixelsize); + ud->f_make_sub_img( + img, + ud->sub_img, + width, + ud->bits_per_pixel, + ud->rgb_fullscale, + &ud->mx + ); } + // 8) Blend ASS into the copied frame ud->apply(ud->sub_img, data, pitch, width, height); } - return src; + // 9) Return the modified copy + return dst; } +