diff --git a/docs/BUILD.md b/docs/BUILD.md index 9b75ab7..ca0e2d6 100644 --- a/docs/BUILD.md +++ b/docs/BUILD.md @@ -7,7 +7,7 @@ - set PKG_CONFIG_PATH env `setx PKG_CONFIG_PATH "C:/lib/pkgconfig"` * Clone repos - - Clone `git clone https://github.com/seiya-dev/assrender.git` + - Clone `git clone https://github.com/pinterf/assrender.git` - and clone libass as subdirectory `cd assrender && git clone https://github.com/libass/libass.git` * Prequisite: avisynth.lib versions (x86 and x64) @@ -30,7 +30,7 @@ ### Linux * Clone repo - - git clone https://github.com/seiya-dev/assrender + - git clone https://github.com/pinterf/assrender - cd assrender - cmake -B build -S . - cmake --build build --clean-first diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index bb094ae..d6c67de 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,18 +1,16 @@ ## Change log -### 0.36.0-dev.3 (20251202) -* Use frame properties if they available for "YCbCr Matrix: None" - -### 0.36.0-dev.2 (20251201) -* Restore x86-32 assrender.dll build - -### 0.36.0-dev.1 (20251201) +### 0.36.0-dev.5 (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 * Unicode-safe file reading * Add frame size parameters * Add set_default_storage_size boolean -* Update libass to 0.17.4 +* 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 @@ -26,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/docs/README.md b/docs/README.md index 88c21a4..2d5f613 100644 --- a/docs/README.md +++ b/docs/README.md @@ -81,8 +81,7 @@ Default is to use the ASS script's "YCbCr Matrix" or "Video Colorspace" property Recognized .ASS properties: "TV.601" "TV.709", "PC.601" "PC.709" "TV.240m" "PC.240m" "TV.fcc" "PC.fcc" and "none". -"none" and "guess" decides upon on video resolution: width > 1280 or height > 576 → BT.709, else → BT.601. -When no hint found in ASS script and 'colorspace' parameter is empty then the default is BT.601. +"none" and "guess" determine the colorspace based on the video's `_Matrix` frame property if available. Otherwise, if the resolution is width > 1024 or height > 576, BT.709 is used; otherwise BT.601. ## Build instructions See: [BUILD.md](BUILD.md) diff --git a/src/assrender.c b/src/assrender.c index 3107785..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; @@ -450,13 +442,6 @@ AVS_Value AVSC_CC assrender_create(AVS_ScriptEnvironment* env, AVS_Value args, color_mt = MATRIX_BT2020; } else if (!strcasecmp(tmpcsp, "none") || !strcasecmp(tmpcsp, "guess")) { - /* not yet - * Theoretically only for 10 and 12 bits: - if (fi->vi.width > 1920 || fi->vi.height > 1080) - color_mt = MATRIX_BT2020; - else - */ - int mt_from_props_ok = 0; matrix_type mt_from_props = MATRIX_NONE; @@ -467,10 +452,16 @@ AVS_Value AVSC_CC assrender_create(AVS_ScriptEnvironment* env, AVS_Value args, if (mt_from_props_ok && mt_from_props != MATRIX_NONE) { color_mt = mt_from_props; } else { - if (vi->width >= 1280 || vi->height >= 576) { - color_mt = MATRIX_PC709; + /* not yet + * Theoretically only for 10 and 12 bits: + if (fi->vi.width > 1920 || fi->vi.height > 1080) + color_mt = MATRIX_BT2020; + else + */ + if (vi->width > 1024 || vi->height > 576) { + color_mt = MATRIX_BT709; } else { - color_mt = MATRIX_PC601; + color_mt = MATRIX_BT601; } } } @@ -501,7 +492,6 @@ AVS_Value AVSC_CC assrender_create(AVS_ScriptEnvironment* env, AVS_Value args, return v; } - switch (fi->vi.pixel_type) { case AVS_CS_YV12: 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..ac09afe 100644 --- a/src/include/avs/capi.h +++ b/src/include/avs/capi.h @@ -43,9 +43,9 @@ #endif #ifdef __cplusplus -# define EXTERN_C extern "C" +# define AVS_EXTERN_C extern "C" #else -# define EXTERN_C +# define AVS_EXTERN_C #endif #ifdef AVS_WINDOWS @@ -99,20 +99,20 @@ # else # define AVSC_EXPORT # endif -# define AVSC_API(ret, name) EXTERN_C AVSC_EXPORT ret AVSC_CC name +# define AVSC_API(ret, name) AVS_EXTERN_C AVSC_EXPORT ret AVSC_CC name #else -# define AVSC_EXPORT EXTERN_C -# define AVSC_API(ret, name) EXTERN_C ret AVSC_CC name +# define AVSC_EXPORT AVS_EXTERN_C +# define AVSC_API(ret, name) AVS_EXTERN_C ret AVSC_CC name #endif #else -# define AVSC_EXPORT EXTERN_C __declspec(dllexport) +# define AVSC_EXPORT AVS_EXTERN_C __declspec(dllexport) # ifndef AVS_STATIC_LIB # define AVSC_IMPORT __declspec(dllimport) # else # define AVSC_IMPORT # endif # ifndef AVSC_NO_DECLSPEC -# define AVSC_API(ret, name) EXTERN_C AVSC_IMPORT ret AVSC_CC name +# define AVSC_API(ret, name) AVS_EXTERN_C AVSC_IMPORT ret AVSC_CC name # else # define AVSC_API(ret, name) typedef ret (AVSC_CC *name##_func) # endif 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; } +