Skip to content

Commit e420df3

Browse files
committed
feat(subtitles): Add interactive subtitle track selection
This change introduces a new feature that allows users to interactively select a subtitle track from a list of available tracks in an MKV file. Previously, the application would automatically select the best English subtitle track. Now, it displays a list of all available subtitle tracks with their languages and prompts the user to choose one. If no selection is made, it falls back to the original behavior of selecting the best English track. This provides more flexibility for users who want to extract subtitles in languages other than English or choose from multiple subtitle tracks for the same language.
1 parent d3a048d commit e420df3

File tree

2 files changed

+170
-8
lines changed

2 files changed

+170
-8
lines changed

internal/video/mkv.go

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@ import (
66
"io"
77
"os"
88
"path/filepath"
9+
"strconv"
910
"strings"
1011
"time"
1112

1213
"github.com/luispater/matroska-go"
1314

15+
"github.com/luispater/gemini-srt-translator-go/internal/logger"
1416
"github.com/luispater/gemini-srt-translator-go/pkg/errors"
17+
"github.com/luispater/gemini-srt-translator-go/pkg/languages"
1518
"github.com/luispater/gemini-srt-translator-go/pkg/srt"
1619
)
1720

@@ -265,18 +268,56 @@ func ExtractSubtitlesFromMKV(mkvPath string) (string, error) {
265268
return "", err
266269
}
267270

268-
// Select the best English subtitle track
269-
track, err := parser.SelectBestEnglishTrack()
270-
if err != nil {
271-
return "", err
271+
tracks := parser.GetSubtitleTracks()
272+
if len(tracks) == 0 {
273+
return "", errors.NewValidationError("no subtitle tracks found in MKV", nil)
272274
}
273275

274-
// Generate output path
276+
logger.Info("Available subtitle tracks:")
277+
for i, tr := range tracks {
278+
lines := len(tr.Entries)
279+
name := strings.TrimSpace(tr.Name)
280+
lang := tr.Language
281+
bcp := languages.BCP47FromMKV(lang)
282+
if bcp != "" {
283+
if name != "" {
284+
lang = fmt.Sprintf("%s (%s)", bcp, name)
285+
} else {
286+
lang = fmt.Sprintf("%s", bcp)
287+
}
288+
}
289+
logger.Info(fmt.Sprintf("[%d] Language: %s, Lines:%d", i+1, lang, lines))
290+
}
291+
292+
var selectedIdx int
293+
for {
294+
input := logger.InputPrompt("Select track number to extract: ")
295+
input = strings.TrimSpace(input)
296+
if input == "" {
297+
best, errSel := parser.SelectBestEnglishTrack()
298+
if errSel == nil && best != nil {
299+
for i := range tracks {
300+
if tracks[i].Number == best.Number {
301+
selectedIdx = i
302+
break
303+
}
304+
}
305+
break
306+
}
307+
}
308+
if n, errAtoi := strconv.Atoi(input); errAtoi == nil && n >= 1 && n <= len(tracks) {
309+
selectedIdx = n - 1
310+
break
311+
}
312+
logger.Warning("Invalid selection. Enter a valid number.")
313+
}
314+
315+
selected := tracks[selectedIdx]
316+
275317
baseName := strings.TrimSuffix(filepath.Base(mkvPath), filepath.Ext(mkvPath))
276318
outputPath := filepath.Join(filepath.Dir(mkvPath), baseName+"_extracted.srt")
277319

278-
// Extract to SRT
279-
if err = parser.ExtractToSRT(track, outputPath); err != nil {
320+
if err := parser.ExtractToSRT(&selected, outputPath); err != nil {
280321
return "", err
281322
}
282323

pkg/languages/languages.go

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package languages
22

3-
// LanguageMap contains mappings from language names to language codes
43
var LanguageMap = map[string]string{
54
// Major World Languages
65
"arabic": "ar",
@@ -154,3 +153,125 @@ func GetLanguageCode(languageName string) (string, bool) {
154153
code, exists := LanguageMap[languageName]
155154
return code, exists
156155
}
156+
157+
func BCP47FromMKV(code string) string {
158+
c := code
159+
if c == "" {
160+
return "Undetermined"
161+
}
162+
c = normalize(c)
163+
switch c {
164+
case "en", "eng":
165+
return "English"
166+
case "es", "spa":
167+
return "Spanish"
168+
case "fr", "fre", "fra":
169+
return "French"
170+
case "de", "ger", "deu":
171+
return "German"
172+
case "pt", "por":
173+
return "Portuguese"
174+
case "zh", "zho", "chi", "cmn":
175+
return "Chinese"
176+
case "ja", "jpn":
177+
return "Japanese"
178+
case "ko", "kor":
179+
return "Korean"
180+
case "it", "ita":
181+
return "Italian"
182+
case "ru", "rus":
183+
return "Russian"
184+
case "ar", "ara":
185+
return "Arabic"
186+
case "hi", "hin":
187+
return "Hindi"
188+
case "nl", "dut", "nld":
189+
return "Dutch"
190+
case "sv", "swe":
191+
return "Swedish"
192+
case "pl", "pol":
193+
return "Polish"
194+
case "tr", "tur":
195+
return "Turkish"
196+
case "he", "heb":
197+
return "Hebrew"
198+
case "uk", "ukr":
199+
return "Ukrainian"
200+
case "cs", "cze", "ces":
201+
return "Czech"
202+
case "ro", "rum", "ron":
203+
return "Romanian"
204+
case "vi", "vie":
205+
return "Vietnamese"
206+
case "th", "tha":
207+
return "Thai"
208+
case "id", "ind":
209+
return "Indonesian"
210+
case "fa", "per", "fas":
211+
return "Persian"
212+
case "el", "gre", "ell":
213+
return "Greek"
214+
case "hu", "hun":
215+
return "Hungarian"
216+
case "fi", "fin":
217+
return "Finnish"
218+
case "no", "nor":
219+
return "Norwegian"
220+
case "da", "dan":
221+
return "Danish"
222+
case "bg", "bul":
223+
return "Bulgarian"
224+
case "sr", "srp":
225+
return "Serbian"
226+
case "hr", "hrv":
227+
return "Croatian"
228+
case "sk", "slk", "slo":
229+
return "Slovak"
230+
case "sl", "slv":
231+
return "Slovenian"
232+
case "lt", "lit":
233+
return "Lithuanian"
234+
case "lv", "lav":
235+
return "Latvian"
236+
case "et", "est":
237+
return "Estonian"
238+
case "ms", "msa", "may":
239+
return "Malay"
240+
case "bn", "ben":
241+
return "Bengali"
242+
case "ur", "urd":
243+
return "Urdu"
244+
case "ta", "tam":
245+
return "Tamil"
246+
case "te", "tel":
247+
return "Telugu"
248+
case "ml", "mal":
249+
return "Malayalam"
250+
case "mr", "mar":
251+
return "Marathi"
252+
case "ne", "nep":
253+
return "Nepali"
254+
case "km", "khm":
255+
return "Khmer"
256+
case "my", "bur", "mya":
257+
return "Burmese"
258+
case "bo", "tib":
259+
return "Tibetan"
260+
case "id-id":
261+
return "Indonesian"
262+
}
263+
return c
264+
}
265+
266+
func normalize(s string) string {
267+
b := make([]byte, 0, len(s))
268+
for i := 0; i < len(s); i++ {
269+
ch := s[i]
270+
if ch >= 'A' && ch <= 'Z' {
271+
b = append(b, ch+32)
272+
continue
273+
}
274+
b = append(b, ch)
275+
}
276+
return string(b)
277+
}

0 commit comments

Comments
 (0)