@@ -41,17 +41,34 @@ var (
4141 ttmlRegexpOffsetTime = regexp .MustCompile (`^(\d+(\.\d+)?)(h|m|s|ms|f|t)$` )
4242)
4343
44+ type TTMLInBodyDiv struct {
45+ XMLName xml.Name `xml:"div"`
46+ Subtitles []TTMLInSubtitle `xml:"p"`
47+
48+ Region string `xml:"region,attr,omitempty"`
49+ Style string `xml:"style,attr,omitempty"`
50+ TTMLInStyleAttributes
51+ }
52+ type TTMLInBody struct {
53+ XMLName xml.Name `xml:"body"`
54+ Divs []TTMLInBodyDiv `xml:"div"`
55+
56+ Region string `xml:"region,attr,omitempty"`
57+ Style string `xml:"style,attr,omitempty"`
58+ TTMLInStyleAttributes
59+ }
60+
4461// TTMLIn represents an input TTML that must be unmarshaled
4562// We split it from the output TTML as we can't add strict namespace without breaking retrocompatibility
4663type TTMLIn struct {
47- Framerate int `xml:"frameRate,attr"`
48- Lang string `xml:"lang,attr"`
49- Metadata TTMLInMetadata `xml:"head>metadata"`
50- Regions []TTMLInRegion `xml:"head>layout>region"`
51- Styles []TTMLInStyle `xml:"head>styling>style"`
52- Subtitles [] TTMLInSubtitle `xml:"body>div>p "`
53- Tickrate int `xml:"tickRate,attr"`
54- XMLName xml.Name `xml:"tt"`
64+ Framerate int `xml:"frameRate,attr"`
65+ Lang string `xml:"lang,attr"`
66+ Metadata TTMLInMetadata `xml:"head>metadata"`
67+ Regions []TTMLInRegion `xml:"head>layout>region"`
68+ Styles []TTMLInStyle `xml:"head>styling>style"`
69+ Body TTMLInBody `xml:"body"`
70+ Tickrate int `xml:"tickRate,attr"`
71+ XMLName xml.Name `xml:"tt"`
5572}
5673
5774// metadata returns the Metadata of the TTML
@@ -386,94 +403,121 @@ func ReadFromTTML(i io.Reader) (o *Subtitles, err error) {
386403 }
387404
388405 // Loop through subtitles
389- for _ , ts := range ttml .Subtitles {
390- // Init item
391- ts . Begin . framerate = ttml . Framerate
392- ts . Begin . tickrate = ttml . Tickrate
393- ts . End . framerate = ttml . Framerate
394- ts . End . tickrate = ttml . Tickrate
395-
396- var s = & Item {
397- EndAt : ts . End . duration (),
398- InlineStyle : ts . TTMLInStyleAttributes . styleAttributes (),
399- StartAt : ts . Begin . duration (),
406+ bodyInlineStyle := ttml .Body . TTMLInStyleAttributes . styleAttributes ()
407+ for _ , div := range ttml . Body . Divs {
408+ divInlineStyle := div . TTMLInStyleAttributes . styleAttributes ()
409+
410+ // Propagate styles from Body -> Div
411+ divInlineStyle . merge ( bodyInlineStyle )
412+ if div . Region == "" {
413+ div . Region = ttml . Body . Region
414+ }
415+ if div . Style == "" {
416+ div . Style = ttml . Body . Style
400417 }
418+ for _ , ts := range div .Subtitles {
419+ // Init item
420+ ts .Begin .framerate = ttml .Framerate
421+ ts .Begin .tickrate = ttml .Tickrate
422+ ts .End .framerate = ttml .Framerate
423+ ts .End .tickrate = ttml .Tickrate
424+
425+ itemInlineStyle := ts .TTMLInStyleAttributes .styleAttributes ()
426+
427+ // Propagate styles from Body -> Div -> Item.
428+ // If the item has its own Region, Style, or InlineStyle, it overrides the Div's.
429+ // This ensures all relevant styles are preserved at the item level,
430+ // maintaining compatibility with existing logic that relies on the Subtitles structure.
431+ itemInlineStyle .merge (divInlineStyle )
432+ if ts .Region == "" {
433+ ts .Region = div .Region
434+ }
435+ if ts .Style == "" {
436+ ts .Style = div .Style
437+ }
401438
402- // Add region
403- if len (ts .Region ) > 0 {
404- if _ , ok := o .Regions [ts .Region ]; ! ok {
405- err = fmt .Errorf ("astisub: Region %s requested by subtitle between %s and %s doesn't exist" , ts .Region , s .StartAt , s .EndAt )
406- return
439+ var s = & Item {
440+ EndAt : ts .End .duration (),
441+ InlineStyle : itemInlineStyle ,
442+ StartAt : ts .Begin .duration (),
407443 }
408- s .Region = o .Regions [ts .Region ]
409- }
410444
411- // Add style
412- if len (ts .Style ) > 0 {
413- if _ , ok := o .Styles [ts .Style ]; ! ok {
414- err = fmt .Errorf ("astisub: Style %s requested by subtitle between %s and %s doesn't exist" , ts .Style , s .StartAt , s .EndAt )
415- return
445+ // Add region
446+ if len (ts .Region ) > 0 {
447+ if _ , ok := o .Regions [ts .Region ]; ! ok {
448+ err = fmt .Errorf ("astisub: Region %s requested by subtitle between %s and %s doesn't exist" , ts .Region , s .StartAt , s .EndAt )
449+ return
450+ }
451+ s .Region = o .Regions [ts .Region ]
416452 }
417- s .Style = o .Styles [ts .Style ]
418- }
419453
420- // Remove items identation
421- lines := strings .Split (ts .Items , "\n " )
422- for i := 0 ; i < len (lines ); i ++ {
423- lines [i ] = strings .TrimLeftFunc (lines [i ], unicode .IsSpace )
424- }
454+ // Add style
455+ if len (ts .Style ) > 0 {
456+ if _ , ok := o .Styles [ts .Style ]; ! ok {
457+ err = fmt .Errorf ("astisub: Style %s requested by subtitle between %s and %s doesn't exist" , ts .Style , s .StartAt , s .EndAt )
458+ return
459+ }
460+ s .Style = o .Styles [ts .Style ]
461+ }
425462
426- // Unmarshal items
427- var items = TTMLInItems {}
428- if err = newTTMLXmlDecoder (strings .Join (lines , "" )).Decode (& items ); err != nil {
429- err = fmt .Errorf ("astisub: unmarshaling items failed: %w" , err )
430- return
431- }
463+ // Remove items identation
464+ lines := strings .Split (ts .Items , "\n " )
465+ for i := 0 ; i < len (lines ); i ++ {
466+ lines [i ] = strings .TrimLeftFunc (lines [i ], unicode .IsSpace )
467+ }
432468
433- // Loop through texts
434- var l = & Line {}
435- for _ , tt := range items {
436- // New line specified with the "br" tag
437- if strings .ToLower (tt .XMLName .Local ) == "br" {
438- s .Lines = append (s .Lines , * l )
439- l = & Line {}
440- continue
469+ // Unmarshal items
470+ var items = TTMLInItems {}
471+ if err = newTTMLXmlDecoder (strings .Join (lines , "" )).Decode (& items ); err != nil {
472+ err = fmt .Errorf ("astisub: unmarshaling items failed: %w" , err )
473+ return
441474 }
442475
443- // New line decoded as a line break. This can happen if there's a "br" tag within the text since
444- // since the go xml unmarshaler will unmarshal a "br" tag as a line break if the field has the
445- // chardata xml tag.
446- for idx , li := range strings .Split (tt .Text , "\n " ) {
447- // New line
448- if idx > 0 {
476+ // Loop through texts
477+ var l = & Line {}
478+ for _ , tt := range items {
479+ // New line specified with the "br" tag
480+ if strings .ToLower (tt .XMLName .Local ) == "br" {
449481 s .Lines = append (s .Lines , * l )
450482 l = & Line {}
483+ continue
451484 }
452485
453- // Init line item
454- var t = LineItem {
455- InlineStyle : tt .TTMLInStyleAttributes .styleAttributes (),
456- Text : li ,
457- }
486+ // New line decoded as a line break. This can happen if there's a "br" tag within the text since
487+ // since the go xml unmarshaler will unmarshal a "br" tag as a line break if the field has the
488+ // chardata xml tag.
489+ for idx , li := range strings .Split (tt .Text , "\n " ) {
490+ // New line
491+ if idx > 0 {
492+ s .Lines = append (s .Lines , * l )
493+ l = & Line {}
494+ }
458495
459- // Add style
460- if len (tt .Style ) > 0 {
461- if _ , ok := o .Styles [tt .Style ]; ! ok {
462- err = fmt .Errorf ("astisub: Style %s requested by item with text %s doesn't exist" , tt .Style , tt .Text )
463- return
496+ // Init line item
497+ var t = LineItem {
498+ InlineStyle : tt .TTMLInStyleAttributes .styleAttributes (),
499+ Text : li ,
464500 }
465- t .Style = o .Styles [tt .Style ]
501+
502+ // Add style
503+ if len (tt .Style ) > 0 {
504+ if _ , ok := o .Styles [tt .Style ]; ! ok {
505+ err = fmt .Errorf ("astisub: Style %s requested by item with text %s doesn't exist" , tt .Style , tt .Text )
506+ return
507+ }
508+ t .Style = o .Styles [tt .Style ]
509+ }
510+
511+ // Append items
512+ l .Items = append (l .Items , t )
466513 }
467514
468- // Append items
469- l .Items = append (l .Items , t )
470515 }
516+ s .Lines = append (s .Lines , * l )
471517
518+ // Append subtitle
519+ o .Items = append (o .Items , s )
472520 }
473- s .Lines = append (s .Lines , * l )
474-
475- // Append subtitle
476- o .Items = append (o .Items , s )
477521 }
478522 return
479523}
0 commit comments