// parseStyle parse an SSA/ASS Subtitle Dialog. func parseDialog(key, value string) *Dialog { // TODO ?: use sprintf d := strings.SplitN(value, ",", 10) return &Dialog{ Layer: utils.Str2int(d[0]), StartTime: d[1], EndTime: d[2], StyleName: d[3], Actor: d[4], Effect: d[8], Text: strings.TrimSpace(d[9]), Comment: key == "comment", } }
// parseStyle parse an SSA/ASS Subtitle Style. func parseStyle(value string) *Style { // TODO ?: use sprintf sty := strings.SplitN(value, ",", 23) c1, a1 := color.SSALtoHEXAlpha(sty[3]) c2, a2 := color.SSALtoHEXAlpha(sty[4]) c3, a3 := color.SSALtoHEXAlpha(sty[5]) c4, a4 := color.SSALtoHEXAlpha(sty[6]) return &Style{ Name: sty[0], FontName: sty[1], FontSize: utils.Str2int(sty[2]), Color: [4]string{ c1, // Primary c2, // Secondary c3, // Bord c4}, // Shadow Alpha: [4]uint8{ uint8(a1), // Primary uint8(a2), // Secondary uint8(a3), // Bord uint8(a4)}, // Shadow Bold: utils.Str2bool(sty[7]), Italic: utils.Str2bool(sty[8]), Underline: utils.Str2bool(sty[9]), StrikeOut: utils.Str2bool(sty[10]), Scale: [2]float64{ utils.Str2float(sty[11]), // X utils.Str2float(sty[12]), // Y }, Spacing: utils.Str2float(sty[13]), Angle: utils.Str2int(sty[14]), OpaqueBox: utils.Obox2bool(sty[15]), Bord: utils.Str2float(sty[16]), Shadow: utils.Str2float(sty[17]), Alignment: utils.Str2int(sty[18]), Margin: [3]int{ utils.Str2int(sty[19]), // L utils.Str2int(sty[20]), // R utils.Str2int(sty[21]), // V }, } }
func (d *Line) Syls() (syls []*Syl) { lineStart := d.StartTime lineEnd := d.EndTime end := 0 fontFace := d.fontFace spaceWidth, _ := utils.MeasureString(fontFace, " ") spaceWidth *= d.Style.Scale[0] / 100.0 curX := d.Left maxWidth := 0.0 sumHeight := 0.0 resx, resy := float64(d.resolution[0]), float64(d.resolution[1]) for i, dlg := range d.syls { duration, inline, text := dlg[1], dlg[2], dlg[3] dur := utils.Str2int(duration) * 10 // cs to ms // Absolute times start := lineStart lineStart += dur if i == d.SylN-1 { // Ensure that the end time and the width of the last syl // is the same that the end time and width of the line end = lineEnd } else { end = lineStart } strippedText, preSpace, postSpace := utils.TrimSpaceCount(text) width, height := utils.MeasureString(fontFace, strippedText) width *= d.Style.Scale[0] / 100.0 height *= d.Height middleheight := float64(height) / 2.0 middlewidth := float64(width) / 2.0 align := d.Style.Alignment curX += float64(preSpace) * spaceWidth sleft := float64(curX) scenter := sleft + middlewidth sright := sleft + width x := 0.0 y := 0.0 stop := 0.0 smid := 0.0 sbot := 0.0 maxWidth = math.Max(maxWidth, width) sumHeight += height // line x if align > 6 || align < 4 { switch align { case 1, 7: // left x = sleft case 2, 8: // center x = scenter case 3, 9: // right x = sright } curX += width + float64(postSpace)*spaceWidth } else { // vertical alignment xFix := (maxWidth - width) / 2.0 switch align { case 4: // left sleft = d.Left + xFix scenter = sleft + middlewidth sright = sleft + width x = sleft case 5: // center sleft = resx/2.0 - middlewidth scenter = sleft + middlewidth sright = sleft + width x = scenter case 6: // right sleft = d.Right - width - xFix scenter = sleft + middlewidth sright = sleft + width x = sright } } curY := resy/2.0 - sumHeight/2.0 + float64(d.Style.Spacing) // line y if align > 6 || align < 4 { stop = d.Top smid = d.Middle sbot = d.Bottom y = d.Y } else { // vertical alignment stop = curY smid = stop + middleheight sbot = stop + height y = smid curY += height } if text != "" { s := &Syl{ Layer: d.Layer, Style: d.Style, StyleName: d.StyleName, Actor: d.Actor, Effect: d.Effect, Tags: d.Tags, Comment: d.Comment, // Syl StartTime: start, EndTime: end, Duration: dur, MidTime: end - start, Text: strippedText, Inline: inline, Width: float64(width), Height: float64(height), Size: [2]float64{float64(width), float64(height)}, X: float64(x), Y: float64(y), Top: float64(stop), Middle: float64(smid), Bottom: float64(sbot), Left: float64(sleft), Center: float64(scenter), Right: float64(sright), } syls = append(syls, s) } } return syls }
func SSAtoSplit(t string) (h, m, s, cs int) { //H:MM:SS.CC (H=Hour, M=Minute, S=Second, C=centisecond) tm := ReSSAfmt.FindStringSubmatch(t) return utils.Str2int(tm[1]), utils.Str2int(tm[2]), utils.Str2int(tm[3]), utils.Str2int(tm[4]) }
// Read parse and read an SSA/ASS Subtitle Script. func Read(fn string) *Script { s := &Script{} f, err := os.Open(fn) if err != nil { panic(fmt.Errorf("reader: failed opening subtitle file: %s", err)) } defer f.Close() s.Style = make(map[string]*Style) s.StyleUsed = make(map[string]*Style) var playresx, playresy int var videozoom, videoar float64 scanner := bufio.NewScanner(f) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if line == "" || strings.HasPrefix(line, ";") || strings.HasPrefix(line, "!:") || strings.HasPrefix(line, "Format:") { continue } keyvalue := strings.SplitN(line, ":", 2) if len(keyvalue) != 2 { continue } key, value := keyvalue[0], keyvalue[1] key = strings.TrimSpace(key) key = strings.ToLower(key) key = strings.Replace(key, " ", "_", -1) value = strings.TrimSpace(value) switch key { case "dialogue", "comment": s.Dialog = append(s.Dialog, parseDialog(key, value)) case "style": style := parseStyle(value) s.Style[style.Name] = style case "playresx": playresx = utils.Str2int(value) case "playresy": playresy = utils.Str2int(value) case "audio_uri", "audio_file": s.Audio = value case "video_file": s.VideoPath = value case "video_zoom_percent": videozoom = utils.Str2float(value) case "video_zoom": // Use "video_zoom_percent" key if present // else use "video_zoom" key if videozoom == 0 { zoom := strings.Replace(value, "%", "", -1) videozoom = utils.Str2float(zoom) / 100.0 } case "video_aspect_ratio", "video_ar_value", "aegisub_video_aspect_ratio": ar := strings.Replace(value, "c", "", -1) numden := strings.SplitN(ar, ":", 2) if len(numden) == 2 { num, den := utils.Str2float(numden[0]), utils.Str2float(numden[1]) videoar = num / den } else { videoar = utils.Str2float(ar) } s.VideoAR = videoar case "video_position": s.VideoPosition = utils.Str2int(value) case "title": s.MetaTitle = value case "original_script": s.MetaOriginalScript = value case "translation": s.MetaTranslation = value case "timing": s.MetaTiming = value default: continue } } s.Resolution = [2]int{playresx, playresy} s.VideoZoom = videozoom s.MetaFilename = fn // Get only the styles used in dialogs for _, d := range s.Dialog { sty, ok := s.Style[d.StyleName] if !ok { sty = s.Style["Default"] } d.Style = sty if !d.Comment { s.StyleUsed[d.StyleName] = sty } } return s }