Version 1.7.5
This commit is contained in:
parent
a9275af1e2
commit
4498e79c89
|
@ -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 )
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -22,5 +22,5 @@ Response: $response
|
|||
''';
|
||||
|
||||
@override
|
||||
String toString() => 'TransientFailureException: $message';
|
||||
String toString() => '$runtimeType: $message';
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -5,7 +5,4 @@ abstract class YoutubeExplodeException implements Exception {
|
|||
|
||||
///
|
||||
YoutubeExplodeException(this.message);
|
||||
|
||||
@override
|
||||
String toString() => '$runtimeType: $message}';
|
||||
}
|
||||
}
|
|
@ -22,55 +22,31 @@ class ClosedCaptionClient {
|
|||
|
||||
/// Gets the manifest that contains information
|
||||
/// about available closed caption tracks in the specified video.
|
||||
Future<ClosedCaptionManifest> getManifest(dynamic videoId,
|
||||
{bool autoGenerated = false}) async {
|
||||
Future<ClosedCaptionManifest> 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<ClosedCaptionFormat> formats = const [
|
||||
ClosedCaptionFormat.srv1,
|
||||
ClosedCaptionFormat.srv2,
|
||||
ClosedCaptionFormat.srv3,
|
||||
ClosedCaptionFormat.ttml,
|
||||
ClosedCaptionFormat.vtt
|
||||
]}) async {
|
||||
videoId = VideoId.fromString(videoId);
|
||||
var tracks = <ClosedCaptionTrackInfo>[];
|
||||
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 = <ClosedCaptionTrackInfo>{};
|
||||
var videoInfoResponse =
|
||||
await VideoInfoResponse.get(_httpClient, videoId.value);
|
||||
var playerResponse = videoInfoResponse.playerResponse;
|
||||
|
||||
var langList = <String>[];
|
||||
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);
|
||||
|
|
|
@ -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<ClosedCaptionTrackInfo> 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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));
|
||||
|
|
Loading…
Reference in New Issue