Commit c67cbdc0be for ffmpeg

commit c67cbdc0bec0a0dba71c7086aa8e8feb529c4d96
Author: Michael Niedermayer <michael@niedermayer.cc>
Date:   Thu Oct 15 18:21:15 2026 +0200

    ffplay: parse the list styles once per canvas

    The styles of a layout go into one ASS track as its header, and the
    script of a strip holds only its events, so a strip render parses the
    events alone, the cache keys shrink to them, and the frame is the whole
    canvas, which lets the metadata popup be one strip instead of a strip
    per line that also draws the line above it.

    Assisted-by: Claude

diff --git a/fftools/ffplay_list.c b/fftools/ffplay_list.c
index b3a544e986..de1ff54891 100644
--- a/fftools/ffplay_list.c
+++ b/fftools/ffplay_list.c
@@ -88,6 +88,7 @@ struct ListView {
 #if CONFIG_LIBASS
     ASS_Library *library;
     ASS_Renderer *ass;
+    ASS_Track *track;
 #endif
     ListStrip *strips;
     int nb_strips;
@@ -240,7 +241,7 @@ static void bprint_cell(AVBPrint *script, const char *style, int x, int y, int r
 }

 typedef struct ListLayout {
-    int font, line, width, height, canvas_h, strip_h, nb_rows, first;
+    int font, line, width, height, canvas_h, nb_rows, first;
     int button_x[LIST_SORT_NB], button_y[LIST_SORT_NB];
     int search_x, search_y, rows_y, text_x, text_right, length_x, detail_x, canvas_w;
 } ListLayout;
@@ -280,7 +281,6 @@ static ListLayout list_layout(const ListView *lv)
     l.search_y   = y;
     l.rows_y     = lv->typing ? y + l.font * 3 / 2 : l.button_y[LIST_SORT_NB - 1] + l.font * 3 / 2;
     l.canvas_h   = FFMIN(y + l.font * 2 + LIST_ROWS * l.line, lv->height - l.font * 2);
-    l.strip_h    = FFMAX(l.line, y + l.font * 3 / 2);
     l.nb_rows    = FFMIN(lv->nb_rows, FFMAX(1, (l.canvas_h - l.rows_y - l.font / 2) / l.line));
     l.first      = av_clip(lv->selected - l.nb_rows / 2, 0, lv->nb_rows - l.nb_rows);
     l.height     = l.rows_y + l.nb_rows * l.line + l.font / 2;
@@ -347,7 +347,7 @@ static void bprint_styles(AVBPrint *script, const ListLayout *l)
                "Style: Ring,Sans,%d,&HFF000000,&H00A0A0A0,0,%d,7\n"
                "Style: Detail,Sans,%d,&H00FFFFFF,&H00000000,0,%d,7\n\n"
                "[Events]\nFormat: Start, End, Style, Text\n",
-               l->canvas_w, l->strip_h, l->font, l->font, l->font / 16 + 1, l->font, l->font / 16 + 1,
+               l->canvas_w, l->canvas_h, l->font, l->font, l->font / 16 + 1, l->font, l->font / 16 + 1,
                l->font * 4 / 5, l->font / 16 + 1, l->font * 4 / 5, l->font / 16 + 1, l->font, l->font, l->font / 16 + 1,
                l->font, l->font, l->font / 16 + 1, l->font * 4 / 5, l->font / 16 + 1);
 }
@@ -485,8 +485,8 @@ static int strip_add(ListView *lv, ListStrip **strips, int *nb, SDL_Rect rect, u
     return 0;
 }

-/* the strips the list consists of right now: the header, each visible row, the bottom padding and the lines of the metadata
- * popup, beside the list or over its rows when the window leaves no room there */
+/* the strips the list consists of right now: the header, each visible row, the bottom padding and the metadata popup,
+ * beside the list or over its rows when the window leaves no room there */
 static int list_strips(ListView *lv, const ListLayout *l, ListStrip **strips, int *nb)
 {
     AVBPrint script;
@@ -501,7 +501,6 @@ static int list_strips(ListView *lv, const ListLayout *l, ListStrip **strips, in
             rect.h = l->font / 2;
         rect.h = FFMIN(rect.h, l->canvas_h - rect.y);
         av_bprint_clear(&script);
-        bprint_styles(&script, l);
         if (!i)
             bprint_header_strip(lv, &script, l);
         else if (row >= 0)
@@ -510,32 +509,20 @@ static int list_strips(ListView *lv, const ListLayout *l, ListStrip **strips, in
     }
     if (ret >= 0 && lv->hover.detail) {
         const AVDictionary *metadata = lv->entries[lv->rows[lv->hover.row]].metadata;
-        const AVDictionaryEntry *above = NULL, *tag = av_dict_iterate(metadata, NULL);
+        const AVDictionaryEntry *tag = NULL;
         int beside = l->canvas_w - l->width >= l->font * 12;
         int height = FFMIN(av_dict_count(metadata) * l->font + l->font / 2, l->canvas_h);
         int x      = beside ? l->width + l->font / 2 : l->text_x;
         int right  = beside ? l->canvas_w : l->length_x - l->font / 2;
-        int y      = av_clip(l->rows_y + (lv->hover.row - l->first) * l->line, 0, l->canvas_h - height);
-
-        /* the descenders of a line reach into the line below, so each line's strip also draws the line above it */
-        for (int top = 0; ret >= 0 && top < height && (above || tag); top += l->font) {
-            const AVDictionaryEntry *lines[2] = { above, tag };
-            SDL_Rect rect = { x, y + top, right - x, FFMIN(l->font, height - top) };
-
-            av_bprint_clear(&script);
-            bprint_styles(&script, l);
-            for (int i = 0; i < 2; i++) {
-                if (!lines[i])
-                    continue;
-                bprint_cell(&script, "Detail", x + l->font / 2, l->font / 4 + (i - 1) * l->font, x + l->font * 7, rect.h,
-                            lines[i]->key);
-                bprint_cell(&script, "Detail", x + l->font * 15 / 2, l->font / 4 + (i - 1) * l->font, right - l->font / 4,
-                            rect.h, lines[i]->value);
-            }
-            ret   = strip_add(lv, strips, nb, rect, POPUP_COLOR, &script);
-            above = tag;
-            tag   = tag ? av_dict_iterate(metadata, tag) : NULL;
+        SDL_Rect rect = { x, av_clip(l->rows_y + (lv->hover.row - l->first) * l->line, 0, l->canvas_h - height),
+                          right - x, height };
+
+        av_bprint_clear(&script);
+        for (int top = l->font / 4; top < height + l->font && (tag = av_dict_iterate(metadata, tag)); top += l->font) {
+            bprint_cell(&script, "Detail", x + l->font / 2, top, x + l->font * 7, height, tag->key);
+            bprint_cell(&script, "Detail", x + l->font * 15 / 2, top, right - l->font / 4, height, tag->value);
         }
+        ret = strip_add(lv, strips, nb, rect, POPUP_COLOR, &script);
     }
     av_bprint_finalize(&script, NULL);
     return ret;
@@ -554,6 +541,11 @@ static void list_discard(ListView *lv)
     if (lv->texture)
         SDL_DestroyTexture(lv->texture);
     lv->texture = NULL;
+#if CONFIG_LIBASS
+    if (lv->track)
+        ass_free_track(lv->track);
+    lv->track = NULL;
+#endif
 }

 #if CONFIG_LIBASS
@@ -568,10 +560,12 @@ static void libass_log(int libass_level, const char *fmt, va_list args, void *op
 }
 #endif

-/* the bitmap, the texture it is uploaded to, and libass rendering a canvas wide strip at a time */
+/* the bitmap, the texture it is uploaded to, and libass with the styles of the layout, rendering a strip at a time */
 static int list_configure(ListView *lv, const ListLayout *l, const SDL_Rect *canvas)
 {
 #if CONFIG_LIBASS
+    AVBPrint styles;
+
     list_discard(lv);
     lv->rect = *canvas;
     if (l->canvas_w <= 0 || l->canvas_h <= 0 || l->canvas_w > INT_MAX / 4 / l->canvas_h)
@@ -595,8 +589,15 @@ static int list_configure(ListView *lv, const ListLayout *l, const SDL_Rect *can
         }
         ass_set_fonts(lv->ass, NULL, NULL, 1, NULL, 1);
     }
-    ass_set_frame_size(lv->ass, l->canvas_w, l->strip_h);
-    ass_set_storage_size(lv->ass, l->canvas_w, l->strip_h);
+    av_bprint_init(&styles, 0, AV_BPRINT_SIZE_UNLIMITED);
+    bprint_styles(&styles, l);
+    if (av_bprint_is_complete(&styles) && (lv->track = ass_new_track(lv->library)))
+        ass_process_codec_private(lv->track, styles.str, styles.len);
+    av_bprint_finalize(&styles, NULL);
+    if (!lv->track)
+        return AVERROR(ENOMEM);
+    ass_set_frame_size(lv->ass, l->canvas_w, l->canvas_h);
+    ass_set_storage_size(lv->ass, l->canvas_w, l->canvas_h);
     return 0;
 #else
     if (!lv->libass_missing_reported++)
@@ -625,17 +626,16 @@ static uint32_t blend_over(uint32_t dst, uint32_t rgb, int a, const uint32_t *re
 static int strip_render(ListView *lv, ListCacheEntry *entry)
 {
 #if CONFIG_LIBASS
-    ASS_Track *track = ass_read_memory(lv->library, entry->script, strlen(entry->script), NULL);
     uint32_t *pixels = (uint32_t *)entry->pixels, reciprocal[256] = { 1 << 16 };
     int w = entry->w, h = entry->h;

-    if (!track)
-        return AVERROR(EINVAL);
+    ass_flush_events(lv->track);
+    ass_process_data(lv->track, entry->script, strlen(entry->script));
     for (int a = 1; a < 256; a++)
         reciprocal[a] = ((255 << 16) + a - 1) / a;
     for (int i = 0; i < w * h; i++)
         pixels[i] = entry->background;
-    for (const ASS_Image *image = ass_render_frame(lv->ass, track, 0, NULL); image; image = image->next) {
+    for (const ASS_Image *image = ass_render_frame(lv->ass, lv->track, 0, NULL); image; image = image->next) {
         int opacity = 255 - (image->color & 0xFF);
         int x0 = FFMAX(image->dst_x, entry->x), x1 = FFMIN(image->dst_x + image->w, entry->x + w);

@@ -651,7 +651,6 @@ static int strip_render(ListView *lv, ListCacheEntry *entry)
             }
         }
     }
-    ass_free_track(track);
     return 0;
 #else
     return AVERROR(ENOSYS);