diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a1ad56..e91bea8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.10.1 +- Fix issue #146: Closed Captions couldn't be extracted anymore. +- Code cleanup. +- + ## 1.10.0 - Fix issue #144: get_video_info was removed from yt. - Min sdk version now is 2.13.0 diff --git a/lib/src/common/engagement.dart b/lib/src/common/engagement.dart index 6e15a0b..1a3afb0 100644 --- a/lib/src/common/engagement.dart +++ b/lib/src/common/engagement.dart @@ -5,7 +5,6 @@ part 'engagement.freezed.dart'; /// User activity statistics. @freezed class Engagement with _$Engagement { - const Engagement._(); const factory Engagement( /// View count. @@ -18,6 +17,8 @@ class Engagement with _$Engagement { int? dislikeCount, ) = _Engagement; + const Engagement._(); + /// Average user rating in stars (1 star to 5 stars). /// Returns -1 if likeCount or dislikeCount is null. num get avgRating { diff --git a/lib/src/reverse_engineering/pages/channel_page.dart b/lib/src/reverse_engineering/pages/channel_page.dart index 671e872..dda8d42 100644 --- a/lib/src/reverse_engineering/pages/channel_page.dart +++ b/lib/src/reverse_engineering/pages/channel_page.dart @@ -1,11 +1,11 @@ import 'package:html/parser.dart' as parser; -import 'package:youtube_explode_dart/src/reverse_engineering/models/youtube_page.dart'; import '../../exceptions/exceptions.dart'; import '../../extensions/helpers_extension.dart'; import '../../retry.dart'; -import '../youtube_http_client.dart'; import '../models/initial_data.dart'; +import '../models/youtube_page.dart'; +import '../youtube_http_client.dart'; /// class ChannelPage extends YoutubePage<_InitialData> { diff --git a/lib/src/reverse_engineering/pages/channel_upload_page.dart b/lib/src/reverse_engineering/pages/channel_upload_page.dart index 81ed763..40e3e9f 100644 --- a/lib/src/reverse_engineering/pages/channel_upload_page.dart +++ b/lib/src/reverse_engineering/pages/channel_upload_page.dart @@ -1,15 +1,14 @@ import 'package:collection/collection.dart'; -import 'package:html/dom.dart'; import 'package:html/parser.dart' as parser; -import 'package:youtube_explode_dart/src/reverse_engineering/models/youtube_page.dart'; import '../../channels/channel_video.dart'; import '../../exceptions/exceptions.dart'; import '../../extensions/helpers_extension.dart'; import '../../retry.dart'; import '../../videos/videos.dart'; -import '../youtube_http_client.dart'; import '../models/initial_data.dart'; +import '../models/youtube_page.dart'; +import '../youtube_http_client.dart'; /// class ChannelUploadPage extends YoutubePage<_InitialData> { diff --git a/lib/src/reverse_engineering/pages/playlist_page.dart b/lib/src/reverse_engineering/pages/playlist_page.dart index 17c5d22..a4d03f8 100644 --- a/lib/src/reverse_engineering/pages/playlist_page.dart +++ b/lib/src/reverse_engineering/pages/playlist_page.dart @@ -1,12 +1,12 @@ import 'package:collection/collection.dart'; import 'package:html/parser.dart' as parser; -import 'package:youtube_explode_dart/src/reverse_engineering/models/youtube_page.dart'; import '../../../youtube_explode_dart.dart'; import '../../extensions/helpers_extension.dart'; import '../../retry.dart'; -import '../youtube_http_client.dart'; import '../models/initial_data.dart'; +import '../models/youtube_page.dart'; +import '../youtube_http_client.dart'; /// class PlaylistPage extends YoutubePage<_InitialData> { diff --git a/lib/src/reverse_engineering/pages/search_page.dart b/lib/src/reverse_engineering/pages/search_page.dart index f0ea5a7..d7e2de7 100644 --- a/lib/src/reverse_engineering/pages/search_page.dart +++ b/lib/src/reverse_engineering/pages/search_page.dart @@ -11,8 +11,8 @@ import '../../search/related_query.dart'; import '../../search/search_filter.dart'; import '../../search/search_video.dart'; import '../../videos/videos.dart'; -import '../youtube_http_client.dart'; import '../models/initial_data.dart'; +import '../youtube_http_client.dart'; /// class SearchPage extends YoutubePage<_InitialData> { diff --git a/lib/src/reverse_engineering/pages/watch_page.dart b/lib/src/reverse_engineering/pages/watch_page.dart index da83bb4..1baa5eb 100644 --- a/lib/src/reverse_engineering/pages/watch_page.dart +++ b/lib/src/reverse_engineering/pages/watch_page.dart @@ -1,16 +1,16 @@ import 'package:collection/collection.dart'; import 'package:html/dom.dart'; import 'package:html/parser.dart' as parser; -import 'package:youtube_explode_dart/src/reverse_engineering/models/youtube_page.dart'; import '../../../youtube_explode_dart.dart'; import '../../extensions/helpers_extension.dart'; import '../../retry.dart'; import '../../videos/video_id.dart'; +import '../models/initial_data.dart'; +import '../models/youtube_page.dart'; import '../player/player_response.dart'; import '../youtube_http_client.dart'; import 'player_config_base.dart'; -import '../models/initial_data.dart'; /// class WatchPage extends YoutubePage<_InitialData> { @@ -21,12 +21,11 @@ class WatchPage extends YoutubePage<_InitialData> { static final RegExp _visitorInfoLiveExp = RegExp('VISITOR_INFO1_LIVE=([^;]+)'); static final RegExp _yscExp = RegExp('YSC=([^;]+)'); - static final RegExp _playerResponseExp = - RegExp(r'var\s+ytInitialPlayerResponse\s*=\s*(\{.*\})'); - static final _xsfrTokenExp = RegExp(r'"XSRF_TOKEN"\s*:\s*"(.+?)"'); @override + // Overridden to be non-nullable. + // ignore: overridden_fields final Document root; /// @@ -48,18 +47,6 @@ class WatchPage extends YoutubePage<_InitialData> { return 'https://youtube.com$url'; } - late final String xsfrToken = getXsfrToken()!.replaceAll(r'\u003d', '='); - - /// - String? getXsfrToken() { - return _xsfrTokenExp - .firstMatch(root - .querySelectorAll('script') - .firstWhere((e) => _xsfrTokenExp.hasMatch(e.text)) - .text) - ?.group(1); - } - /// bool get isOk => root.body?.querySelector('#player') != null; diff --git a/lib/src/reverse_engineering/player/player_response.dart b/lib/src/reverse_engineering/player/player_response.dart index 3a5f8f1..9a1c209 100644 --- a/lib/src/reverse_engineering/player/player_response.dart +++ b/lib/src/reverse_engineering/player/player_response.dart @@ -148,8 +148,8 @@ class ClosedCaptionTrack { String get languageCode => root.getT('languageCode')!; /// - String get languageName => - root.get('name')!.getT>('runs')!.parseRuns(); + String? get languageName => + root.get('name')!.getT('simpleText'); /// bool get autoGenerated => diff --git a/lib/src/reverse_engineering/responses/video_info_client.dart b/lib/src/reverse_engineering/responses/video_info_client.dart index 279b6f7..2c5047b 100644 --- a/lib/src/reverse_engineering/responses/video_info_client.dart +++ b/lib/src/reverse_engineering/responses/video_info_client.dart @@ -1,15 +1,16 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:http_parser/http_parser.dart'; import '../../exceptions/exceptions.dart'; import '../../extensions/helpers_extension.dart'; import '../../retry.dart'; -import '../youtube_http_client.dart'; -import '../player/player_response.dart'; import '../models/stream_info_provider.dart'; +import '../player/player_response.dart'; +import '../youtube_http_client.dart'; /// /// -@deprecated +@Deprecated('This endpoint is not supported anymore.') class VideoInfoClient { final Map root; @@ -51,6 +52,7 @@ class VideoInfoClient { VideoInfoClient.parse(String raw) : root = Uri.splitQueryString(raw); /// + @alwaysThrows static Future get( YoutubeHttpClient httpClient, String videoId, [String? sts]) { diff --git a/lib/src/search/search_video.dart b/lib/src/search/search_video.dart index 74a8649..adb45fb 100644 --- a/lib/src/search/search_video.dart +++ b/lib/src/search/search_video.dart @@ -37,6 +37,7 @@ class SearchVideo with _$SearchVideo, BaseSearchContent { String? uploadDate, /// True if this video is a live stream. + // ignore: avoid_positional_boolean_parameters bool isLive, /// Channel id diff --git a/lib/src/search/search_video.freezed.dart b/lib/src/search/search_video.freezed.dart index 14ff204..df4b43c 100644 --- a/lib/src/search/search_video.freezed.dart +++ b/lib/src/search/search_video.freezed.dart @@ -72,6 +72,7 @@ mixin _$SearchVideo { String? get uploadDate => throw _privateConstructorUsedError; /// True if this video is a live stream. +// ignore: avoid_positional_boolean_parameters bool get isLive => throw _privateConstructorUsedError; /// Channel id @@ -317,6 +318,7 @@ class _$_SearchVideo with BaseSearchContent implements _SearchVideo { @override /// True if this video is a live stream. +// ignore: avoid_positional_boolean_parameters final bool isLive; @override @@ -428,6 +430,7 @@ abstract class _SearchVideo implements SearchVideo, BaseSearchContent { @override /// True if this video is a live stream. +// ignore: avoid_positional_boolean_parameters bool get isLive => throw _privateConstructorUsedError; @override diff --git a/lib/src/videos/closed_captions/closed_caption_client.dart b/lib/src/videos/closed_captions/closed_caption_client.dart index fa38be4..1175b52 100644 --- a/lib/src/videos/closed_captions/closed_caption_client.dart +++ b/lib/src/videos/closed_captions/closed_caption_client.dart @@ -1,7 +1,8 @@ +import 'package:youtube_explode_dart/src/reverse_engineering/pages/watch_page.dart'; + import '../../extensions/helpers_extension.dart'; import '../../reverse_engineering/responses/closed_caption_client.dart' as re show ClosedCaptionClient; -import '../../reverse_engineering/responses/video_info_client.dart'; import '../../reverse_engineering/youtube_http_client.dart'; import '../videos.dart'; import 'closed_caption.dart'; @@ -34,16 +35,16 @@ class ClosedCaptionClient { ]}) async { videoId = VideoId.fromString(videoId); var tracks = {}; - var videoInfoResponse = - await VideoInfoClient.get(_httpClient, videoId.value); - var playerResponse = videoInfoResponse.playerResponse; + var watchPage = + await WatchPage.get(_httpClient, videoId.value); + var playerResponse = watchPage.playerResponse!; for (final track in playerResponse.closedCaptionTrack) { for (final ext in formats) { tracks.add(ClosedCaptionTrackInfo( Uri.parse(track.url) .replaceQueryParameters({'fmt': ext.formatCode}), - Language(track.languageCode, track.languageName), + Language(track.languageCode, track.languageName ?? ''), isAutoGenerated: track.autoGenerated, format: ext)); } diff --git a/lib/src/videos/closed_captions/language.dart b/lib/src/videos/closed_captions/language.dart index 76277eb..cece02f 100644 --- a/lib/src/videos/closed_captions/language.dart +++ b/lib/src/videos/closed_captions/language.dart @@ -13,14 +13,11 @@ class Language with _$Language { /// ISO 639-1 code of this language. String code, - /// Full English name of this language. + /// Full English name of this language. This could be an empty string. String name) = _Language; const Language._(); - @override - String toString() => 'Language: $name'; - /// factory Language.fromJson(Map json) => _$LanguageFromJson(json); diff --git a/lib/src/videos/closed_captions/language.freezed.dart b/lib/src/videos/closed_captions/language.freezed.dart index 1331a2e..fc58ebd 100644 --- a/lib/src/videos/closed_captions/language.freezed.dart +++ b/lib/src/videos/closed_captions/language.freezed.dart @@ -40,7 +40,7 @@ mixin _$Language { /// ISO 639-1 code of this language. String get code => throw _privateConstructorUsedError; - /// Full English name of this language. + /// Full English name of this language. This could be an empty string. String get name => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @@ -131,9 +131,14 @@ class _$_Language extends _Language { final String code; @override - /// Full English name of this language. + /// Full English name of this language. This could be an empty string. final String name; + @override + String toString() { + return 'Language(code: $code, name: $name)'; + } + @override bool operator ==(dynamic other) { return identical(this, other) || @@ -173,7 +178,7 @@ abstract class _Language extends Language { String get code => throw _privateConstructorUsedError; @override - /// Full English name of this language. + /// Full English name of this language. This could be an empty string. String get name => throw _privateConstructorUsedError; @override @JsonKey(ignore: true) diff --git a/lib/src/videos/comments/comments_client.dart b/lib/src/videos/comments/comments_client.dart index 7f99693..1c5c3a5 100644 --- a/lib/src/videos/comments/comments_client.dart +++ b/lib/src/videos/comments/comments_client.dart @@ -1,7 +1,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import '../../channels/channel_id.dart'; -import '../../extensions/helpers_extension.dart'; import '../../reverse_engineering/responses/comments_client.dart' as re; import '../../reverse_engineering/youtube_http_client.dart'; import '../videos.dart'; diff --git a/lib/src/videos/streams/bitrate.dart b/lib/src/videos/streams/bitrate.dart index a48bef9..57f9be0 100644 --- a/lib/src/videos/streams/bitrate.dart +++ b/lib/src/videos/streams/bitrate.dart @@ -26,9 +26,6 @@ class Bitrate with Comparable, _$Bitrate { @override int compareTo(Bitrate other) => bitsPerSecond.compareTo(other.bitsPerSecond); - @override - List get props => [bitsPerSecond]; - String _getLargestSymbol() { if (gigaBitsPerSecond.abs() >= 1) { return 'Gbit/s'; diff --git a/lib/src/videos/streams/streams_client.dart b/lib/src/videos/streams/streams_client.dart index eff79b9..a877e12 100644 --- a/lib/src/videos/streams/streams_client.dart +++ b/lib/src/videos/streams/streams_client.dart @@ -4,10 +4,8 @@ import '../../reverse_engineering/cipher/cipher_operations.dart'; import '../../reverse_engineering/dash_manifest.dart'; import '../../reverse_engineering/heuristics.dart'; import '../../reverse_engineering/models/stream_info_provider.dart'; -import '../../reverse_engineering/pages/embed_page.dart'; import '../../reverse_engineering/pages/watch_page.dart'; import '../../reverse_engineering/player/player_source.dart'; -import '../../reverse_engineering/responses/video_info_client.dart'; import '../../reverse_engineering/youtube_http_client.dart'; import '../video_id.dart'; import 'bitrate.dart'; @@ -37,7 +35,8 @@ class StreamsClient { return DashManifest.get(_httpClient, dashManifestUrl); } - Future _getStreamContextFromVideoInfo(VideoId videoId) async { + // Not used anymore since Youtube removed the `video_info` endpoint. +/* Future _getStreamContextFromVideoInfo(VideoId videoId) async { var embedPage = await EmbedPage.get(_httpClient, videoId.toString()); var playerConfig = embedPage.playerConfig; if (playerConfig == null) { @@ -79,7 +78,7 @@ class StreamsClient { streamInfoProviders.addAll(dashManifest.streams); } return StreamContext(streamInfoProviders, cipherOperations); - } + }*/ Future _getStreamContextFromWatchPage(VideoId videoId) async { final watchPage = await WatchPage.get(_httpClient, videoId.toString()); diff --git a/lib/src/videos/video_client.dart b/lib/src/videos/video_client.dart index e662a2b..be06b7e 100644 --- a/lib/src/videos/video_client.dart +++ b/lib/src/videos/video_client.dart @@ -1,5 +1,3 @@ -import 'package:youtube_explode_dart/src/reverse_engineering/player/player_response.dart'; - import '../channels/channel_id.dart'; import '../common/common.dart'; import '../extensions/helpers_extension.dart'; diff --git a/lib/src/videos/video_id.dart b/lib/src/videos/video_id.dart index 9cfb52f..83b3fb2 100644 --- a/lib/src/videos/video_id.dart +++ b/lib/src/videos/video_id.dart @@ -11,13 +11,6 @@ class VideoId with _$VideoId { static final _shortMatchExp = RegExp(r'youtu\.be/(.*?)(?:\?|&|/|$)'); static final _embedMatchExp = RegExp(r'youtube\..+?/embed/(.*?)(?:\?|&|/|$)'); - const VideoId._(); - - const factory VideoId._internal( - - /// ID as string. - String value) = _VideoId; - /// Initializes an instance of [VideoId] with a url or video id. factory VideoId(String idOrUrl) { final id = parseVideoId(idOrUrl); @@ -29,6 +22,13 @@ class VideoId with _$VideoId { return VideoId._internal(id); } + const VideoId._(); + + const factory VideoId._internal( + + /// ID as string. + String value) = _VideoId; + /// Converts [obj] to a [VideoId] by calling .toString on that object. /// If it is already a [VideoId], [obj] is returned factory VideoId.fromString(dynamic obj) { diff --git a/pubspec.yaml b/pubspec.yaml index b4a7143..21788d9 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.10.0 +version: 1.10.1 homepage: https://github.com/Hexer10/youtube_explode_dart