-
Notifications
You must be signed in to change notification settings - Fork 6
How to test libass without Kodi
If you need to test the libass features externally to Kodi, you can prepare a special environment in which a script will use the libass library to generate an image of the desired subtitle as a PNG. This guide is specifically for use with Windows 64bit. For the purposes of these instructions, we will use the library from upstream sources.
To build and run the libass script, you will require:
Open MSYS2 shell at 64bit from the Windows menu; from now on, all following commands must be run in the shell.
Install the toolchains and required packages:
pacman -S mingw-w64-x86_64-toolchain
pacman -S libtool
pre="mingw-w64-x86_64"; pacman --noconfirm -S automake autoconf nasm make git $pre-libtool $pre-pkg-config $pre-gcc $pre-fribidi $pre-freetype $pre-harfbuzz $pre-fontconfig $pre-libpng
Clone the libass master branch:
git clone https://github.com/libass/libass.git libass-upstream
Open the new libass directory:
cd libass-upstream
Build the libass library:
./autogen.sh && ./configure && make -j 6 && make install
Return to the main directory:
cd..
Copy the libass script code (below) and save it as manual_test.c
in the MSYS2 user home directory:
The path will be similar to this: C:\msys64\home\[your user name]\manual_test.c
Compile the libass script:
gcc -O2 -g -o manual_test.exe manual_test.c -lpng $(pkg-config --static --libs libass)
Run the script:
./manual_test.exe
You should now find the rendered image in the root user directory with the name of test_output_frame.png
; you can open this with any image viewer.
The script uses a fixed timestamp, so the timing of the subtitles will have to be adapted according to your needs. Note that every time you edit the script file you will have to re-compile it before running again.
Show/Hide libass `manual_test.c` script source code
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <ass/ass.h>
#include <ass/ass_types.h>
#include <png.h>
#define FRAME_HEIGHT 1080
#define FRAME_WIDTH 1920
#define FRAME_TIME 3700
#define FRAME_PNG_PATH "./test_output_frame.png"
struct event_src {
const char* text;
long start;
long duration;
};
static const struct event_src events[] = {
{"This a subtitle line", 3500, 2000},
{"This is another line", 3500, 7000},
{"Of course another one", 3500, 2000},
{"It is clear!", 3500, 7000},
{NULL}
};
static char ass_header[] =
"[Script Info]\n"
"ScriptType: v4.00+\n"
"ScaledBorderAndShadow: yes\n"
"YCbCr Matrix: TV.709\n"
"PlayResX: 1920\n"
"PlayResY: 1080\n"
"[V4+ Styles]\n"
"Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, "
"Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, "
"Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\n"
"Style: Default,DejaVu Sans,72,&H00FFFFFF,&HFF0000FF,&H00303030,&H00000000,-1,-1,0,0,100,100,0,0,1,4.2,1.5,2,255,255,63,1\n"
"[Events]\n"
"Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n";
void msg_callback(int level, const char *fmt, va_list va, void *data)
{
printf("libass:%d: ", level);
vprintf(fmt, va);
printf("\n");
}
static void write_image_to_png(const char* filename, ASS_Image* img);
int main()
{
ASS_Library* ass_library = NULL;
ASS_Renderer* ass_renderer = NULL;
ASS_Track* ass_track = NULL;
int ret = 0;
ass_library = ass_library_init();
if(!ass_library) goto errexit;
ass_set_message_cb(ass_library, msg_callback, NULL);
ass_set_extract_fonts(ass_library, 1);
ass_renderer = ass_renderer_init(ass_library);
if (!ass_renderer) goto errexit;
ass_set_frame_size(ass_renderer, FRAME_WIDTH, FRAME_HEIGHT);
ass_set_fonts(ass_renderer, NULL, "Sans", 1, NULL, 1);
ass_track = ass_new_track(ass_library);
ass_process_codec_private(ass_track, (char*)ass_header, sizeof(ass_header));
// Manually add events instead of process_chunk
size_t i = 0;
while(events[i].text) {
int evid = ass_alloc_event(ass_track);
if(evid < 0) {
fprintf(stderr, "Error allocating event!\n");
goto errexit;
}
ASS_Event* event = ass_track->events + evid;
event->Start = events[i].start;
event->Duration = events[i].duration;
event->Style = ass_track->default_style;
event->Text = strdup(events[i].text);
event->ReadOrder = evid;
if(!event->Text) {
fprintf(stderr, "Strdup failed!\n");
goto errexit;
}
fprintf(stderr, "DynEvent: %d, %d, %d, %s\n", evid, events[i].start, events[i].duration, events[i].text);
++i;
}
// Make sample rendering
ASS_Image* img = ass_render_frame(ass_renderer, ass_track, FRAME_TIME, NULL);
if(!img) {
fprintf(stderr, "Rendering failed!\n");
goto errexit;
}
write_image_to_png(FRAME_PNG_PATH, img);
goto clean_exit;
errexit:
ret = 1;
clean_exit:
if(ass_track) ass_free_track(ass_track);
if(ass_renderer) ass_renderer_done(ass_renderer);
if(ass_library) ass_library_done(ass_library);
return ret;
}
// ---- Plucked from test/test.c ---- //
typedef struct image_s {
int width, height, stride;
unsigned char *buffer; // RGB24
} image_t;
static void write_png(const char *fname, image_t *img)
{
FILE *fp;
png_structp png_ptr;
png_infop info_ptr;
png_byte **row_pointers;
int k;
png_ptr =
png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
info_ptr = png_create_info_struct(png_ptr);
fp = NULL;
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_write_struct(&png_ptr, &info_ptr);
fclose(fp);
return;
}
fp = fopen(fname, "wb");
if (fp == NULL) {
fprintf(stderr, "PNG Error opening %s for writing!\n", fname);
return;
}
png_init_io(png_ptr, fp);
png_set_compression_level(png_ptr, 0);
png_set_IHDR(png_ptr, info_ptr, img->width, img->height,
8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_write_info(png_ptr, info_ptr);
png_set_bgr(png_ptr);
row_pointers = (png_byte **) malloc(img->height * sizeof(png_byte *));
for (k = 0; k < img->height; k++)
row_pointers[k] = img->buffer + img->stride * k;
png_write_image(png_ptr, row_pointers);
png_write_end(png_ptr, info_ptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
free(row_pointers);
fclose(fp);
}
static image_t *gen_image(int width, int height)
{
image_t *img = malloc(sizeof(image_t));
img->width = width;
img->height = height;
img->stride = width * 3;
img->buffer = (unsigned char *) calloc(1, height * width * 3);
memset(img->buffer, 63, img->stride * img->height);
//for (int i = 0; i < height * width * 3; ++i)
// img->buffer[i] = (i/3/50) % 100;
return img;
}
#define _r(c) ((c)>>24)
#define _g(c) (((c)>>16)&0xFF)
#define _b(c) (((c)>>8)&0xFF)
#define _a(c) ((c)&0xFF)
static void blend_single(image_t * frame, ASS_Image *img)
{
int x, y;
unsigned char opacity = 255 - _a(img->color);
unsigned char r = _r(img->color);
unsigned char g = _g(img->color);
unsigned char b = _b(img->color);
unsigned char *src;
unsigned char *dst;
src = img->bitmap;
dst = frame->buffer + img->dst_y * frame->stride + img->dst_x * 3;
for (y = 0; y < img->h; ++y) {
for (x = 0; x < img->w; ++x) {
unsigned k = ((unsigned) src[x]) * opacity / 255;
// possible endianness problems
dst[x * 3] = (k * b + (255 - k) * dst[x * 3]) / 255;
dst[x * 3 + 1] = (k * g + (255 - k) * dst[x * 3 + 1]) / 255;
dst[x * 3 + 2] = (k * r + (255 - k) * dst[x * 3 + 2]) / 255;
}
src += img->stride;
dst += frame->stride;
}
}
static void blend(image_t * frame, ASS_Image *img)
{
int cnt = 0;
while (img) {
blend_single(frame, img);
++cnt;
img = img->next;
}
fprintf(stderr, "%d images blended\n", cnt);
}
// ---- End of "Plucked from test/test.c" ---- //
static void write_image_to_png(const char* filename, ASS_Image* img)
{
image_t* framebuf = gen_image(FRAME_WIDTH, FRAME_HEIGHT);
blend(framebuf, img);
write_png(filename, framebuf);
free(framebuf->buffer);
free(framebuf);
}
The sample script was provided with the courtesy of the libass developers.
To remove libass, open the libass directory:
cd libass-upstream
Perform the uninstall:
make uninstall