UNPKG

subforge

Version:

High-performance subtitle toolkit for parsing, converting, and authoring across 20+ formats.

97 lines (80 loc) 2.63 kB
import type { SubtitleDocument, SubtitleEvent, TextSegment } from '../../../core/types.ts' import { generateCSS } from './css.ts' /** * Serialize subtitle document to SAMI format * * @param doc - Subtitle document to serialize * @returns SAMI formatted string * * @example * ```ts * const sami = toSAMI(doc) * ``` */ export function toSAMI(doc: SubtitleDocument): string { const title = doc.info.title || 'Subtitle' let result = '<SAMI>\n<HEAD>\n' result += `<TITLE>${escapeHTML(title)}</TITLE>\n` result += '<STYLE TYPE="text/css">\n<!--\n' // Generate CSS from styles result += generateCSS(doc.styles) result += '-->\n</STYLE>\n</HEAD>\n<BODY>\n' // Sort events by start time const sortedEvents = [...doc.events].sort((a, b) => a.start - b.start) // Output SYNC points for (const event of sortedEvents) { const text = event.dirty && event.segments.length > 0 ? serializeTags(event.segments) : escapeHTML(event.text) const className = event.style !== 'Default' ? event.style.toUpperCase().replace(/[^A-Z0-9]/g, '') : 'ENCC' result += `<SYNC Start=${event.start}><P Class=${className}>${text}</P>\n` // Add clear marker at end time if (event.end > event.start) { result += `<SYNC Start=${event.end}><P Class=${className}>&nbsp;</P>\n` } } result += '</BODY>\n</SAMI>\n' return result } function serializeTags(segments: TextSegment[]): string { let result = '' for (const seg of segments) { let text = escapeHTML(seg.text) const openTags: string[] = [] const closeTags: string[] = [] if (seg.style?.bold) { openTags.push('<b>') closeTags.unshift('</b>') } if (seg.style?.italic) { openTags.push('<i>') closeTags.unshift('</i>') } if (seg.style?.underline) { openTags.push('<u>') closeTags.unshift('</u>') } if (seg.style?.strikeout) { openTags.push('<s>') closeTags.unshift('</s>') } if (seg.style?.primaryColor !== undefined) { const color = seg.style.primaryColor const r = color & 0xFF const g = (color >> 8) & 0xFF const b = (color >> 16) & 0xFF const hex = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}` openTags.push(`<font color="${hex}">`) closeTags.unshift('</font>') } result += openTags.join('') + text + closeTags.join('') } return result } function escapeHTML(text: string): string { return text .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') }