import 'package:xml/xml.dart' as xml; import '../../extensions/helpers_extension.dart'; import '../../reverse_engineering/responses/responses.dart' hide ClosedCaption, ClosedCaptionPart, ClosedCaptionTrack; import '../../reverse_engineering/youtube_http_client.dart'; import '../videos.dart'; import 'closed_caption.dart'; import 'closed_caption_manifest.dart'; import 'closed_caption_part.dart'; import 'closed_caption_track.dart'; import 'closed_caption_track_info.dart'; import 'language.dart'; /// Queries related to closed captions of YouTube videos. class ClosedCaptionClient { final YoutubeHttpClient _httpClient; /// Initializes an instance of [ClosedCaptionClient] ClosedCaptionClient(this._httpClient); /// Gets the manifest that contains information /// about available closed caption tracks in the specified video. Future getManifest(dynamic videoId, {bool autoGenerated = false}) async { videoId = VideoId.fromString(videoId); var tracks = []; if (!autoGenerated) { var subList = await _httpClient.get( 'https://video.google.com/timedtext?hl=en&type=list&v=${videoId.value}', validate: true); // ignore: deprecated_member_use var content = xml.parse(subList.body); var langList = []; for (var track in content.findAllElements('track')) { var lang = track.getAttribute('lang_code'); if (langList.contains(lang)) { continue; } langList.add(lang); for (var ext in ClosedCaptionFormat.values) { tracks.add(ClosedCaptionTrackInfo( Uri.parse('https://www.youtube.com/api/timedtext') .replaceQueryParameters({ 'lang': lang, 'v': videoId.value, 'fmt': ext.formatCode, 'name': track.getAttribute('name'), }), Language(lang, track.getAttribute('lang_translated')), format: ext)); } } if (langList.isEmpty) { return ClosedCaptionManifest([]); } return ClosedCaptionManifest(tracks); } else { var videoInfoResponse = await VideoInfoResponse.get(_httpClient, videoId.value); var playerResponse = videoInfoResponse.playerResponse; for (var track in playerResponse.closedCaptionTrack) { for (var ext in ClosedCaptionFormat.values) { tracks.add(ClosedCaptionTrackInfo( Uri.parse(track.url) .replaceQueryParameters({'fmt': ext.formatCode}), Language(track.languageCode, track.languageName), isAutoGenerated: track.autoGenerated, format: ext)); } } } return ClosedCaptionManifest(tracks); } /// Gets the actual closed caption track which is /// identified by the specified metadata. Future get(ClosedCaptionTrackInfo trackInfo) async { var response = await ClosedCaptionTrackResponse.get( _httpClient, trackInfo.url); var captions = response.closedCaptions .where((e) => !e.text.isNullOrWhiteSpace) .map((e) => ClosedCaption(e.text, e.offset, e.duration, e.getParts().map((f) => ClosedCaptionPart(f.text, f.offset)))); return ClosedCaptionTrack(captions); } /// Returns the subtitles as a string. Future getSubTitles(ClosedCaptionTrackInfo trackInfo) => _httpClient.getString(trackInfo.url); }