feat: basic ass processing (without style)
This commit is contained in:
parent
8897d7ae90
commit
ebbf516689
10 changed files with 2301 additions and 808 deletions
|
@ -5,6 +5,7 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"sub-cli/internal/format/ass"
|
||||
"sub-cli/internal/format/lrc"
|
||||
"sub-cli/internal/format/srt"
|
||||
"sub-cli/internal/format/vtt"
|
||||
|
@ -23,8 +24,10 @@ func SyncLyrics(sourceFile, targetFile string) error {
|
|||
return syncSRTFiles(sourceFile, targetFile)
|
||||
} else if sourceFmt == "vtt" && targetFmt == "vtt" {
|
||||
return syncVTTFiles(sourceFile, targetFile)
|
||||
} else if sourceFmt == "ass" && targetFmt == "ass" {
|
||||
return syncASSFiles(sourceFile, targetFile)
|
||||
} else {
|
||||
return fmt.Errorf("sync only supports files of the same format (lrc-to-lrc, srt-to-srt, or vtt-to-vtt)")
|
||||
return fmt.Errorf("sync only supports files of the same format (lrc-to-lrc, srt-to-srt, vtt-to-vtt, or ass-to-ass)")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,6 +106,31 @@ func syncVTTFiles(sourceFile, targetFile string) error {
|
|||
return vtt.Generate(syncedSubtitle, targetFile)
|
||||
}
|
||||
|
||||
// syncASSFiles synchronizes two ASS files
|
||||
func syncASSFiles(sourceFile, targetFile string) error {
|
||||
sourceSubtitle, err := ass.Parse(sourceFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing source ASS file: %w", err)
|
||||
}
|
||||
|
||||
targetSubtitle, err := ass.Parse(targetFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing target ASS file: %w", err)
|
||||
}
|
||||
|
||||
// Check if entry counts match
|
||||
if len(sourceSubtitle.Events) != len(targetSubtitle.Events) {
|
||||
fmt.Printf("Warning: Source (%d events) and target (%d events) have different event counts. Timeline will be adjusted.\n",
|
||||
len(sourceSubtitle.Events), len(targetSubtitle.Events))
|
||||
}
|
||||
|
||||
// Sync the timelines
|
||||
syncedSubtitle := syncASSTimeline(sourceSubtitle, targetSubtitle)
|
||||
|
||||
// Write the synced subtitle to the target file
|
||||
return ass.Generate(syncedSubtitle, targetFile)
|
||||
}
|
||||
|
||||
// syncLRCTimeline applies the timeline from the source lyrics to the target lyrics
|
||||
func syncLRCTimeline(source, target model.Lyrics) model.Lyrics {
|
||||
result := model.Lyrics{
|
||||
|
@ -131,6 +159,15 @@ func syncSRTTimeline(sourceEntries, targetEntries []model.SRTEntry) []model.SRTE
|
|||
// Copy target entries
|
||||
copy(result, targetEntries)
|
||||
|
||||
// If source is empty, just return the target entries as is
|
||||
if len(sourceEntries) == 0 {
|
||||
// Ensure proper sequence numbering
|
||||
for i := range result {
|
||||
result[i].Number = i + 1
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// If source and target have the same number of entries, directly apply timings
|
||||
if len(sourceEntries) == len(targetEntries) {
|
||||
for i := range result {
|
||||
|
@ -253,6 +290,51 @@ func syncVTTTimeline(source, target model.Subtitle) model.Subtitle {
|
|||
return result
|
||||
}
|
||||
|
||||
// syncASSTimeline applies the timing from source ASS subtitle to target ASS subtitle
|
||||
func syncASSTimeline(source, target model.ASSFile) model.ASSFile {
|
||||
result := model.ASSFile{
|
||||
ScriptInfo: target.ScriptInfo,
|
||||
Styles: target.Styles,
|
||||
Events: make([]model.ASSEvent, len(target.Events)),
|
||||
}
|
||||
|
||||
// Copy target events to preserve content
|
||||
copy(result.Events, target.Events)
|
||||
|
||||
// If there are no events in either source or target, return as is
|
||||
if len(source.Events) == 0 || len(target.Events) == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
// Create a timeline of source start and end times
|
||||
sourceStartTimes := make([]model.Timestamp, len(source.Events))
|
||||
sourceEndTimes := make([]model.Timestamp, len(source.Events))
|
||||
|
||||
for i, event := range source.Events {
|
||||
sourceStartTimes[i] = event.StartTime
|
||||
sourceEndTimes[i] = event.EndTime
|
||||
}
|
||||
|
||||
// Scale the timeline if source and target have different number of events
|
||||
var scaledStartTimes, scaledEndTimes []model.Timestamp
|
||||
|
||||
if len(source.Events) != len(target.Events) {
|
||||
scaledStartTimes = scaleTimeline(sourceStartTimes, len(target.Events))
|
||||
scaledEndTimes = scaleTimeline(sourceEndTimes, len(target.Events))
|
||||
} else {
|
||||
scaledStartTimes = sourceStartTimes
|
||||
scaledEndTimes = sourceEndTimes
|
||||
}
|
||||
|
||||
// Apply scaled timeline to target events
|
||||
for i := range result.Events {
|
||||
result.Events[i].StartTime = scaledStartTimes[i]
|
||||
result.Events[i].EndTime = scaledEndTimes[i]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// scaleTimeline scales a timeline to match a different number of entries
|
||||
func scaleTimeline(timeline []model.Timestamp, targetCount int) []model.Timestamp {
|
||||
if targetCount <= 0 || len(timeline) == 0 {
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue