From b0b86f307878431b3b6882a67490dc08971856f8 Mon Sep 17 00:00:00 2001 From: Martin Herkt Date: Fri, 20 Apr 2012 08:01:19 +0200 Subject: [PATCH] Add support for BT.709, restructure sources Color spaces are now automatically guessed based on video dimensions unless the user specifies a specific color space. --- CMakeLists.txt | 16 ++ README.txt | 17 +- src/CMakeLists.txt | 17 +- src/assrender.c | 545 ++------------------------------------------- src/assrender.h | 40 ++++ src/render.c | 300 +++++++++++++++++++++++++ src/render.h | 25 +++ src/sub.c | 120 ++++++++++ src/sub.h | 8 + src/timecodes.c | 83 +++++++ src/timecodes.h | 3 + 11 files changed, 629 insertions(+), 545 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 src/assrender.h create mode 100644 src/render.c create mode 100644 src/render.h create mode 100644 src/sub.c create mode 100644 src/sub.h create mode 100644 src/timecodes.c create mode 100644 src/timecodes.h diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..960a73b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 2.8) + +project(assrender) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(LIBASS REQUIRED libass>=0.10.0) +find_package(EXPAT REQUIRED) +find_package(ZLIB REQUIRED) + +if (CMAKE_COMPILER_IS_GNUCXX) + add_definitions("-Wall -Wno-comment -pedantic -std=gnu99") + set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "-s") + set(CMAKE_C_FLAGS_RELEASE "-O3") +endif() + +add_subdirectory(src) diff --git a/README.txt b/README.txt index d717ce3..2297049 100644 --- a/README.txt +++ b/README.txt @@ -1,8 +1,16 @@ +AssRender is an AviSynth plugin that renders ASS/SSA and SRT (without the +HTML-like markup) subtitles. It uses libass to render the subtitles, which makes +it the fastest and most correct ASS renderer for AviSynth. + +This also means that it is much more picky about script syntax than VSFilter +and friends, so keep that in mind before blaming the filter. Yes, people have +reported a lot of errors that were actually the script author’s fault. + Usage: assrender(clip, string file, [string vfr, int hinting, float scale, float line_spacing, float dar, float sar, int top, int bottom, int left, int right, string charset, int debuglevel, string fontdir, - string srt_font]) + string srt_font, string colorspace]) string file: Your subtitle file. May be ASS, SSA or SRT. @@ -30,4 +38,9 @@ string fontdir: Useful if you are lazy but want to keep your system fonts clean. string srt_font: Font to use for SRT subtitles. - Defaults to whatever Fontconfig chooses for “Sans”. \ No newline at end of file + Defaults to whatever Fontconfig chooses for “Sans”. +string colorspace: + The color space of your (YUV) video. Possible values: + - auto, guess (guess based on video resolution) + - Rec709, BT.709 + - Rec601, BT.601 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1c89d28..9763a27 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,17 +1,2 @@ -cmake_minimum_required(VERSION 2.8) - -project(assrender) - -find_package(PkgConfig REQUIRED) -pkg_check_modules(LIBASS REQUIRED libass>=0.10.0) -find_package(EXPAT REQUIRED) -find_package(ZLIB REQUIRED) - -if (CMAKE_COMPILER_IS_GNUCXX) - add_definitions("-Wall -pedantic -std=gnu89") - set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "-s") - set(CMAKE_C_FLAGS_RELEASE "-O3") -endif() - -add_library(assrender SHARED assrender.c) +add_library(assrender SHARED render.c sub.c timecodes.c assrender.c) target_link_libraries(assrender ${LIBASS_LDFLAGS} ${EXPAT_LIBRARY} ${ZLIB_LIBRARY} avisynth.lib) diff --git a/src/assrender.c b/src/assrender.c index a62a459..4a68a74 100644 --- a/src/assrender.c +++ b/src/assrender.c @@ -1,531 +1,10 @@ -#include "avisynth_c.h" -#include #include -#include -#include #include -#include -#if defined(_MSC_VER) -#define __NO_ISOCEXT -#define __NO_INLINE__ -#endif - -#include - -#define _r(c) ((c)>>24) -#define _g(c) (((c)>>16)&0xFF) -#define _b(c) (((c)>>8)&0xFF) -#define _a(c) ((c)&0xFF) - -#define rgba2y(c) ( (( 263*_r(c) + 516*_g(c) + 100*_b(c)) >> 10) + 16 ) -#define rgba2u(c) ( ((-152*_r(c) - 298*_g(c) + 450*_b(c)) >> 10) + 128 ) -#define rgba2v(c) ( (( 450*_r(c) - 376*_g(c) - 73*_b(c)) >> 10) + 128 ) - -#define blend(srcA, srcRGB, dstA, dstRGB, outA) \ - (((srcA * 255 * srcRGB + (dstRGB * dstA * (255 - srcA))) / outA + 255) >> 8) - -#if defined(_WIN32) && !defined(__MINGW32__) -// replacement of POSIX rint() for Windows -static int rint(double x) -{ - return floor(x + .5); -} - -#define strcasecmp _stricmp -#define atoll _atoi64 -#endif - -typedef struct { - unsigned char *uv_tmp[2]; - struct lbounds { - uint16_t start; - uint16_t end; - } *lbounds; - unsigned int isvfr; - ASS_Track *ass; - ASS_Library *ass_library; - ASS_Renderer *ass_renderer; - int64_t *timestamp; -} udata; - - -int parse_timecodesv1(FILE *f, int total, udata *ud) -{ - int start, end, n = 0; - double t = 0.0, basefps = 0.0, fps; - char l[BUFSIZ]; - int64_t *ts = calloc(total, sizeof(int64_t)); - - if (!ts) - return 0; - - while ((fgets(l, BUFSIZ - 1, f) != NULL) && n < total) { - if (l[0] == 0 || l[0] == '\n' || l[0] == '\r' || l[0] == '#') - continue; - - if (sscanf(l, "Assume %lf", &basefps) == 1) - continue; - - if (!sscanf(l, "%d,%d,%lf", &start, &end, &fps) == 3) - continue; - - if (basefps == 0.0) - continue; - - while (n < start && n < total) { - ts[n++] = (int64_t) rint(t); - t += 1000.0 / basefps; - } - - while (n <= end && n < total) { - ts[n++] = (int64_t) rint(t); - t += 1000.0 / fps; - } - } - - fclose(f); - - if (basefps == 0.0) { - free(ts); - return 0; - } - - while (n < total) { - ts[n++] = (int64_t) rint(t); - t += 1000.0 / basefps; - } - - ud->timestamp = ts; - - return 1; -} - -int parse_timecodesv2(FILE *f, int total, udata *ud) -{ - int n = 0; - int64_t *ts = calloc(total, sizeof(int64_t)); - char l[BUFSIZ]; - - if (!ts) { - return 0; - } - - while ((fgets(l, BUFSIZ - 1, f) != NULL) && n < total) { - if (l[0] == 0 || l[0] == '\n' || l[0] == '\r' || l[0] == '#') - continue; - - ts[n++] = atoll(l); - } - - fclose(f); - - if (n < total) { - free(ts); - return 0; - } - - ud->timestamp = ts; - - return 1; -} - -ASS_Track *parse_srt(const char *f, udata *ud, const char *srt_font) -{ - char l[BUFSIZ], buf[BUFSIZ]; - int start[4], end[4], isn; - ASS_Track *ass = ass_new_track(ud->ass_library); - FILE *fh = fopen(f, "r"); - - if (!fh) - return NULL; - - sprintf(buf, "[V4+ Styles]\nStyle: Default,%s,20,&H1EFFFFFF,&H00FFFFFF," - "&H29000000,&H3C000000,0,0,0,0,100,100,0,0,1,1,1.2,2,10,10," - "12,1\n\n[Events]\n", - srt_font); - - ass_process_data(ass, buf, BUFSIZ - 1); - - while (fgets(l, BUFSIZ - 1, fh) != NULL) { - if (l[0] == 0 || l[0] == '\n' || l[0] == '\r') - continue; - - if (sscanf(l, "%d:%d:%d,%d --> %d:%d:%d,%d", &start[0], &start[1], - &start[2], &start[3], &end[0], &end[1], &end[2], - &end[3]) == 8) { - sprintf(buf, "Dialogue: 0,%d:%02d:%02d.%02d,%d:%02d:%02d.%02d," - "Default,,0,0,0,,{\\blur0.7}", - start[0], start[1], start[2], - (int) rint((double) start[3] / 10.0), end[0], end[1], - end[2], (int) rint((double) end[3] / 10.0)); - isn = 0; - - while (fgets(l, BUFSIZ - 1, fh) != NULL) { - if (l[0] == 0 || l[0] == '\n' || l[0] == '\r') - break; - - if (l[strlen(l) - 1] == '\n' || l[strlen(l) - 1] == '\r') - l[strlen(l) - 1] = 0; - - if (isn) { - strcat(buf, "\\N"); - } - - strncat(buf, l, BUFSIZ - 1); - isn = 1; - } - - ass_process_data(ass, buf, BUFSIZ - 1); - } - } - - return ass; -} - -void msg_callback(int level, const char *fmt, va_list va, void *data) -{ - if (level > (int) data) - return; - - fprintf(stderr, "libass: "); - vfprintf(stderr, fmt, va); - fprintf(stderr, "\n"); -} - -int init_ass(int w, int h, double scale, double line_spacing, - ASS_Hinting hinting, double dar, double sar, int top, - int bottom, int left, int right, int verbosity, - const char *fontdir, udata *ud) -{ - extern int FcDebugVal; - ASS_Renderer *ass_renderer; - ASS_Library *ass_library = ass_library_init(); - - if (!ass_library) - return 0; - - ass_set_message_cb(ass_library, msg_callback, (void *) verbosity); - ass_set_extract_fonts(ass_library, 0); - ass_set_style_overrides(ass_library, 0); - - ass_renderer = ass_renderer_init(ass_library); - - if (!ass_renderer) - return 0; - - ass_set_font_scale(ass_renderer, scale); - ass_set_hinting(ass_renderer, hinting); - ass_set_frame_size(ass_renderer, w, h); - ass_set_margins(ass_renderer, top, bottom, left, right); - ass_set_use_margins(ass_renderer, 1); - - if (line_spacing) - ass_set_line_spacing(ass_renderer, line_spacing); - - if (dar && sar) - ass_set_aspect_ratio(ass_renderer, dar, sar); - - if (verbosity > 0) { - // update Fontconfig’s cache verbosely - FcDebugVal = 128; - } - - // don’t scan for home directory as it’s not needed on win32 - FcConfigEnableHome(FcFalse); - - if (strcmp(fontdir, "")) - ass_set_fonts_dir(ass_library, fontdir); - - ass_set_fonts(ass_renderer, NULL, NULL, 1, NULL, 1); - FcDebugVal = 0; - ud->ass_library = ass_library; - ud->ass_renderer = ass_renderer; - - return 1; -} - -void blit_rgb(unsigned char *data, ASS_Image *img, unsigned int pitch, - unsigned int height, unsigned int numc) -{ - while (img) { - unsigned char *dst; - unsigned int dst_delta; - int x, y, k, c = 0; - unsigned char a, r, g, b; - unsigned char outa; - unsigned char *sp = img->bitmap; - - if (img->w == 0 || img->h == 0) { - img = img->next; - continue; - } - - // Move destination pointer to the bottom right corner of the - // bounding box that contains the current overlay bitmap. - // Remember that avisynth RGB bitmaps are upside down, hence we - // need to render upside down. - dst = - data + (pitch * (height - img->dst_y - 1)) + img->dst_x * numc; - dst_delta = pitch + img->w * numc; - - a = 255 - _a(img->color); - r = _r(img->color); - g = _g(img->color); - b = _b(img->color); - - - for (y = 0; y < img->h; y++) { - for (x = 0; x < img->w; ++x) { - k = (sp[x] * a + 255) >> 8; - - if (k && numc == 4) { - outa = (k * 255 + (dst[c + 3] * (255 - k)) + 255) >> 8; - dst[c ] = blend(k, b, dst[c + 3], dst[c ], outa); - dst[c + 1] = blend(k, g, dst[c + 3], dst[c + 1], outa); - dst[c + 2] = blend(k, r, dst[c + 3], dst[c + 2], outa); - dst[c + 3] = outa; - } else { - k = (sp[x] * a + 255) >> 8; - dst[c ] = (k * b + (255 - k) * dst[c ] + 255) >> 8; - dst[c + 1] = (k * g + (255 - k) * dst[c + 1] + 255) >> 8; - dst[c + 2] = (k * r + (255 - k) * dst[c + 2] + 255) >> 8; - } - - c += numc; - } - - dst -= dst_delta; - sp += img->stride; - } - - img = img->next; - } -} - -void blit444(ASS_Image *img, unsigned char *dataY, unsigned char *dataU, - unsigned char *dataV, unsigned int pitch) -{ - while (img) { - unsigned char y = rgba2y(img->color); - unsigned char u = rgba2u(img->color); - unsigned char v = rgba2v(img->color); - unsigned char opacity = 255 - _a(img->color); - - int i, j; - - unsigned char *src = img->bitmap; - unsigned char *dsty = dataY + img->dst_x + img->dst_y * pitch; - unsigned char *dstu = dataU + img->dst_x + img->dst_y * pitch; - unsigned char *dstv = dataV + img->dst_x + img->dst_y * pitch; - - if (img->w == 0 || img->h == 0) { - img = img->next; - continue; - } - - for (i = 0; i < img->h; ++i) { - for (j = 0; j < img->w; ++j) { - unsigned int k = (src[j] * opacity + 255) >> 8; - dsty[j] = (k * y + (255 - k) * dsty[j] + 255) >> 8; - dstu[j] = (k * u + (255 - k) * dstu[j] + 255) >> 8; - dstv[j] = (k * v + (255 - k) * dstv[j] + 255) >> 8; - } - - src += img->stride; - dsty += pitch; - - dstu += pitch; - dstv += pitch; - } - - img = img->next; - } -} - -void setbounds(udata *ud, int starty, int endy, - int startx, int endx) -{ - int i; - starty >>= 1; - endy = (endy + 1) >> 1; - startx >>= 1; - endx = (endx + 1) >> 1; - - for (i = starty; i < endy; i++) { - struct lbounds *ll = ud->lbounds + i; - - if (startx < ll->start) - ll->start = startx; - - if (endx > ll->end) - ll->end = endx; - } -} - -AVS_VideoFrame *AVSC_CC assrender_get_frame(AVS_FilterInfo *p, int n) -{ - udata *ud = (udata *) p->user_data; - ASS_Track *ass = ud->ass; - ASS_Renderer *ass_renderer = ud->ass_renderer; - ASS_Image *img; - AVS_VideoFrame *src; - unsigned int height, pitch, pitchUV = 0; - unsigned char *data, *dataY = 0, *dataU = 0, *dataV = 0; - int64_t ts; - - src = avs_get_frame(p->child, n); - - avs_make_writable(p->env, &src); - - if (avs_is_planar(&p->vi)) { - dataY = avs_get_write_ptr_p(src, AVS_PLANAR_Y); - dataU = avs_get_write_ptr_p(src, AVS_PLANAR_U); - dataV = avs_get_write_ptr_p(src, AVS_PLANAR_V); - pitchUV = avs_get_pitch_p(src, AVS_PLANAR_U); - } - - data = avs_get_write_ptr(src); - - height = avs_get_height(src); - pitch = avs_get_pitch(src); - - if (!ud->isvfr) { - // 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 { - ts = ud->timestamp[n]; - } - - img = ass_render_frame(ass_renderer, ass, ts, NULL); - - if (avs_is_rgb32(&p->vi) || avs_is_rgb24(&p->vi)) { - blit_rgb(data, img, pitch, height, avs_is_rgb32(&p->vi) ? 4 : 3); - } else if (avs_is_yuy2(&p->vi)) { - // TODO - } else if (avs_is_yuv(&p->vi)) { - - if (avs_is_yv12(&p->vi)) { - int i, j; - static ASS_Image *im; - unsigned char *dstu, *dstv, *srcu, *srcv; - unsigned char *dstu_next, *dstv_next, *srcu_next, *srcv_next; - - if (img) { - for (i = 0; i < (height + 1) >> 1; i++) { - ud->lbounds[i].start = 65535; - ud->lbounds[i].end = 0; - } - - for (im = img; im; im = im->next) - setbounds(ud, im->dst_y, im->dst_y + im->h, - im->dst_x, im->dst_x + im->w); - - dstu = ud->uv_tmp[0]; - dstv = ud->uv_tmp[1]; - srcu = dataU; - srcv = dataV; - - for (i = 0; i < (height + 1) >> 1; i++) { - struct lbounds *lb = ud->lbounds + i; - dstu_next = dstu + pitch; - dstv_next = dstv + pitch; - - for (j = lb->start; j < lb->end; j++) { - dstu[j << 1] - = dstu[(j << 1) + 1] - = dstu_next[j << 1] - = dstu_next[(j << 1) + 1] - = srcu[j]; - - dstv[j << 1] - = dstv[(j << 1) + 1] - = dstv_next[j << 1] - = dstv_next[(j << 1) + 1] - = srcv[j]; - } - - srcu += pitchUV; - srcv += pitchUV; - dstu = dstu_next + pitch; - dstv = dstv_next + pitch; - } - - blit444(img, dataY, ud->uv_tmp[0], ud->uv_tmp[1], pitch); - - srcu = ud->uv_tmp[0]; - srcv = ud->uv_tmp[1]; - srcu_next = srcu + pitch; - srcv_next = srcv + pitch; - dstu = dataU; - dstv = dataV; - - for (i = 0; i < (height + 1) >> 1; ++i) { - for (j = ud->lbounds[i].start; - j < ud->lbounds[i].end; j++) { - dstu[j] = ( - srcu[j << 1] - + srcu[(j << 1) + 1] - + srcu_next[j << 1] - + srcu_next[(j << 1) + 1] - ) >> 2; - - dstv[j] = ( - srcv[j << 1] - + srcv[(j << 1) + 1] - + srcv_next[j << 1] - + srcv_next[(j << 1) + 1] - ) >> 2; - } - - dstu += pitchUV; - dstv += pitchUV; - srcu = srcu_next + pitch; - srcu_next = srcu + pitch; - srcv = srcv_next + pitch; - srcv_next = srcv + pitch; - } - } - } else { - if (pitchUV && pitchUV < pitch) { // probably YV16 - // TODO - } else if (pitchUV) // YV24 - blit444(img, dataY, dataU, dataV, pitch); - else { // Y8 - while (img) { - unsigned char y = rgba2y(img->color); - unsigned char opacity = 255 - _a(img->color); - - int i, j; - - unsigned char *src = img->bitmap; - unsigned char *dsty = - dataY + img->dst_x + img->dst_y * pitch; - - if (img->w == 0 || img->h == 0) { - img = img->next; - continue; - } - - for (i = 0; i < img->h; ++i) { - for (j = 0; j < img->w; ++j) { - unsigned int k = (src[j] * opacity + 255) >> 8; - dsty[j] = (k * y + (255 - k) * dsty[j] + 255) >> 8; - } - - src += img->stride; - dsty += pitch; - } - - img = img->next; - } - } - } - } - - return src; -} +#include "assrender.h" +#include "render.h" +#include "sub.h" +#include "timecodes.h" void AVSC_CC assrender_destroy(void *ud, AVS_ScriptEnvironment *env) { @@ -578,6 +57,8 @@ AVS_Value AVSC_CC assrender_create(AVS_ScriptEnvironment *env, AVS_Value args, avs_as_string(avs_array_elt(args, 14)) : ""; const char *srt_font = avs_as_string(avs_array_elt(args, 15)) ? avs_as_string(avs_array_elt(args, 15)) : "Sans"; + const char *colorspace = avs_as_string(avs_array_elt(args, 16)) ? + avs_as_string(avs_array_elt(args, 16)) : "guess"; ASS_Hinting hinting; udata *data; @@ -688,6 +169,16 @@ AVS_Value AVSC_CC assrender_create(AVS_ScriptEnvironment *env, AVS_Value args, data->isvfr = 0; } + if (!strcasecmp(colorspace, "guess") || !strcasecmp(colorspace, "auto")) { + if (fi->vi.width > 1280 || fi->vi.height > 576) + data->colorspace = BT709; + else + data->colorspace = BT601; + } else if (!strcasecmp(colorspace, "bt.709") || !strcasecmp(colorspace,"rec709")) + data->colorspace = BT709; + else if (!strcasecmp(colorspace, "bt.601") || !strcasecmp(colorspace, "rec601")) + data->colorspace = BT601; + data->uv_tmp[0] = malloc(fi->vi.width * fi->vi.height); data->uv_tmp[1] = malloc(fi->vi.width * fi->vi.height); data->lbounds = malloc(((fi->vi.height + 1) >> 1) @@ -710,9 +201,9 @@ const char *AVSC_CC avisynth_c_plugin_init(AVS_ScriptEnvironment *env) avs_add_function(env, "assrender", "c[file]s[vfr]s[hinting]i[scale]f[line_spacing]f[dar]f" "[sar]f[top]i[bottom]i[left]i[right]i[charset]s" - "[debuglevel]i[fontdir]s[srt_font]s", + "[debuglevel]i[fontdir]s[srt_font]s[colorspace]s", assrender_create, 0); - return "AssRender 0.24.1: draws .asses better and faster than ever before"; + return "AssRender 0.25: draws .asses better and faster than ever before"; } // kate: indent-mode cstyle; space-indent on; indent-width 4; replace-tabs on; diff --git a/src/assrender.h b/src/assrender.h new file mode 100644 index 0000000..0ffcd6c --- /dev/null +++ b/src/assrender.h @@ -0,0 +1,40 @@ +#include +#include +#include "avisynth_c.h" + +#if defined(_MSC_VER) +#define __NO_ISOCEXT +#define __NO_INLINE__ +#endif + +#if defined(_WIN32) && !defined(__MINGW32__) +// replacement of POSIX rint() for Windows +static int rint(double x) +{ + return floor(x + .5); +} + +#define strcasecmp _stricmp +#define atoll _atoi64 +#endif + +enum csp { + BT601, + BT709 +}; + +enum plane { Y, U, V }; + +typedef struct { + uint8_t *uv_tmp[2]; + struct lbounds { + uint16_t start; + uint16_t end; + } *lbounds; + uint32_t isvfr; + ASS_Track *ass; + ASS_Library *ass_library; + ASS_Renderer *ass_renderer; + int64_t *timestamp; + enum csp colorspace; +} udata; diff --git a/src/render.c b/src/render.c new file mode 100644 index 0000000..b0b3d19 --- /dev/null +++ b/src/render.c @@ -0,0 +1,300 @@ +#include "assrender.h" +#include "render.h" + +void setbounds(udata *ud, int starty, int endy, int startx, int endx) +{ + int i; + starty >>= 1; + endy = (endy + 1) >> 1; + startx >>= 1; + endx = (endx + 1) >> 1; + + for (i = starty; i < endy; i++) { + struct lbounds *ll = ud->lbounds + i; + + if (startx < ll->start) + ll->start = startx; + + if (endx > ll->end) + ll->end = endx; + } +} + +void blit_rgb(uint8_t *data, ASS_Image *img, uint32_t pitch, uint32_t height, + uint32_t numc) +{ + while (img) { + uint8_t *dst; + uint32_t dst_delta; + int x, y, k, c = 0; + uint8_t a, r, g, b; + uint8_t outa; + uint8_t *sp = img->bitmap; + + if (img->w == 0 || img->h == 0) { + img = img->next; + continue; + } + + // Move destination pointer to the bottom right corner of the + // bounding box that contains the current overlay bitmap. + // Remember that avisynth RGB bitmaps are upside down, hence we + // need to render upside down. + dst = + data + (pitch * (height - img->dst_y - 1)) + img->dst_x * numc; + dst_delta = pitch + img->w * numc; + + a = 255 - _a(img->color); + r = _r(img->color); + g = _g(img->color); + b = _b(img->color); + + + for (y = 0; y < img->h; y++) { + for (x = 0; x < img->w; ++x) { + k = (sp[x] * a + 255) >> 8; + + if (k && numc == 4) { + outa = (k * 255 + (dst[c + 3] * (255 - k)) + 255) >> 8; + dst[c ] = blend(k, b, dst[c + 3], dst[c ], outa); + dst[c + 1] = blend(k, g, dst[c + 3], dst[c + 1], outa); + dst[c + 2] = blend(k, r, dst[c + 3], dst[c + 2], outa); + dst[c + 3] = outa; + } else { + k = (sp[x] * a + 255) >> 8; + dst[c ] = (k * b + (255 - k) * dst[c ] + 255) >> 8; + dst[c + 1] = (k * g + (255 - k) * dst[c + 1] + 255) >> 8; + dst[c + 2] = (k * r + (255 - k) * dst[c + 2] + 255) >> 8; + } + + c += numc; + } + + dst -= dst_delta; + sp += img->stride; + } + + img = img->next; + } +} + +void blit444(ASS_Image *img, uint8_t *dataY, uint8_t *dataU, uint8_t *dataV, + uint32_t pitch, enum csp colorspace) +{ + uint8_t y, u, v, opacity, *src, *dsty, *dstu, *dstv; + uint32_t k; + + int i, j; + + while (img) { + if (colorspace == BT709) { + y = rgba2y709(img->color); + u = rgba2u709(img->color); + v = rgba2v709(img->color); + } else { + y = rgba2y601(img->color); + u = rgba2y601(img->color); + v = rgba2y601(img->color); + } + + opacity = 255 - _a(img->color); + + src = img->bitmap; + dsty = dataY + img->dst_x + img->dst_y * pitch; + dstu = dataU + img->dst_x + img->dst_y * pitch; + dstv = dataV + img->dst_x + img->dst_y * pitch; + + if (img->w == 0 || img->h == 0) { + img = img->next; + continue; + } + + for (i = 0; i < img->h; ++i) { + for (j = 0; j < img->w; ++j) { + k = (src[j] * opacity + 255) >> 8; + dsty[j] = (k * y + (255 - k) * dsty[j] + 255) >> 8; + dstu[j] = (k * u + (255 - k) * dstu[j] + 255) >> 8; + dstv[j] = (k * v + (255 - k) * dstv[j] + 255) >> 8; + } + + src += img->stride; + dsty += pitch; + + dstu += pitch; + dstv += pitch; + } + + img = img->next; + } +} + +AVS_VideoFrame *AVSC_CC assrender_get_frame(AVS_FilterInfo *p, int n) +{ + udata *ud = (udata *) p->user_data; + ASS_Track *ass = ud->ass; + ASS_Renderer *ass_renderer = ud->ass_renderer; + ASS_Image *img; + AVS_VideoFrame *src; + uint32_t height, pitch, pitchUV = 0; + uint8_t *data, *dataY = 0, *dataU = 0, *dataV = 0; + int64_t ts; + + src = avs_get_frame(p->child, n); + + avs_make_writable(p->env, &src); + + if (avs_is_planar(&p->vi)) { + dataY = avs_get_write_ptr_p(src, AVS_PLANAR_Y); + dataU = avs_get_write_ptr_p(src, AVS_PLANAR_U); + dataV = avs_get_write_ptr_p(src, AVS_PLANAR_V); + pitchUV = avs_get_pitch_p(src, AVS_PLANAR_U); + } + + data = avs_get_write_ptr(src); + + height = avs_get_height(src); + pitch = avs_get_pitch(src); + + if (!ud->isvfr) { + // 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 { + ts = ud->timestamp[n]; + } + + img = ass_render_frame(ass_renderer, ass, ts, NULL); + + if (avs_is_rgb32(&p->vi) || avs_is_rgb24(&p->vi)) { + blit_rgb(data, img, pitch, height, avs_is_rgb32(&p->vi) ? 4 : 3); + } else if (avs_is_yuy2(&p->vi)) { + // TODO + } else if (avs_is_yuv(&p->vi)) { + + if (avs_is_yv12(&p->vi)) { + int i, j; + static ASS_Image *im; + uint8_t *dstu, *dstv, *srcu, *srcv; + uint8_t *dstu_next, *dstv_next, *srcu_next, *srcv_next; + + if (img) { + for (i = 0; i < (height + 1) >> 1; i++) { + ud->lbounds[i].start = 65535; + ud->lbounds[i].end = 0; + } + + for (im = img; im; im = im->next) + setbounds(ud, im->dst_y, im->dst_y + im->h, + im->dst_x, im->dst_x + im->w); + + dstu = ud->uv_tmp[0]; + dstv = ud->uv_tmp[1]; + srcu = dataU; + srcv = dataV; + + for (i = 0; i < (height + 1) >> 1; i++) { + struct lbounds *lb = ud->lbounds + i; + dstu_next = dstu + pitch; + dstv_next = dstv + pitch; + + for (j = lb->start; j < lb->end; j++) { + dstu[j << 1] + = dstu[(j << 1) + 1] + = dstu_next[j << 1] + = dstu_next[(j << 1) + 1] + = srcu[j]; + + dstv[j << 1] + = dstv[(j << 1) + 1] + = dstv_next[j << 1] + = dstv_next[(j << 1) + 1] + = srcv[j]; + } + + srcu += pitchUV; + srcv += pitchUV; + dstu = dstu_next + pitch; + dstv = dstv_next + pitch; + } + + blit444(img, dataY, ud->uv_tmp[0], ud->uv_tmp[1], pitch, + ud->colorspace); + + srcu = ud->uv_tmp[0]; + srcv = ud->uv_tmp[1]; + srcu_next = srcu + pitch; + srcv_next = srcv + pitch; + dstu = dataU; + dstv = dataV; + + for (i = 0; i < (height + 1) >> 1; ++i) { + for (j = ud->lbounds[i].start; + j < ud->lbounds[i].end; j++) { + dstu[j] = ( + srcu[j << 1] + + srcu[(j << 1) + 1] + + srcu_next[j << 1] + + srcu_next[(j << 1) + 1] + ) >> 2; + + dstv[j] = ( + srcv[j << 1] + + srcv[(j << 1) + 1] + + srcv_next[j << 1] + + srcv_next[(j << 1) + 1] + ) >> 2; + } + + dstu += pitchUV; + dstv += pitchUV; + srcu = srcu_next + pitch; + srcu_next = srcu + pitch; + srcv = srcv_next + pitch; + srcv_next = srcv + pitch; + } + } + } else { + if (pitchUV && pitchUV < pitch) { // probably YV16 + // TODO + } else if (pitchUV) // YV24 + blit444(img, dataY, dataU, dataV, pitch, ud->colorspace); + else { // Y8 + while (img) { + uint8_t y; + + if (ud->colorspace == BT709) { + y = rgba2y709(img->color); + } else { + y = rgba2y601(img->color); + } + + uint8_t opacity = 255 - _a(img->color); + + int i, j; + + uint8_t *src = img->bitmap; + uint8_t *dsty = dataY + img->dst_x + img->dst_y * pitch; + + if (img->w == 0 || img->h == 0) { + img = img->next; + continue; + } + + for (i = 0; i < img->h; ++i) { + for (j = 0; j < img->w; ++j) { + uint32_t k = (src[j] * opacity + 255) >> 8; + dsty[j] = (k * y + (255 - k) * dsty[j] + 255) >> 8; + } + + src += img->stride; + dsty += pitch; + } + + img = img->next; + } + } + } + } + + return src; +} \ No newline at end of file diff --git a/src/render.h b/src/render.h new file mode 100644 index 0000000..68671a8 --- /dev/null +++ b/src/render.h @@ -0,0 +1,25 @@ +#define _r(c) ((c)>>24) +#define _g(c) (((c)>>16)&0xFF) +#define _b(c) (((c)>>8)&0xFF) +#define _a(c) ((c)&0xFF) + +#define rgba2y601(c) ( (( 263*_r(c) + 516*_g(c) + 100*_b(c)) >> 10) + 16 ) +#define rgba2u601(c) ( ((-152*_r(c) - 298*_g(c) + 450*_b(c)) >> 10) + 128 ) +#define rgba2v601(c) ( (( 450*_r(c) - 376*_g(c) - 73*_b(c)) >> 10) + 128 ) + +#define rgba2y709(c) ( (( 745 *_r(c) + 2506 *_g(c) + 253 *_b(c) ) >> 12) + 16 ) +#define rgba2u709(c) ( ((-41 *_r(c) - 407 *_g(c) + 448 *_b(c) ) >> 10) + 128 ) +#define rgba2v709(c) ( (( 14336*_r(c) - 11051*_g(c) - 3285*_b(c) ) >> 15) + 128 ) + +#define blend(srcA, srcRGB, dstA, dstRGB, outA) \ + (((srcA * 255 * srcRGB + (dstRGB * dstA * (255 - srcA))) / outA + 255) >> 8) + +void setbounds(udata *ud, int starty, int endy, int startx, int endx); + +void blit_rgb(uint8_t *data, ASS_Image *img, uint32_t pitch, uint32_t height, + uint32_t numc); + +void blit444(ASS_Image *img, uint8_t *dataY, uint8_t *dataU, uint8_t *dataV, + uint32_t pitch, enum csp colorspace); + +AVS_VideoFrame *AVSC_CC assrender_get_frame(AVS_FilterInfo *p, int n); diff --git a/src/sub.c b/src/sub.c new file mode 100644 index 0000000..ad3aa59 --- /dev/null +++ b/src/sub.c @@ -0,0 +1,120 @@ +#include +#include +#include + +#include "assrender.h" + +ASS_Track *parse_srt(const char *f, udata *ud, const char *srt_font) +{ + char l[BUFSIZ], buf[BUFSIZ]; + int start[4], end[4], isn; + ASS_Track *ass = ass_new_track(ud->ass_library); + FILE *fh = fopen(f, "r"); + + if (!fh) + return NULL; + + sprintf(buf, "[V4+ Styles]\nStyle: Default,%s,20,&H1EFFFFFF,&H00FFFFFF," + "&H29000000,&H3C000000,0,0,0,0,100,100,0,0,1,1,1.2,2,10,10," + "12,1\n\n[Events]\n", + srt_font); + + ass_process_data(ass, buf, BUFSIZ - 1); + + while (fgets(l, BUFSIZ - 1, fh) != NULL) { + if (l[0] == 0 || l[0] == '\n' || l[0] == '\r') + continue; + + if (sscanf(l, "%d:%d:%d,%d --> %d:%d:%d,%d", &start[0], &start[1], + &start[2], &start[3], &end[0], &end[1], &end[2], + &end[3]) == 8) { + sprintf(buf, "Dialogue: 0,%d:%02d:%02d.%02d,%d:%02d:%02d.%02d," + "Default,,0,0,0,,{\\blur0.7}", + start[0], start[1], start[2], + (int) rint((double) start[3] / 10.0), end[0], end[1], + end[2], (int) rint((double) end[3] / 10.0)); + isn = 0; + + while (fgets(l, BUFSIZ - 1, fh) != NULL) { + if (l[0] == 0 || l[0] == '\n' || l[0] == '\r') + break; + + if (l[strlen(l) - 1] == '\n' || l[strlen(l) - 1] == '\r') + l[strlen(l) - 1] = 0; + + if (isn) { + strcat(buf, "\\N"); + } + + strncat(buf, l, BUFSIZ - 1); + isn = 1; + } + + ass_process_data(ass, buf, BUFSIZ - 1); + } + } + + return ass; +} + +void msg_callback(int level, const char *fmt, va_list va, void *data) +{ + if (level > (int) data) + return; + + fprintf(stderr, "libass: "); + vfprintf(stderr, fmt, va); + fprintf(stderr, "\n"); +} + +int init_ass(int w, int h, double scale, double line_spacing, + ASS_Hinting hinting, double dar, double sar, int top, + int bottom, int left, int right, int verbosity, + const char *fontdir, udata *ud) +{ + extern int FcDebugVal; + ASS_Renderer *ass_renderer; + ASS_Library *ass_library = ass_library_init(); + + if (!ass_library) + return 0; + + ass_set_message_cb(ass_library, msg_callback, (void *) verbosity); + ass_set_extract_fonts(ass_library, 0); + ass_set_style_overrides(ass_library, 0); + + ass_renderer = ass_renderer_init(ass_library); + + if (!ass_renderer) + return 0; + + ass_set_font_scale(ass_renderer, scale); + ass_set_hinting(ass_renderer, hinting); + ass_set_frame_size(ass_renderer, w, h); + ass_set_margins(ass_renderer, top, bottom, left, right); + ass_set_use_margins(ass_renderer, 1); + + if (line_spacing) + ass_set_line_spacing(ass_renderer, line_spacing); + + if (dar && sar) + ass_set_aspect_ratio(ass_renderer, dar, sar); + + if (verbosity > 0) { + // update Fontconfig’s cache verbosely + FcDebugVal = 128; + } + + // don’t scan for home directory as it’s not needed on win32 + FcConfigEnableHome(FcFalse); + + if (strcmp(fontdir, "")) + ass_set_fonts_dir(ass_library, fontdir); + + ass_set_fonts(ass_renderer, NULL, NULL, 1, NULL, 1); + FcDebugVal = 0; + ud->ass_library = ass_library; + ud->ass_renderer = ass_renderer; + + return 1; +} diff --git a/src/sub.h b/src/sub.h new file mode 100644 index 0000000..8c82d12 --- /dev/null +++ b/src/sub.h @@ -0,0 +1,8 @@ +ASS_Track *parse_srt(const char *f, udata *ud, const char *srt_font); + +void msg_callback(int level, const char *fmt, va_list va, void *data); + +int init_ass(int w, int h, double scale, double line_spacing, + ASS_Hinting hinting, double dar, double sar, int top, + int bottom, int left, int right, int verbosity, + const char *fontdir, udata *ud); diff --git a/src/timecodes.c b/src/timecodes.c new file mode 100644 index 0000000..3c7d168 --- /dev/null +++ b/src/timecodes.c @@ -0,0 +1,83 @@ +#include +#include +#include "assrender.h" + +int parse_timecodesv1(FILE *f, int total, udata *ud) +{ + int start, end, n = 0; + double t = 0.0, basefps = 0.0, fps; + char l[BUFSIZ]; + int64_t *ts = calloc(total, sizeof(int64_t)); + + if (!ts) + return 0; + + while ((fgets(l, BUFSIZ - 1, f) != NULL) && n < total) { + if (l[0] == 0 || l[0] == '\n' || l[0] == '\r' || l[0] == '#') + continue; + + if (sscanf(l, "Assume %lf", &basefps) == 1) + continue; + + if (!sscanf(l, "%d,%d,%lf", &start, &end, &fps) == 3) + continue; + + if (basefps == 0.0) + continue; + + while (n < start && n < total) { + ts[n++] = (int64_t) rint(t); + t += 1000.0 / basefps; + } + + while (n <= end && n < total) { + ts[n++] = (int64_t) rint(t); + t += 1000.0 / fps; + } + } + + fclose(f); + + if (basefps == 0.0) { + free(ts); + return 0; + } + + while (n < total) { + ts[n++] = (int64_t) rint(t); + t += 1000.0 / basefps; + } + + ud->timestamp = ts; + + return 1; +} + +int parse_timecodesv2(FILE *f, int total, udata *ud) +{ + int n = 0; + int64_t *ts = calloc(total, sizeof(int64_t)); + char l[BUFSIZ]; + + if (!ts) { + return 0; + } + + while ((fgets(l, BUFSIZ - 1, f) != NULL) && n < total) { + if (l[0] == 0 || l[0] == '\n' || l[0] == '\r' || l[0] == '#') + continue; + + ts[n++] = atoll(l); + } + + fclose(f); + + if (n < total) { + free(ts); + return 0; + } + + ud->timestamp = ts; + + return 1; +} diff --git a/src/timecodes.h b/src/timecodes.h new file mode 100644 index 0000000..d9c3a2b --- /dev/null +++ b/src/timecodes.h @@ -0,0 +1,3 @@ +int parse_timecodesv1(FILE *f, int total, udata *ud); + +int parse_timecodesv2(FILE *f, int total, udata *ud);