diff mbox series

[FFmpeg-devel,02/13] lavc/ass: add support for configuring default style via AVOptions

Message ID 20200125020200.2049-2-rcombs@rcombs.me
State New
Headers show
Series [FFmpeg-devel,01/13] lavc/ass: realign ff_ass_subtitle_header_default
Related show

Checks

Context Check Description
andriy/ffmpeg-patchwork pending
andriy/ffmpeg-patchwork success Applied patch
andriy/ffmpeg-patchwork success Configure finished
andriy/ffmpeg-patchwork success Make finished
andriy/ffmpeg-patchwork success Make fate finished

Commit Message

Ridley Combs Jan. 25, 2020, 2:01 a.m. UTC
---
 libavcodec/ass.c | 154 ++++++++++++++++++++++++++++++++++-------------
 libavcodec/ass.h | 106 +++++++++++++++++++++++++++++++-
 2 files changed, 215 insertions(+), 45 deletions(-)

Comments

Moritz Barsnick Jan. 27, 2020, 2:28 p.m. UTC | #1
On Fri, Jan 24, 2020 at 20:01:49 -0600, rcombs wrote:
> +static int invert_ass_alpha(uint32_t c)
> +{
> +    uint32_t a = c >> 24;
> +    return ((255 - a) << 24) | (c & 0xffffff);
> +}

You're inverting the "leftmost" 8 bits of c? Can't you just make this

return (c ^ 0xff000000);

? Even inline, or just a macro? (Or perhaps I'm missing something, and
(255 - a) is not always the same as (a ^ 0xff).

> +#define ASS_HEADER_AVOPTIONS(cls, obj) \
> +    { "res",  "script resolution", offsetof(cls, obj.res_x), AV_OPT_TYPE_IMAGE_SIZE, { .str = ASS_DEFAULT_PLAYRES_STR }, 1, INT_MAX, ASS_DS }, \
> +    { "font", "default font name", offsetof(cls, obj.style.font), AV_OPT_TYPE_STRING,  { .str = NULL }, 0, 0, ASS_DS }, \
> +    { "font_size", "default font name", offsetof(cls, obj.style.font_size), AV_OPT_TYPE_DOUBLE,  { .dbl = ASS_DEFAULT_FONT_SIZE }, 0, INT_MAX, ASS_DS }, \
> +    { "color", "default text color", offsetof(cls, obj.style.color), AV_OPT_TYPE_COLOR, { .str = ASS_DEFAULT_COLOR_STR }, 0, 0, ASS_DS }, \
> +    { "color2", "default secondary text color", offsetof(cls, obj.style.color2), AV_OPT_TYPE_COLOR, { .str = ASS_DEFAULT_COLOR_STR }, 0, 0, ASS_DS }, \
> +    { "outline_color", "default text outline color", offsetof(cls, obj.style.outline_color), AV_OPT_TYPE_COLOR, { .str = ASS_DEFAULT_BCOLOR_STR }, 0, 0, ASS_DS }, \
> +    { "back_color", "default text background/shadow color", offsetof(cls, obj.style.back_color), AV_OPT_TYPE_COLOR, { .str = ASS_DEFAULT_BCOLOR_STR }, 0, 0, ASS_DS }, \
> +    { "bold", "default text boldness (0/1/weight value)", offsetof(cls, obj.style.bold), AV_OPT_TYPE_INT, { .i64 = ASS_DEFAULT_BOLD }, 0, INT_MAX, ASS_DS }, \
> +    { "italic", "default text italics", offsetof(cls, obj.style.bold), AV_OPT_TYPE_BOOL, { .i64 = ASS_DEFAULT_ITALIC }, 0, 1, ASS_DS }, \
> +    { "underline", "default text italics", offsetof(cls, obj.style.underline), AV_OPT_TYPE_BOOL, { .i64 = ASS_DEFAULT_UNDERLINE }, 0, 1, ASS_DS }, \
> +    { "strikeout", "default text strikeout", offsetof(cls, obj.style.strikeout), AV_OPT_TYPE_BOOL, { .i64 = ASS_DEFAULT_STRIKEOUT }, 0, 1, ASS_DS }, \
> +    { "scale_x", "default horizontal text scale", offsetof(cls, obj.style.scale_x), AV_OPT_TYPE_DOUBLE, { .dbl = ASS_DEFAULT_SCALE_X }, 0, INT_MAX, ASS_DS }, \
> +    { "scale_y", "default vertical text scale", offsetof(cls, obj.style.scale_y), AV_OPT_TYPE_DOUBLE, { .dbl = ASS_DEFAULT_SCALE_Y }, 0, INT_MAX, ASS_DS }, \
> +    { "border_style", "default text border style", offsetof(cls, obj.style.border_style), AV_OPT_TYPE_INT, { .i64 = ASS_DEFAULT_BORDERSTYLE }, 1, 4, ASS_DS }, \
> +    { "outline", "default text outline width", offsetof(cls, obj.style.outline), AV_OPT_TYPE_DOUBLE, { .dbl = ASS_DEFAULT_OUTLINE }, 0, INT_MAX, ASS_DS }, \
> +    { "shadow", "default text shadow drop", offsetof(cls, obj.style.shadow), AV_OPT_TYPE_DOUBLE, { .dbl = ASS_DEFAULT_SHADOW }, 0, INT_MAX, ASS_DS }, \
> +    { "alignment", "default text alignment", offsetof(cls, obj.style.alignment), AV_OPT_TYPE_INT, { .i64 = ASS_DEFAULT_ALIGNMENT }, 1, 9, ASS_DS }, \
> +    { "margin_l", "default left margin", offsetof(cls, obj.style.margin_l), AV_OPT_TYPE_INT, { .i64 = ASS_DEFAULT_MARGINL }, 0, INT_MAX, ASS_DS }, \
> +    { "margin_r", "default right margin", offsetof(cls, obj.style.margin_r), AV_OPT_TYPE_INT, { .i64 = ASS_DEFAULT_MARGINR }, 0, INT_MAX, ASS_DS }, \
> +    { "margin_v", "default vertical margin", offsetof(cls, obj.style.margin_v), AV_OPT_TYPE_INT, { .i64 = ASS_DEFAULT_MARGINV }, 0, INT_MAX, ASS_DS }, \

To make this more readable, you should also MACROify the "offsetof(cls,
obj.X)", as many other options sections do it, and perhaps align the
entries' columns.

Moritz
Ridley Combs Jan. 29, 2020, 4:42 a.m. UTC | #2
> On Jan 27, 2020, at 08:28, Moritz Barsnick <barsnick@gmx.net> wrote:
> 
> On Fri, Jan 24, 2020 at 20:01:49 -0600, rcombs wrote:
>> +static int invert_ass_alpha(uint32_t c)
>> +{
>> +    uint32_t a = c >> 24;
>> +    return ((255 - a) << 24) | (c & 0xffffff);
>> +}
> 
> You're inverting the "leftmost" 8 bits of c? Can't you just make this
> 
> return (c ^ 0xff000000);
> 
> ? Even inline, or just a macro? (Or perhaps I'm missing something, and
> (255 - a) is not always the same as (a ^ 0xff).

These are indeed equivalent, but I thought this more clearly conveyed the intent here ("convert between 0=transparent and 0=opaque by inverting the 0-255 alpha field").

> 
>> +#define ASS_HEADER_AVOPTIONS(cls, obj) \
>> +    { "res",  "script resolution", offsetof(cls, obj.res_x), AV_OPT_TYPE_IMAGE_SIZE, { .str = ASS_DEFAULT_PLAYRES_STR }, 1, INT_MAX, ASS_DS }, \
>> +    { "font", "default font name", offsetof(cls, obj.style.font), AV_OPT_TYPE_STRING,  { .str = NULL }, 0, 0, ASS_DS }, \
>> +    { "font_size", "default font name", offsetof(cls, obj.style.font_size), AV_OPT_TYPE_DOUBLE,  { .dbl = ASS_DEFAULT_FONT_SIZE }, 0, INT_MAX, ASS_DS }, \
>> +    { "color", "default text color", offsetof(cls, obj.style.color), AV_OPT_TYPE_COLOR, { .str = ASS_DEFAULT_COLOR_STR }, 0, 0, ASS_DS }, \
>> +    { "color2", "default secondary text color", offsetof(cls, obj.style.color2), AV_OPT_TYPE_COLOR, { .str = ASS_DEFAULT_COLOR_STR }, 0, 0, ASS_DS }, \
>> +    { "outline_color", "default text outline color", offsetof(cls, obj.style.outline_color), AV_OPT_TYPE_COLOR, { .str = ASS_DEFAULT_BCOLOR_STR }, 0, 0, ASS_DS }, \
>> +    { "back_color", "default text background/shadow color", offsetof(cls, obj.style.back_color), AV_OPT_TYPE_COLOR, { .str = ASS_DEFAULT_BCOLOR_STR }, 0, 0, ASS_DS }, \
>> +    { "bold", "default text boldness (0/1/weight value)", offsetof(cls, obj.style.bold), AV_OPT_TYPE_INT, { .i64 = ASS_DEFAULT_BOLD }, 0, INT_MAX, ASS_DS }, \
>> +    { "italic", "default text italics", offsetof(cls, obj.style.bold), AV_OPT_TYPE_BOOL, { .i64 = ASS_DEFAULT_ITALIC }, 0, 1, ASS_DS }, \
>> +    { "underline", "default text italics", offsetof(cls, obj.style.underline), AV_OPT_TYPE_BOOL, { .i64 = ASS_DEFAULT_UNDERLINE }, 0, 1, ASS_DS }, \
>> +    { "strikeout", "default text strikeout", offsetof(cls, obj.style.strikeout), AV_OPT_TYPE_BOOL, { .i64 = ASS_DEFAULT_STRIKEOUT }, 0, 1, ASS_DS }, \
>> +    { "scale_x", "default horizontal text scale", offsetof(cls, obj.style.scale_x), AV_OPT_TYPE_DOUBLE, { .dbl = ASS_DEFAULT_SCALE_X }, 0, INT_MAX, ASS_DS }, \
>> +    { "scale_y", "default vertical text scale", offsetof(cls, obj.style.scale_y), AV_OPT_TYPE_DOUBLE, { .dbl = ASS_DEFAULT_SCALE_Y }, 0, INT_MAX, ASS_DS }, \
>> +    { "border_style", "default text border style", offsetof(cls, obj.style.border_style), AV_OPT_TYPE_INT, { .i64 = ASS_DEFAULT_BORDERSTYLE }, 1, 4, ASS_DS }, \
>> +    { "outline", "default text outline width", offsetof(cls, obj.style.outline), AV_OPT_TYPE_DOUBLE, { .dbl = ASS_DEFAULT_OUTLINE }, 0, INT_MAX, ASS_DS }, \
>> +    { "shadow", "default text shadow drop", offsetof(cls, obj.style.shadow), AV_OPT_TYPE_DOUBLE, { .dbl = ASS_DEFAULT_SHADOW }, 0, INT_MAX, ASS_DS }, \
>> +    { "alignment", "default text alignment", offsetof(cls, obj.style.alignment), AV_OPT_TYPE_INT, { .i64 = ASS_DEFAULT_ALIGNMENT }, 1, 9, ASS_DS }, \
>> +    { "margin_l", "default left margin", offsetof(cls, obj.style.margin_l), AV_OPT_TYPE_INT, { .i64 = ASS_DEFAULT_MARGINL }, 0, INT_MAX, ASS_DS }, \
>> +    { "margin_r", "default right margin", offsetof(cls, obj.style.margin_r), AV_OPT_TYPE_INT, { .i64 = ASS_DEFAULT_MARGINR }, 0, INT_MAX, ASS_DS }, \
>> +    { "margin_v", "default vertical margin", offsetof(cls, obj.style.margin_v), AV_OPT_TYPE_INT, { .i64 = ASS_DEFAULT_MARGINV }, 0, INT_MAX, ASS_DS }, \
> 
> To make this more readable, you should also MACROify the "offsetof(cls,
> obj.X)", as many other options sections do it, and perhaps align the
> entries' columns.

I shied away from this to avoid polluting the global macro space (since this is in a header); I can do it anyway if you prefer (it's not like it's a public header, at least).

> 
> Moritz
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> 
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Moritz Barsnick Jan. 29, 2020, 9:15 a.m. UTC | #3
On Tue, Jan 28, 2020 at 22:42:33 -0600, rcombs wrote:
> > You're inverting the "leftmost" 8 bits of c? Can't you just make this
> > return (c ^ 0xff000000);
> > ? Even inline, or just a macro? (Or perhaps I'm missing something, and
> > (255 - a) is not always the same as (a ^ 0xff).
>

> These are indeed equivalent, but I thought this more clearly conveyed
> the intent here ("convert between 0=transparent and 0=opaque by
> inverting the 0-255 alpha field").

Whatever is more clear to you. ;) I though it overcomplicated the whole
process, but perhaps it doesn't matter (I don't decide whether it
does).

> > To make this more readable, you should also MACROify the "offsetof(cls,
> > obj.X)", as many other options sections do it, and perhaps align the
> > entries' columns.
>
> I shied away from this to avoid polluting the global macro space
> (since this is in a header); I can do it anyway if you prefer (it's
> not like it's a public header, at least).

As you write, it's not global space. Anyway, I recall seeing such
macros (for multiple use of options structs) in the actual
implementation (*.c), not the declaration. If that reduces your clutter
concern.

Cheers,
looking forward to other proper reviews of your contributions ;-)
Moritz
diff mbox series

Patch

diff --git a/libavcodec/ass.c b/libavcodec/ass.c
index da05a83d69..65942a2567 100644
--- a/libavcodec/ass.c
+++ b/libavcodec/ass.c
@@ -24,57 +24,119 @@ 
 #include "libavutil/avassert.h"
 #include "libavutil/avstring.h"
 #include "libavutil/bprint.h"
+#include "libavutil/bswap.h"
 #include "libavutil/common.h"
 
+static int invert_ass_alpha(uint32_t c)
+{
+    uint32_t a = c >> 24;
+    return ((255 - a) << 24) | (c & 0xffffff);
+}
+
+#define CL_FF2ASS(c) invert_ass_alpha(av_le2ne32(c))
+#define CL_ASS2FF(c) av_le2ne32(invert_ass_alpha(c))
+
+int ff_ass_bprint_style(AVBPrint *buf, const FFASSStyle *style)
+{
+    av_bprintf(buf,
+               "Style: "
+               "%s,"                  /* Name */
+               "%s,%g,"               /* Font{name,size} */
+               "&H%x,&H%x,&H%x,&H%x," /* {Primary,Secondary,Outline,Back}Colour */
+               "%d,%d,%d,%d,"         /* Bold, Italic, Underline, StrikeOut */
+               "%g,%g,"               /* Scale{X,Y} */
+               "%g,%g,"               /* Spacing, Angle */
+               "%d,%g,%g,"            /* BorderStyle, Outline, Shadow */
+               "%d,%d,%d,%d,"         /* Alignment, Margin[LRV] */
+               "0\r\n",               /* Encoding */
+               style->name ? style->name : "Default",
+               style->font ? style->font : ASS_DEFAULT_FONT, style->font_size,
+               CL_FF2ASS(style->color), CL_FF2ASS(style->color2),
+               CL_FF2ASS(style->outline_color), CL_FF2ASS(style->back_color),
+               style->bold, style->italic, style->underline, style->strikeout,
+               style->scale_x, style->scale_y,
+               style->spacing, style->angle,
+               style->border_style, style->outline, style->shadow,
+               style->alignment, style->margin_l, style->margin_r, style->margin_v);
+
+    return 0; // Not currently possible to detect bprintf errors
+}
+
+int ff_ass_subtitle_header2(AVCodecContext *avctx, int res_x, int res_y, const FFASSStyle *style)
+{
+    int ret = 0;
+    AVBPrint buf;
+    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
+    av_bprintf(&buf,
+               "[Script Info]\r\n"
+               "; Script generated by FFmpeg/Lavc%s\r\n"
+               "ScriptType: v4.00+\r\n"
+               "PlayResX: %d\r\n"
+               "PlayResY: %d\r\n"
+               "\r\n"
+               "[V4+ Styles]\r\n"
+
+               /* ASSv4+ header */
+               "Format: Name, "
+               "Fontname, Fontsize, "
+               "PrimaryColour, SecondaryColour, OutlineColour, BackColour, "
+               "Bold, Italic, Underline, StrikeOut, "
+               "ScaleX, ScaleY, "
+               "Spacing, Angle, "
+               "BorderStyle, Outline, Shadow, "
+               "Alignment, MarginL, MarginR, MarginV, "
+               "Encoding\r\n",
+               !(avctx->flags & AV_CODEC_FLAG_BITEXACT) ? AV_STRINGIFY(LIBAVCODEC_VERSION) : "",
+               res_x, res_y);
+
+    if ((ret = ff_ass_bprint_style(&buf, style) < 0))
+        goto fail;
+
+    av_bprintf(&buf,
+               "\r\n"
+               "[Events]\r\n"
+               "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\r\n");
+
+    if ((ret = av_bprint_finalize(&buf, (char**)&avctx->subtitle_header)) < 0)
+        return ret;
+
+    avctx->subtitle_header_size = buf.len;
+    return 0;
+
+fail:
+    av_bprint_finalize(&buf, NULL);
+    return ret;
+}
+
+int ff_ass_subtitle_header_from_opts(AVCodecContext *avctx, const FFASSHeaderOptions *opts)
+{
+    return ff_ass_subtitle_header2(avctx, opts->res_x, opts->res_y, &opts->style);
+}
+
 int ff_ass_subtitle_header(AVCodecContext *avctx,
                            const char *font, int font_size,
                            int color, int back_color,
                            int bold, int italic, int underline,
                            int border_style, int alignment)
 {
-    avctx->subtitle_header = av_asprintf(
-             "[Script Info]\r\n"
-             "; Script generated by FFmpeg/Lavc%s\r\n"
-             "ScriptType: v4.00+\r\n"
-             "PlayResX: %d\r\n"
-             "PlayResY: %d\r\n"
-             "\r\n"
-             "[V4+ Styles]\r\n"
-
-             /* ASSv4 header */
-             "Format: Name, "
-             "Fontname, Fontsize, "
-             "PrimaryColour, SecondaryColour, OutlineColour, BackColour, "
-             "Bold, Italic, Underline, StrikeOut, "
-             "ScaleX, ScaleY, "
-             "Spacing, Angle, "
-             "BorderStyle, Outline, Shadow, "
-             "Alignment, MarginL, MarginR, MarginV, "
-             "Encoding\r\n"
-
-             "Style: "
-             "Default,"             /* Name */
-             "%s,%d,"               /* Font{name,size} */
-             "&H%x,&H%x,&H%x,&H%x," /* {Primary,Secondary,Outline,Back}Colour */
-             "%d,%d,%d,0,"          /* Bold, Italic, Underline, StrikeOut */
-             "100,100,"             /* Scale{X,Y} */
-             "0,0,"                 /* Spacing, Angle */
-             "%d,1,0,"              /* BorderStyle, Outline, Shadow */
-             "%d,10,10,10,"         /* Alignment, Margin[LRV] */
-             "0\r\n"                /* Encoding */
-
-             "\r\n"
-             "[Events]\r\n"
-             "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\r\n",
-             !(avctx->flags & AV_CODEC_FLAG_BITEXACT) ? AV_STRINGIFY(LIBAVCODEC_VERSION) : "",
-             ASS_DEFAULT_PLAYRESX, ASS_DEFAULT_PLAYRESY,
-             font, font_size, color, color, back_color, back_color,
-             -bold, -italic, -underline, border_style, alignment);
-
-    if (!avctx->subtitle_header)
-        return AVERROR(ENOMEM);
-    avctx->subtitle_header_size = strlen(avctx->subtitle_header);
-    return 0;
+    return ff_ass_subtitle_header2(avctx, ASS_DEFAULT_PLAYRESX, ASS_DEFAULT_PLAYRESY,
+                                   &(FFASSStyle){
+                                       .name = "Default",
+                                       .font = font, .font_size = font_size,
+                                       .color = CL_ASS2FF(color), .color2 = CL_ASS2FF(color),
+                                       .outline_color = CL_ASS2FF(back_color), .back_color = CL_ASS2FF(back_color),
+                                       .bold = bold, .italic = italic,
+                                       .underline = underline, .strikeout = 0,
+                                       .scale_x = 100, .scale_y = 100,
+                                       .spacing = 0, .angle = 0,
+                                       .border_style = border_style,
+                                       .outline = ASS_DEFAULT_OUTLINE,
+                                       .shadow = ASS_DEFAULT_SHADOW,
+                                       .alignment = alignment,
+                                       .margin_l = ASS_DEFAULT_MARGINL,
+                                       .margin_r = ASS_DEFAULT_MARGINR,
+                                       .margin_v = ASS_DEFAULT_MARGINV,
+                                   });
 }
 
 int ff_ass_subtitle_header_default(AVCodecContext *avctx)
@@ -90,6 +152,12 @@  int ff_ass_subtitle_header_default(AVCodecContext *avctx)
                                   ASS_DEFAULT_ALIGNMENT);
 }
 
+int ff_ass_subtitle_header_options(AVCodecContext *avctx)
+{
+    FFASSDecoderContext *s = avctx->priv_data;
+    return ff_ass_subtitle_header_from_opts(avctx, &s->common);
+}
+
 char *ff_ass_get_dialog(int readorder, int layer, const char *style,
                         const char *speaker, const char *text)
 {
diff --git a/libavcodec/ass.h b/libavcodec/ass.h
index 314b43b686..454e5663ed 100644
--- a/libavcodec/ass.h
+++ b/libavcodec/ass.h
@@ -24,9 +24,11 @@ 
 
 #include "avcodec.h"
 #include "libavutil/bprint.h"
+#include "libavutil/opt.h"
 
 #define ASS_DEFAULT_PLAYRESX 384
 #define ASS_DEFAULT_PLAYRESY 288
+#define ASS_DEFAULT_PLAYRES_STR "384x288"
 
 /**
  * @name Default values for ASS style
@@ -35,29 +37,120 @@ 
 #define ASS_DEFAULT_FONT        "Arial"
 #define ASS_DEFAULT_FONT_SIZE   16
 #define ASS_DEFAULT_COLOR       0xffffff
+#define ASS_DEFAULT_COLOR_STR   "#ffffff"
 #define ASS_DEFAULT_BACK_COLOR  0
+#define ASS_DEFAULT_BCOLOR_STR  "#000000"
 #define ASS_DEFAULT_BOLD        0
 #define ASS_DEFAULT_ITALIC      0
 #define ASS_DEFAULT_UNDERLINE   0
-#define ASS_DEFAULT_ALIGNMENT   2
+#define ASS_DEFAULT_STRIKEOUT   0
+#define ASS_DEFAULT_SCALE_X     100.
+#define ASS_DEFAULT_SCALE_Y     100.
 #define ASS_DEFAULT_BORDERSTYLE 1
+#define ASS_DEFAULT_OUTLINE     1
+#define ASS_DEFAULT_SHADOW      0
+#define ASS_DEFAULT_ALIGNMENT   2
+#define ASS_DEFAULT_MARGINL     10
+#define ASS_DEFAULT_MARGINR     10
+#define ASS_DEFAULT_MARGINV     10
 /** @} */
 
+typedef struct FFASSStyle {
+    const char *name;
+    const char *font; double font_size;
+    uint32_t color, color2, outline_color, back_color; // Big-endian RGBA
+    int bold, italic, underline, strikeout;
+    double scale_x, scale_y;
+    double spacing, angle;
+    int border_style;
+    double outline, shadow;
+    int alignment;
+    int margin_l, margin_r, margin_v;
+} FFASSStyle;
+
+typedef struct FFASSHeaderOptions {
+    int res_x, res_y;
+    FFASSStyle style;
+} FFASSHeaderOptions;
+
+#define ASS_DS AV_OPT_FLAG_DECODING_PARAM | AV_OPT_FLAG_SUBTITLE_PARAM
+
+#define ASS_HEADER_AVOPTIONS(cls, obj) \
+    { "res",  "script resolution", offsetof(cls, obj.res_x), AV_OPT_TYPE_IMAGE_SIZE, { .str = ASS_DEFAULT_PLAYRES_STR }, 1, INT_MAX, ASS_DS }, \
+    { "font", "default font name", offsetof(cls, obj.style.font), AV_OPT_TYPE_STRING,  { .str = NULL }, 0, 0, ASS_DS }, \
+    { "font_size", "default font name", offsetof(cls, obj.style.font_size), AV_OPT_TYPE_DOUBLE,  { .dbl = ASS_DEFAULT_FONT_SIZE }, 0, INT_MAX, ASS_DS }, \
+    { "color", "default text color", offsetof(cls, obj.style.color), AV_OPT_TYPE_COLOR, { .str = ASS_DEFAULT_COLOR_STR }, 0, 0, ASS_DS }, \
+    { "color2", "default secondary text color", offsetof(cls, obj.style.color2), AV_OPT_TYPE_COLOR, { .str = ASS_DEFAULT_COLOR_STR }, 0, 0, ASS_DS }, \
+    { "outline_color", "default text outline color", offsetof(cls, obj.style.outline_color), AV_OPT_TYPE_COLOR, { .str = ASS_DEFAULT_BCOLOR_STR }, 0, 0, ASS_DS }, \
+    { "back_color", "default text background/shadow color", offsetof(cls, obj.style.back_color), AV_OPT_TYPE_COLOR, { .str = ASS_DEFAULT_BCOLOR_STR }, 0, 0, ASS_DS }, \
+    { "bold", "default text boldness (0/1/weight value)", offsetof(cls, obj.style.bold), AV_OPT_TYPE_INT, { .i64 = ASS_DEFAULT_BOLD }, 0, INT_MAX, ASS_DS }, \
+    { "italic", "default text italics", offsetof(cls, obj.style.bold), AV_OPT_TYPE_BOOL, { .i64 = ASS_DEFAULT_ITALIC }, 0, 1, ASS_DS }, \
+    { "underline", "default text italics", offsetof(cls, obj.style.underline), AV_OPT_TYPE_BOOL, { .i64 = ASS_DEFAULT_UNDERLINE }, 0, 1, ASS_DS }, \
+    { "strikeout", "default text strikeout", offsetof(cls, obj.style.strikeout), AV_OPT_TYPE_BOOL, { .i64 = ASS_DEFAULT_STRIKEOUT }, 0, 1, ASS_DS }, \
+    { "scale_x", "default horizontal text scale", offsetof(cls, obj.style.scale_x), AV_OPT_TYPE_DOUBLE, { .dbl = ASS_DEFAULT_SCALE_X }, 0, INT_MAX, ASS_DS }, \
+    { "scale_y", "default vertical text scale", offsetof(cls, obj.style.scale_y), AV_OPT_TYPE_DOUBLE, { .dbl = ASS_DEFAULT_SCALE_Y }, 0, INT_MAX, ASS_DS }, \
+    { "border_style", "default text border style", offsetof(cls, obj.style.border_style), AV_OPT_TYPE_INT, { .i64 = ASS_DEFAULT_BORDERSTYLE }, 1, 4, ASS_DS }, \
+    { "outline", "default text outline width", offsetof(cls, obj.style.outline), AV_OPT_TYPE_DOUBLE, { .dbl = ASS_DEFAULT_OUTLINE }, 0, INT_MAX, ASS_DS }, \
+    { "shadow", "default text shadow drop", offsetof(cls, obj.style.shadow), AV_OPT_TYPE_DOUBLE, { .dbl = ASS_DEFAULT_SHADOW }, 0, INT_MAX, ASS_DS }, \
+    { "alignment", "default text alignment", offsetof(cls, obj.style.alignment), AV_OPT_TYPE_INT, { .i64 = ASS_DEFAULT_ALIGNMENT }, 1, 9, ASS_DS }, \
+    { "margin_l", "default left margin", offsetof(cls, obj.style.margin_l), AV_OPT_TYPE_INT, { .i64 = ASS_DEFAULT_MARGINL }, 0, INT_MAX, ASS_DS }, \
+    { "margin_r", "default right margin", offsetof(cls, obj.style.margin_r), AV_OPT_TYPE_INT, { .i64 = ASS_DEFAULT_MARGINR }, 0, INT_MAX, ASS_DS }, \
+    { "margin_v", "default vertical margin", offsetof(cls, obj.style.margin_v), AV_OPT_TYPE_INT, { .i64 = ASS_DEFAULT_MARGINV }, 0, INT_MAX, ASS_DS }, \
+
+#define ASS_GENERIC_OPTIONS(name) \
+static const AVOption name##_options[] = { \
+    ASS_HEADER_AVOPTIONS(FFASSDecoderContext, common) \
+    { NULL }, \
+};
+
 typedef struct FFASSDecoderContext {
+    AVClass *class;
     int readorder;
+    FFASSHeaderOptions common;
 } FFASSDecoderContext;
 
+#define ASS_GENERIC_CLASS(name, StringName) \
+ASS_GENERIC_OPTIONS(name) \
+\
+static const AVClass name##_decoder_class = { \
+    .class_name = #StringName " subtitle decoder", \
+    .item_name  = av_default_item_name, \
+    .option     = name##_options, \
+    .version    = LIBAVUTIL_VERSION_INT, \
+};
+
+
+/**
+ * Convert an FFASSStyle to ASS text
+ */
+int ff_ass_bprint_style(AVBPrint *buf, const FFASSStyle *style);
+
 /**
  * Generate a suitable AVCodecContext.subtitle_header for SUBTITLE_ASS.
  *
  * @param avctx pointer to the AVCodecContext
+ * @param res_x horizontal script resolution
+ * @param res_y vertical script resolution
+ * @param style the default style to use
+ * @return >= 0 on success otherwise an error code <0
+ */
+int ff_ass_subtitle_header2(AVCodecContext *avctx,
+                            int res_x, int res_y,
+                            const FFASSStyle *style);
+
+int ff_ass_subtitle_header_from_opts(AVCodecContext *avctx, const FFASSHeaderOptions *opts);
+
+/**
+ * Simple form of ff_ass_subtitle_header2
+ *
+ * @param avctx pointer to the AVCodecContext
  * @param font name of the default font face to use
  * @param font_size default font size to use
  * @param color default text color to use (ABGR)
  * @param back_color default background color to use (ABGR)
  * @param bold 1 for bold text, 0 for normal text
  * @param italic 1 for italic text, 0 for normal text
- * @param underline 1 for underline text, 0 for normal text
+ * @param underline 1 for underlined text, 0 for normal text
  * @param alignment position of the text (left, center, top...), defined after
  *                  the layout of the numpad (1-3 sub, 4-6 mid, 7-9 top)
  * @return >= 0 on success otherwise an error code <0
@@ -77,6 +170,15 @@  int ff_ass_subtitle_header(AVCodecContext *avctx,
  */
 int ff_ass_subtitle_header_default(AVCodecContext *avctx);
 
+/**
+ * Generate a suitable AVCodecContext.subtitle_header for SUBTITLE_ASS
+ * with style configured by FFASSDecoderContext AVOptions.
+ *
+ * @param avctx pointer to the AVCodecContext
+ * @return >= 0 on success otherwise an error code <0
+ */
+int ff_ass_subtitle_header_options(AVCodecContext *avctx);
+
 /**
  * Craft an ASS dialog string.
  */