diff --git a/CHANGELOG.md b/CHANGELOG.md index 7573d0f..6ed2170 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.7.5 +- Fix auto translated closed captions ( #50 ) +- Deprecated `autoGenerated` from `getManifest`. +- Added `autoGenerated` parameter to `manifest.getByLanguage(...)` + ## 1.7.4 - Fix slow download ( #92 ) - Fix stream retrieving on some videos ( #90 ) diff --git a/lib/src/exceptions/fatal_failure_exception.dart b/lib/src/exceptions/fatal_failure_exception.dart index 1432a34..d4a2fd3 100644 --- a/lib/src/exceptions/fatal_failure_exception.dart +++ b/lib/src/exceptions/fatal_failure_exception.dart @@ -3,7 +3,8 @@ import 'package:http/http.dart'; import 'youtube_explode_exception.dart'; /// Exception thrown when a fatal failure occurs. -class FatalFailureException implements YoutubeExplodeException { +class FatalFailureException + implements YoutubeExplodeException { /// Description message @override final String message; @@ -20,4 +21,7 @@ If this issue persists, please report it on the project's GitHub page. Request: ${response.request} Response: (${response.statusCode}) '''; + + @override + String toString() => '$runtimeType: $message'; } diff --git a/lib/src/exceptions/request_limit_exceeded_exception.dart b/lib/src/exceptions/request_limit_exceeded_exception.dart index 251b561..46b8129 100644 --- a/lib/src/exceptions/request_limit_exceeded_exception.dart +++ b/lib/src/exceptions/request_limit_exceeded_exception.dart @@ -21,4 +21,7 @@ Unfortunately, there's nothing the library can do to work around this error. Request: ${response.request} Response: $response '''; + + @override + String toString() => '$runtimeType: $message'; } diff --git a/lib/src/exceptions/transient_failure_exception.dart b/lib/src/exceptions/transient_failure_exception.dart index 2a9671e..9551341 100644 --- a/lib/src/exceptions/transient_failure_exception.dart +++ b/lib/src/exceptions/transient_failure_exception.dart @@ -22,5 +22,5 @@ Response: $response '''; @override - String toString() => 'TransientFailureException: $message'; + String toString() => '$runtimeType: $message'; } diff --git a/lib/src/exceptions/video_requires_purchase_exception.dart b/lib/src/exceptions/video_requires_purchase_exception.dart index 3386854..c0760b4 100644 --- a/lib/src/exceptions/video_requires_purchase_exception.dart +++ b/lib/src/exceptions/video_requires_purchase_exception.dart @@ -15,4 +15,7 @@ class VideoRequiresPurchaseException implements VideoUnplayableException { : message = 'Video `$videoId` is unplayable because it requires purchase.' 'Streams are not available for this video.' 'There is a preview video available: `$previewVideoId`.'; + + @override + String toString() => '$runtimeType: $message'; } diff --git a/lib/src/exceptions/video_unavailable_exception.dart b/lib/src/exceptions/video_unavailable_exception.dart index 6f4959e..5fd6157 100644 --- a/lib/src/exceptions/video_unavailable_exception.dart +++ b/lib/src/exceptions/video_unavailable_exception.dart @@ -20,4 +20,7 @@ class VideoUnavailableException implements VideoUnplayableException { 'If you can however open this video in your browser in incognito mode, ' // ignore: lines_longer_than_80_chars 'it most likely means that YouTube changed something, which broke this library.\n' // ignore: lines_longer_than_80_chars 'Please report this issue on GitHub in that case.'; + + @override + String toString() => '$runtimeType: $message'; } diff --git a/lib/src/exceptions/video_unplayable_exception.dart b/lib/src/exceptions/video_unplayable_exception.dart index fc1f0c3..74f26ef 100644 --- a/lib/src/exceptions/video_unplayable_exception.dart +++ b/lib/src/exceptions/video_unplayable_exception.dart @@ -28,4 +28,7 @@ class VideoUnplayableException implements YoutubeExplodeException { VideoUnplayableException.notLiveStream(VideoId videoId) : message = 'Video \'$videoId\' is not an ongoing live stream.\n' 'Live stream manifest is not available for this video'; + + @override + String toString() => '$runtimeType: $message'; } diff --git a/lib/src/exceptions/youtube_explode_exception.dart b/lib/src/exceptions/youtube_explode_exception.dart index fd92fd2..44a75c8 100644 --- a/lib/src/exceptions/youtube_explode_exception.dart +++ b/lib/src/exceptions/youtube_explode_exception.dart @@ -5,7 +5,4 @@ abstract class YoutubeExplodeException implements Exception { /// YoutubeExplodeException(this.message); - - @override - String toString() => '$runtimeType: $message}'; -} +} \ No newline at end of file diff --git a/lib/src/videos/closed_captions/closed_caption_client.dart b/lib/src/videos/closed_captions/closed_caption_client.dart index cc5443d..b7352d1 100644 --- a/lib/src/videos/closed_captions/closed_caption_client.dart +++ b/lib/src/videos/closed_captions/closed_caption_client.dart @@ -22,55 +22,31 @@ class ClosedCaptionClient { /// Gets the manifest that contains information /// about available closed caption tracks in the specified video. - Future getManifest(dynamic videoId, - {bool autoGenerated = false}) async { + Future getManifest( + dynamic videoId, + {@Deprecated('Not used anymore, use track.isAutoGenerated to see if a track is autogenerated or not.') // ignore: lines_longer_than_80_chars + bool autoGenerated = false, + List formats = const [ + ClosedCaptionFormat.srv1, + ClosedCaptionFormat.srv2, + ClosedCaptionFormat.srv3, + ClosedCaptionFormat.ttml, + ClosedCaptionFormat.vtt + ]}) 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 tracks = {}; + var videoInfoResponse = + await VideoInfoResponse.get(_httpClient, videoId.value); + var playerResponse = videoInfoResponse.playerResponse; - 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)); - } + for (var track in playerResponse.closedCaptionTrack) { + for (var ext in formats) { + tracks.add(ClosedCaptionTrackInfo( + Uri.parse(track.url) + .replaceQueryParameters({'fmt': ext.formatCode}), + Language(track.languageCode, track.languageName), + isAutoGenerated: track.autoGenerated, + format: ext)); } } return ClosedCaptionManifest(tracks); diff --git a/lib/src/videos/closed_captions/closed_caption_manifest.dart b/lib/src/videos/closed_captions/closed_caption_manifest.dart index 08a0659..20db7f7 100644 --- a/lib/src/videos/closed_captions/closed_caption_manifest.dart +++ b/lib/src/videos/closed_captions/closed_caption_manifest.dart @@ -14,15 +14,17 @@ class ClosedCaptionManifest { : tracks = UnmodifiableListView(tracks); /// Gets all the closed caption tracks in the specified language and format. + /// If [autoGenerated] is true auto generated tracks are included as well. /// Returns an empty list of no track is found. List getByLanguage(String language, - {ClosedCaptionFormat format}) { + {ClosedCaptionFormat format, bool autoGenerated = false}) { language = language.toLowerCase(); return tracks .where((e) => (e.language.code.toLowerCase() == language || e.language.name.toLowerCase() == language) && - (format == null || e.format == format)) + (format == null || e.format == format) && + (!autoGenerated || e.isAutoGenerated)) .toList(); } } diff --git a/lib/src/videos/closed_captions/closed_caption_track_info.dart b/lib/src/videos/closed_captions/closed_caption_track_info.dart index d6cb92d..0df1244 100644 --- a/lib/src/videos/closed_captions/closed_caption_track_info.dart +++ b/lib/src/videos/closed_captions/closed_caption_track_info.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -31,7 +33,7 @@ class ClosedCaptionTrackInfo extends Equatable { /// Keeping the same format. ClosedCaptionTrackInfo autoTranslate(String lang) { return ClosedCaptionTrackInfo( - url.replaceQueryParameters({'tlang': lang}), Language(lang, ''), + url.replaceQueryParameters({'tlang': lang}), Language(lang, lang), isAutoGenerated: isAutoGenerated, format: format); } diff --git a/pubspec.yaml b/pubspec.yaml index d6a83a5..abf89db 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: youtube_explode_dart description: A port in dart of the youtube explode library. Supports several API functions without the need of Youtube API Key. -version: 1.7.4 +version: 1.7.5 homepage: https://github.com/Hexer10/youtube_explode_dart environment: diff --git a/test/closed_caption_test.dart b/test/closed_caption_test.dart index be478be..d586acd 100644 --- a/test/closed_caption_test.dart +++ b/test/closed_caption_test.dart @@ -22,10 +22,17 @@ void main() { expect(track.captions, isNotEmpty); }); + + test('Get closed auto translated caption track file of a video', () async { + var manifest = await yt.videos.closedCaptions.getManifest('WOxr2dmLHLo'); + var trackInfo = manifest.tracks.first; + var subtitles = await yt.videos.closedCaptions.getSubTitles(trackInfo); + + expect(subtitles, isNotEmpty); + }); test('Get closed caption track at a specific time', () async { - var manifest = await yt.videos.closedCaptions - .getManifest('WOxr2dmLHLo', autoGenerated: false); - var trackInfo = manifest.getByLanguage('en'); + var manifest = await yt.videos.closedCaptions.getManifest('qfJthDvcZ08'); + var trackInfo = manifest.getByLanguage('en', autoGenerated: false); var track = await yt.videos.closedCaptions.get(trackInfo.first); var caption = track.getByTime(const Duration(hours: 0, minutes: 1, seconds: 48)); @@ -33,13 +40,12 @@ void main() { expect(caption, isNotNull); expect(caption.parts, isEmpty); expect(caption.text, - 'The second way to add subtitles is the one\nwe always use.'); + 'But what if you don\'t have a captions file'); }); test('Get auto-generated closed caption track at a specific time', () async { - var manifest = await yt.videos.closedCaptions - .getManifest('ppJy5uGZLi4', autoGenerated: true); - var trackInfo = manifest.getByLanguage('en'); + var manifest = await yt.videos.closedCaptions.getManifest('ppJy5uGZLi4'); + var trackInfo = manifest.getByLanguage('en', autoGenerated: true); var track = await yt.videos.closedCaptions.get(trackInfo.first); var caption = track.getByTime(const Duration(hours: 0, minutes: 13, seconds: 22));