@@ -24,6 +24,7 @@ import project.pipepipe.app.database.DatabaseOperations
2424import project.pipepipe.app.download.DownloadManagerHolder
2525import project.pipepipe.app.helper.FormatHelper
2626import project.pipepipe.app.download.YtDlpFormatHelper
27+ import project.pipepipe.app.helper.LanguageHelper.getLocalizedLanguageName
2728import project.pipepipe.app.uistate.DownloadType
2829import project.pipepipe.shared.infoitem.StreamInfo
2930
@@ -69,6 +70,9 @@ fun DownloadFormatDialog(
6970 var loadError by remember { mutableStateOf<String ?>(null ) }
7071 var videoFormats by remember { mutableStateOf<List <Format >>(emptyList()) }
7172 var audioFormats by remember { mutableStateOf<List <Format >>(emptyList()) }
73+ var subtitleFormats by remember { mutableStateOf<List <Format >>(emptyList()) }
74+
75+ val autoCaptionMsg = stringResource(MR .strings.player_subtitle_auto_generated)
7276
7377 // State for permission handling
7478 var showPermissionDialog by remember { mutableStateOf(false ) }
@@ -101,9 +105,22 @@ fun DownloadFormatDialog(
101105 try {
102106 if (! streamInfo.dashManifest.isNullOrEmpty()) {
103107 // Parse from dashManifest
104- val (videos, audios) = YtDlpFormatHelper .parseFormatsFromDashManifest(streamInfo.dashManifest!! , streamInfo.url)
108+ val (videos, audios, subtitleIds ) = YtDlpFormatHelper .parseFormatsFromDashManifest(streamInfo.dashManifest!! , streamInfo.url)
105109 videoFormats = videos
106110 audioFormats = audios
111+ // Build subtitle formats from subtitleIds
112+ subtitleFormats = subtitleIds
113+ .map { it.substringAfterLast(' .' ) }
114+ .distinct()
115+ .map { lang ->
116+ Format (
117+ id = lang,
118+ url = " " ,
119+ displayLabel = getLocalizedLanguageName(lang),
120+ codec = " vtt" ,
121+ filesize = null
122+ )
123+ }
107124 isLoadingFormats = false
108125 } else {
109126 // Fetch from yt-dlp
@@ -138,6 +155,31 @@ fun DownloadFormatDialog(
138155 )
139156 }.distinctBy { it.displayLabel }
140157
158+ // Build subtitle formats from subtitles list and autoCaption
159+ val subtitleList = formatsResult.subtitles.map { lang ->
160+ Format (
161+ id = lang,
162+ url = " " ,
163+ displayLabel = getLocalizedLanguageName(lang),
164+ codec = " srt" ,
165+ filesize = null
166+ )
167+ }
168+ val autoCaptionFormat = formatsResult.autoCaption?.let { lang ->
169+ Format (
170+ id = lang,
171+ url = " " ,
172+ displayLabel = " ${getLocalizedLanguageName(lang)} ($autoCaptionMsg )" ,
173+ codec = " srt" ,
174+ filesize = null
175+ )
176+ }
177+ subtitleFormats = if (autoCaptionFormat != null ) {
178+ subtitleList + autoCaptionFormat
179+ } else {
180+ subtitleList
181+ }
182+
141183 isLoadingFormats = false
142184 }.onFailure { e ->
143185 loadError = " Failed to load formats: ${e.message} "
@@ -154,8 +196,10 @@ fun DownloadFormatDialog(
154196 var selectedType by remember { mutableStateOf(DownloadType .VIDEO ) }
155197 var selectedVideoFormat by remember { mutableStateOf<Format ?>(null ) }
156198 var selectedAudioFormat by remember { mutableStateOf<Format ?>(null ) }
199+ var selectedSubtitleFormat by remember { mutableStateOf<Format ?>(null ) }
157200 var videoDropdownExpanded by remember { mutableStateOf(false ) }
158201 var audioDropdownExpanded by remember { mutableStateOf(false ) }
202+ var subtitleDropdownExpanded by remember { mutableStateOf(false ) }
159203
160204 // Get best audio format for calculating total video download size
161205 val bestAudioFormat = remember(audioFormats) {
@@ -175,6 +219,12 @@ fun DownloadFormatDialog(
175219 }
176220 }
177221
222+ LaunchedEffect (subtitleFormats) {
223+ if (subtitleFormats.isNotEmpty() && selectedSubtitleFormat == null ) {
224+ selectedSubtitleFormat = subtitleFormats.first()
225+ }
226+ }
227+
178228 // Perform download with selected format
179229 val performDownload: (Format , DownloadType ) -> Unit = { format, type ->
180230 val finalFormatId = if (type == DownloadType .VIDEO && format.isVideoOnly) {
@@ -187,10 +237,10 @@ fun DownloadFormatDialog(
187237 url = streamInfo.url,
188238 title = streamInfo.name ? : " Unknown" ,
189239 imageUrl = streamInfo.thumbnailUrl,
190- duration = streamInfo.duration?.toInt() ? : 0 ,
240+ duration = if (type == DownloadType . SUBTITLE ) 0 else streamInfo.duration?.toInt() ? : 0 ,
191241 downloadType = type,
192242 quality = format.displayLabel,
193- codec = FormatHelper .parseCodecName(format.codec),
243+ codec = if (type == DownloadType . SUBTITLE ) " srt " else FormatHelper .parseCodecName(format.codec),
194244 formatId = finalFormatId
195245 )
196246 }
@@ -214,7 +264,7 @@ fun DownloadFormatDialog(
214264 overflow = TextOverflow .Ellipsis
215265 )
216266
217- // Download Type Selection (Video/Audio)
267+ // Download Type Selection (Video/Audio/Subtitle )
218268 Row (
219269 modifier = Modifier
220270 .fillMaxWidth()
@@ -236,6 +286,14 @@ fun DownloadFormatDialog(
236286 modifier = Modifier .weight(1f ),
237287 enabled = ! isLoadingFormats && loadError == null && audioFormats.isNotEmpty()
238288 )
289+
290+ FilterChip (
291+ selected = selectedType == DownloadType .SUBTITLE ,
292+ onClick = { selectedType = DownloadType .SUBTITLE },
293+ label = { Text (stringResource(MR .strings.download_subtitles)) },
294+ modifier = Modifier .weight(1f ),
295+ enabled = ! isLoadingFormats && loadError == null && subtitleFormats.isNotEmpty()
296+ )
239297 }
240298
241299 // Content area based on state
@@ -382,6 +440,43 @@ fun DownloadFormatDialog(
382440 }
383441 }
384442 }
443+
444+ DownloadType .SUBTITLE -> {
445+ if (subtitleFormats.isNotEmpty()) {
446+ ExposedDropdownMenuBox (
447+ expanded = subtitleDropdownExpanded,
448+ onExpandedChange = { subtitleDropdownExpanded = it }
449+ ) {
450+ OutlinedTextField (
451+ value = selectedSubtitleFormat?.displayLabel
452+ ? : " Select subtitle" ,
453+ onValueChange = {},
454+ readOnly = true ,
455+ label = { Text (stringResource(MR .strings.download_subtitles)) },
456+ trailingIcon = { ExposedDropdownMenuDefaults .TrailingIcon (expanded = subtitleDropdownExpanded) },
457+ modifier = Modifier
458+ .fillMaxWidth()
459+ .menuAnchor()
460+ )
461+ ExposedDropdownMenu (
462+ expanded = subtitleDropdownExpanded,
463+ onDismissRequest = { subtitleDropdownExpanded = false }
464+ ) {
465+ subtitleFormats.forEach { format ->
466+ DropdownMenuItem (
467+ text = {
468+ Text (format.displayLabel)
469+ },
470+ onClick = {
471+ selectedSubtitleFormat = format
472+ subtitleDropdownExpanded = false
473+ }
474+ )
475+ }
476+ }
477+ }
478+ }
479+ }
385480 }
386481 }
387482 }
@@ -393,6 +488,7 @@ fun DownloadFormatDialog(
393488 val selectedFormat = when (selectedType) {
394489 DownloadType .VIDEO -> selectedVideoFormat
395490 DownloadType .AUDIO -> selectedAudioFormat
491+ DownloadType .SUBTITLE -> selectedSubtitleFormat
396492 }
397493
398494 selectedFormat?.let { format ->
@@ -444,6 +540,7 @@ fun DownloadFormatDialog(
444540 enabled = when (selectedType) {
445541 DownloadType .VIDEO -> selectedVideoFormat != null
446542 DownloadType .AUDIO -> selectedAudioFormat != null
543+ DownloadType .SUBTITLE -> selectedSubtitleFormat != null
447544 }
448545 ) {
449546 Text (stringResource(MR .strings.download))
@@ -477,6 +574,7 @@ fun DownloadFormatDialog(
477574 val selectedFormat = when (selectedType) {
478575 DownloadType .VIDEO -> selectedVideoFormat
479576 DownloadType .AUDIO -> selectedAudioFormat
577+ DownloadType .SUBTITLE -> selectedSubtitleFormat
480578 }
481579
482580 AlertDialog (
0 commit comments