package sync import ( "fmt" "sub-cli/internal/format/vtt" "sub-cli/internal/model" ) // syncVTTFiles synchronizes two VTT files func syncVTTFiles(sourceFile, targetFile string) error { sourceSubtitle, err := vtt.Parse(sourceFile) if err != nil { return fmt.Errorf("error parsing source VTT file: %w", err) } targetSubtitle, err := vtt.Parse(targetFile) if err != nil { return fmt.Errorf("error parsing target VTT file: %w", err) } // Check if entry counts match if len(sourceSubtitle.Entries) != len(targetSubtitle.Entries) { fmt.Printf("Warning: Source (%d entries) and target (%d entries) have different entry counts. Timeline will be adjusted.\n", len(sourceSubtitle.Entries), len(targetSubtitle.Entries)) } // Sync the timelines syncedSubtitle := syncVTTTimeline(sourceSubtitle, targetSubtitle) // Write the synced subtitle to the target file return vtt.Generate(syncedSubtitle, targetFile) } // syncVTTTimeline applies the timing from source VTT subtitle to target VTT subtitle func syncVTTTimeline(source, target model.Subtitle) model.Subtitle { result := model.NewSubtitle() result.Format = "vtt" result.Title = target.Title result.Metadata = target.Metadata result.Styles = target.Styles // Create entries array with same length as target result.Entries = make([]model.SubtitleEntry, len(target.Entries)) // Copy target entries copy(result.Entries, target.Entries) // If source subtitle is empty or target subtitle is empty, return copied target if len(source.Entries) == 0 || len(target.Entries) == 0 { // Ensure proper index numbering for i := range result.Entries { result.Entries[i].Index = i + 1 } return result } // If source and target have the same number of entries, directly apply timings if len(source.Entries) == len(target.Entries) { for i := range result.Entries { result.Entries[i].StartTime = source.Entries[i].StartTime result.Entries[i].EndTime = source.Entries[i].EndTime } } else { // If entry counts differ, scale the timing similar to SRT sync for i := range result.Entries { // Calculate scaled index sourceIdx := 0 if len(source.Entries) > 1 { sourceIdx = i * (len(source.Entries) - 1) / (len(target.Entries) - 1) } // Ensure the index is within bounds if sourceIdx >= len(source.Entries) { sourceIdx = len(source.Entries) - 1 } // Apply the scaled timing result.Entries[i].StartTime = source.Entries[sourceIdx].StartTime // Calculate end time: if not the last entry, use duration from source if i < len(result.Entries)-1 { // If next source entry exists, calculate duration var duration model.Timestamp if sourceIdx+1 < len(source.Entries) { duration = calculateDuration(source.Entries[sourceIdx].StartTime, source.Entries[sourceIdx+1].StartTime) } else { duration = calculateDuration(source.Entries[sourceIdx].StartTime, source.Entries[sourceIdx].EndTime) } result.Entries[i].EndTime = addDuration(result.Entries[i].StartTime, duration) } else { // For the last entry, use the end time from source result.Entries[i].EndTime = source.Entries[sourceIdx].EndTime } } } // Ensure proper index numbering for i := range result.Entries { result.Entries[i].Index = i + 1 } return result }