subsync

package
v0.1.147 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: GPL-2.0, GPL-3.0 Imports: 29 Imported by: 0

Documentation

Overview

Package subsync provides subtitle timing synchronization.

It supports multiple sync strategies:

  • Constant-offset alignment (alass algorithm port)
  • Framerate correction (known ratios + golden-section search)
  • Split-aware DP alignment (handles commercial breaks, different cuts)
  • Audio-based sync (energy VAD + FFT cross-correlation, no reference needed)
  • Encoding normalization (UTF-16, Windows-1252 → UTF-8)

Basic usage:

result, err := subsync.SyncFile(ctx, "video.mkv", "subtitle.srt", nil)
if err != nil {
    log.Fatal(err)
}
os.WriteFile("subtitle.synced.srt", result.Data, 0o644)

With a reference subtitle:

result, err := subsync.SyncFile(ctx, "video.mkv", "subtitle.srt", &subsync.Options{
    Reference: "reference.en.srt",
})

Index

Constants

View Source
const MinCuesForSync = 5

MinCuesForSync is the minimum number of subtitle cues required for any timing-based sync strategy to produce meaningful results. Fewer than this provides insufficient signal for correlation or alignment.

Variables

View Source
var DefaultConfidenceCaps = ConfidenceCaps{
	Audio:                  0.9,
	Offset:                 0.9,
	FramerateGSS:           0.85,
	FramerateKnown:         0.95,
	FramerateFPS:           0.98,
	Crosslang:              0.92,
	SplitBase:              0.85,
	SplitPenaltyPerSegment: 0.05,
	SplitMinConf:           0.4,
}

DefaultConfidenceCaps is the production confidence cap table.

View Source
var ErrFileTooLarge = errors.New("subsync: file too large")

ErrFileTooLarge is returned when a subtitle file exceeds the maximum safe size. Wrap it with fmt.Errorf("%w: ...") so callers can use errors.Is.

Functions

func IsASSContent

func IsASSContent(data []byte) bool

IsASSContent reports whether data looks like ASS/SSA subtitle content by checking for the [Script Info] header or Dialogue: lines.

func NormalizeEncoding

func NormalizeEncoding(data []byte) []byte

NormalizeEncoding detects the encoding of subtitle data and converts it to UTF-8. Handles UTF-8 (with/without BOM), UTF-16 LE/BE, and common single-byte encodings (Windows-1252, ISO-8859-1).

Returns the UTF-8 normalized data with any byte-order mark and embedded NUL bytes removed. When the input is already valid, BOM-free, NUL-free UTF-8 the original slice is returned (not a copy); callers must not mutate the returned slice if they need the original data unchanged. The result is a fixed point: NormalizeEncoding(NormalizeEncoding(x)) always equals NormalizeEncoding(x).

func OutputPath

func OutputPath(inputPath string) string

OutputPath generates the output file path for a synced subtitle. For "movie.fr.srt" it returns "movie.fr.synced.srt".

func PostProcessBytes

func PostProcessBytes(data []byte, opts PostProcessOptions) []byte

PostProcessBytes applies encoding normalization and line ending fixes to raw subtitle bytes. Call this before parsing, or on the final output.

func WriteSRT

func WriteSRT(w io.Writer, cues []Cue) error

WriteSRT writes cues as an SRT file.

Types

type AudioSyncHints

type AudioSyncHints struct {
	// DialogueCues and MaskCues are pre-classified ASS cues from the native
	// parser. When set, audioSyncFromPCM uses these directly instead of
	// applying text-based heuristics (karaoke pair detection, SDH filtering)
	// to the flat cue list. This produces better results for ASS files
	// because style-based classification is more accurate than text patterns.
	DialogueCues []Cue
	MaskCues     []Cue

	// DurationSec is the total media duration in seconds.
	DurationSec int

	// IsASS indicates the subtitle was ASS-extracted (clean cues, no tag remnants).
	IsASS bool

	// DisableVAD skips the GMM VAD fallback (Strategy D). Used for benchmarking
	// to measure the energy-only pipeline's accuracy.
	DisableVAD bool
}

AudioSyncHints provides content characteristics for adaptive strategy selection. Zero values use the default strategy.

type Confidence

type Confidence float64

Confidence represents the quality of a sync operation (0.0 to 1.0).

const (
	// ConfidenceNone means no sync was performed or the result is unusable.
	ConfidenceNone Confidence = 0

	// ConfidenceWeak means the sync may be wrong; caller should consider
	// keeping the original timing.
	ConfidenceWeak Confidence = 0.3

	// ConfidenceModerate means the sync is likely correct but may have issues.
	ConfidenceModerate Confidence = 0.6

	// ConfidenceStrong means high confidence in the sync result.
	ConfidenceStrong Confidence = 0.8

	// ConfidencePerfect means hash match or perfect correlation.
	ConfidencePerfect Confidence = 1.0
)
const DefaultMinConfidence Confidence = 0.5

DefaultMinConfidence is the default minimum confidence used by the sync engine when no explicit threshold is provided. This is the single source of truth for the engine's default; the API layer's DefaultSyncMinConfidence (0.6) is intentionally stricter for user-facing auto-apply decisions.

const ShouldApplyThreshold Confidence = 0.5

ShouldApplyThreshold is the exported minimum confidence for a sync result to be considered applicable. Consumers comparing Result.Confidence against a threshold should use this constant instead of a magic 0.5 literal. This is the audio/fallback threshold; reference-based sync uses DefaultMinConfidence (0.5) as the engine default, while the API layer may apply a stricter threshold (e.g. 0.6) for user-facing auto-sync.

type ConfidenceCaps

type ConfidenceCaps struct {
	Audio                  Confidence // audio-based sync
	Offset                 Confidence // constant-offset sync
	FramerateGSS           Confidence // golden-section framerate search
	FramerateKnown         Confidence // known-ratio framerate match
	FramerateFPS           Confidence // video FPS confirms the ratio
	Crosslang              Confidence // cross-language alignment
	SplitBase              Confidence // split-aware alignment (reduced by segment penalty)
	SplitPenaltyPerSegment Confidence // confidence penalty per additional segment
	SplitMinConf           Confidence // minimum confidence floor for split alignment
}

ConfidenceCaps holds per-strategy confidence ceilings. Each strategy has a different cap reflecting its inherent reliability. The relative ordering is intentional: framerate with FPS confirmation > known ratio > audio/offset > GSS/split.

func (ConfidenceCaps) ForMethod

func (c ConfidenceCaps) ForMethod(m SyncMethod) Confidence

ForMethod returns the confidence cap for the given sync method. For framerate methods, returns FramerateGSS as the conservative default; callers with FPS confirmation should use FramerateFPS directly.

type Cue

type Cue struct {
	Text  string
	Start time.Duration
	End   time.Duration
}

Cue represents a single subtitle cue with timing and text content. This is a local definition that mirrors api.SubtitleCue, decoupling the subsync package from the application's domain types.

func ExtractEmbeddedSRT

func ExtractEmbeddedSRT(ctx context.Context, videoPath, lang, excludeLang string, mapper ffmpeg.LangMapper) ([]Cue, error)

ExtractEmbeddedSRT extracts text-based subtitle data from a video file and returns it as parsed SRT cues. Uses ffprobe for track detection and ffmpeg for subtitle extraction.

Track selection priority:

  1. Matching language (if lang is non-empty)
  2. Prefer SRT/subrip over ASS/SSA
  3. Skip forced tracks (sparse cues)
  4. Skip tracks matching excludeLang

Returns nil cues if no suitable track is found.

func ParseASSDialogue

func ParseASSDialogue(data []byte) (dialogueCues, maskCues []Cue, err error)

ParseASSDialogue parses raw ASS content and returns dialogue cues and mask cues separately.

dialogueCues: only cues from classified dialogue styles (for subtitle envelope / correlation signal). maskCues: all cues regardless of style classification (for dialogue mask / time region detection). Even OP/ED/signs mark time regions with audio activity, giving broader mask coverage.

Two-pass: first collects all style names and cue counts, classifies them, then extracts cues.

func ParseSRT

func ParseSRT(r io.Reader) ([]Cue, error)

ParseSRT parses an SRT subtitle file into cues.

func PostProcess

func PostProcess(cues []Cue, opts PostProcessOptions) []Cue

PostProcess applies the configured processing steps to subtitle cues. Steps run in order: encoding normalization (on raw bytes before parsing) is handled by the caller; this function operates on parsed cues.

func ShiftCues

func ShiftCues(cues []Cue, offset time.Duration) []Cue

ShiftCues applies a time offset to all cues.

type LangMapper

type LangMapper = ffmpeg.LangMapper

LangMapper is a type alias for ffmpeg.LangMapper, preserving backward compatibility for consumers that reference subsync.LangMapper.

type Options

type Options struct {
	// PostProcess configures subtitle post-processing (HI removal, tag stripping, etc.).
	// Nil means no post-processing.
	PostProcess *PostProcessOptions

	// Framerate enables framerate correction detection.
	// nil = default (true), non-nil = explicit value.
	Framerate *bool

	// Splits enables split-aware alignment.
	// nil = default (true), non-nil = explicit value.
	Splits *bool

	// Reference is the path to a correctly-timed reference subtitle.
	Reference string

	// SplitPenalty controls split detection sensitivity (0 = default 1000ms).
	SplitPenalty float64

	// MinConfidence is the minimum confidence to accept a sync result (default: 0.5).
	MinConfidence float64

	// Audio enables audio-based sync (default: false).
	Audio bool
}

Options configures a sync operation.

type PostProcessOptions

type PostProcessOptions struct {
	// StripHI removes hearing-impaired annotations:
	// [sound effects], (music playing), ♪ lyrics ♪, and speaker labels (JOHN:).
	StripHI bool

	// StripTags removes HTML-like tags: <i>, </i>, <b>, </b>, <u>, </u>, <font ...>, </font>.
	StripTags bool

	// NormalizeEncoding converts the subtitle to UTF-8 from any detected encoding
	// (UTF-16 LE/BE, Windows-1252, ISO-8859-1). Also strips UTF-8 BOM.
	NormalizeEncoding bool

	// NormalizeLineEndings converts all line endings to CRLF (SRT standard)
	// and ensures a single trailing CRLF.
	NormalizeLineEndings bool

	// CleanWhitespace trims leading/trailing whitespace from each text line
	// and removes blank lines and bare dialogue dashes.
	CleanWhitespace bool

	// RemoveEmpty drops cues that have no text content after all other
	// processing steps. Cue numbers are assigned when writing SRT output.
	RemoveEmpty bool
}

PostProcessOptions configures subtitle post-processing. All fields default to false (no processing). Enable what you need.

type Result

type Result struct {
	// Method describes which strategy produced the result.
	// One of: MethodOffset, MethodFramerate, MethodSplit, MethodAudio, MethodCrosslang, MethodNone.
	Method SyncMethod

	// Data is the synchronized subtitle file content (UTF-8, SRT format).
	Data []byte

	// OffsetMs is the constant offset applied (milliseconds).
	OffsetMs int64

	// Confidence is the quality score of the sync (0.0 to 1.0).
	Confidence Confidence

	// Rate is the framerate ratio applied (1.0 = no change).
	Rate float64

	// Applied is true if the sync changed timing and confidence was sufficient.
	Applied bool
}

Result holds the output of a sync operation.

func SyncFile

func SyncFile(ctx context.Context, videoPath, subtitlePath string, opts *Options) (Result, error)

SyncFile synchronizes a subtitle file against a video file. The video path is used for audio-based sync when no reference is available. Pass nil for opts to use defaults.

type SyncMethod

type SyncMethod string

SyncMethod is a typed string identifying the sync algorithm used.

const (
	MethodNone      SyncMethod = "none"
	MethodOffset    SyncMethod = "offset"
	MethodFramerate SyncMethod = "framerate"
	MethodSplit     SyncMethod = "split"
	MethodAudio     SyncMethod = "audio"
	MethodCrosslang SyncMethod = "crosslang"
)

Sync method identifiers.

func (SyncMethod) String

func (m SyncMethod) String() string

String implements fmt.Stringer.

type SyncOptions

type SyncOptions struct {
	// VideoPath is the path to the video file (required for audio sync).
	VideoPath string

	// AudioHints provides content characteristics for adaptive audio
	// sync strategy selection. Optional; zero value uses defaults.
	AudioHints AudioSyncHints

	// SplitPenalty controls split detection sensitivity (0 = default).
	SplitPenalty float64

	// MinConfidence is the minimum confidence to apply a sync result.
	// Default: 0.5.
	MinConfidence Confidence

	// EnableFramerate enables framerate correction detection.
	EnableFramerate bool

	// EnableSplits enables split-aware DP alignment.
	EnableSplits bool

	// EnableAudio enables audio-based sync (requires video file path).
	EnableAudio bool
}

SyncOptions configures the sync behavior.

func DefaultSyncOptions

func DefaultSyncOptions() SyncOptions

DefaultSyncOptions returns sensible defaults.

type SyncResult

type SyncResult struct {
	Method     SyncMethod // MethodNone, MethodOffset, MethodFramerate, MethodSplit, MethodAudio, MethodCrosslang
	Cues       []Cue
	Offset     int64      // milliseconds (constant offset applied)
	Confidence Confidence // quality of the sync
	Rate       float64    // framerate ratio applied (1.0 = no change)
}

SyncResult holds the output of any sync operation.

func SyncWithOptions

func SyncWithOptions(ctx context.Context, reference, incorrect []Cue, opts *SyncOptions) SyncResult

SyncWithOptions performs multi-strategy subtitle synchronization.

Strategy order:

  1. If reference subtitle available: try framerate correction, then split-aware alignment, then constant offset
  2. If no reference but video path provided and audio enabled: try audio-based sync

Returns the best result above the minimum confidence threshold.

func (SyncResult) Applied

func (r SyncResult) Applied() bool

Applied returns true if the sync actually changed the subtitle timing. Checks offset (constant shift), rate (framerate correction), and split method (multi-segment alignment where no single offset/rate captures the change).

func (SyncResult) ShouldApply

func (r SyncResult) ShouldApply() bool

ShouldApply returns true if the confidence is high enough to use the result. Threshold: ShouldApplyThreshold (moderate confidence or better). This is the post-hoc check used by callers after sync completes.

type TimeSpan

type TimeSpan struct {
	Start int64 // milliseconds
	End   int64
}

TimeSpan is a start/end pair used by the alignment algorithm.

Directories

Path Synopsis
Package crosslang implements cross-language subtitle alignment using anchor-based matching and dynamic programming.
Package crosslang implements cross-language subtitle alignment using anchor-based matching and dynamic programming.
Package ffmpeg provides low-level ffmpeg/ffprobe subprocess wrappers for stream probing, subtitle extraction, and PCM audio extraction.
Package ffmpeg provides low-level ffmpeg/ffprobe subprocess wrappers for stream probing, subtitle extraction, and PCM audio extraction.
Package fft provides a radix-2 Cooley-Tukey FFT implementation for cross-correlation in the subsync package.
Package fft provides a radix-2 Cooley-Tukey FFT implementation for cross-correlation in the subsync package.
Package framerate provides framerate drift detection for subtitle alignment.
Package framerate provides framerate drift detection for subtitle alignment.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL