All checks were successful
ci/woodpecker/tag/release Pipeline was successful
98 lines
2.3 KiB
Go
98 lines
2.3 KiB
Go
package render
|
||
|
||
import (
|
||
"fmt"
|
||
"math"
|
||
"strings"
|
||
)
|
||
|
||
// MarkdownRenderer renders transcript artifacts as Markdown.
|
||
type MarkdownRenderer struct{}
|
||
|
||
// Render renders the transcript into deterministic Markdown.
|
||
func (MarkdownRenderer) Render(transcript Transcript, opts Options) (string, error) {
|
||
var lines []string
|
||
|
||
title := strings.TrimSpace(opts.Title)
|
||
if title == "" {
|
||
title = "Transcript"
|
||
}
|
||
lines = append(lines, "# "+escapeMarkdownInline(title), "")
|
||
|
||
if opts.IncludeMetadata {
|
||
lines = append(lines,
|
||
fmt.Sprintf("- Application: %s", escapeMarkdownInline(transcript.Metadata.Application)),
|
||
fmt.Sprintf("- Version: %s", escapeMarkdownInline(transcript.Metadata.Version)),
|
||
fmt.Sprintf("- Output schema: %s", escapeMarkdownInline(transcript.Schema)),
|
||
"",
|
||
)
|
||
}
|
||
|
||
for _, segment := range transcript.Segments {
|
||
parts := make([]string, 0, 4)
|
||
if opts.IncludeTimestamps {
|
||
parts = append(parts, fmt.Sprintf("[%s–%s]", formatTimestamp(segment.Start), formatTimestamp(segment.End)))
|
||
}
|
||
if opts.IncludeSegmentIDs {
|
||
parts = append(parts, fmt.Sprintf("[#%d]", segment.ID))
|
||
}
|
||
|
||
text := escapeMarkdownInline(segment.Text)
|
||
if shouldItalicize(segment.Categories) {
|
||
text = "*" + text + "*"
|
||
}
|
||
parts = append(parts, fmt.Sprintf("**%s:** %s", escapeMarkdownInline(segment.Speaker), text))
|
||
lines = append(lines, strings.Join(parts, " "))
|
||
lines = append(lines, "")
|
||
}
|
||
|
||
output := strings.Join(lines, "\n")
|
||
if !strings.HasSuffix(output, "\n") {
|
||
output += "\n"
|
||
}
|
||
return output, nil
|
||
}
|
||
|
||
func escapeMarkdownInline(value string) string {
|
||
replacer := strings.NewReplacer(
|
||
`\`, `\\`,
|
||
"`", "\\`",
|
||
"*", "\\*",
|
||
"_", "\\_",
|
||
"{", "\\{",
|
||
"}", "\\}",
|
||
"[", "\\[",
|
||
"]", "\\]",
|
||
"(", "\\(",
|
||
")", "\\)",
|
||
"#", "\\#",
|
||
"+", "\\+",
|
||
"!", "\\!",
|
||
"|", "\\|",
|
||
"<", "\\<",
|
||
">", "\\>",
|
||
)
|
||
return replacer.Replace(value)
|
||
}
|
||
|
||
func shouldItalicize(categories []string) bool {
|
||
for _, category := range categories {
|
||
switch category {
|
||
case "background", "backchannel", "filler":
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func formatTimestamp(seconds float64) string {
|
||
total := int(math.Round(seconds))
|
||
if total < 0 {
|
||
total = 0
|
||
}
|
||
hours := total / 3600
|
||
minutes := (total % 3600) / 60
|
||
remainder := total % 60
|
||
return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, remainder)
|
||
}
|