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
|
## 1.7.4
|
||||||
- Fix slow download ( #92 )
|
- Fix slow download ( #92 )
|
||||||
- Fix stream retrieving on some videos ( #90 )
|
- Fix stream retrieving on some videos ( #90 )
|
||||||
|
|
|
@ -3,7 +3,8 @@ import 'package:http/http.dart';
|
||||||
import 'youtube_explode_exception.dart';
|
import 'youtube_explode_exception.dart';
|
||||||
|
|
||||||
/// Exception thrown when a fatal failure occurs.
|
/// Exception thrown when a fatal failure occurs.
|
||||||
class FatalFailureException implements YoutubeExplodeException {
|
class FatalFailureException
|
||||||
|
implements YoutubeExplodeException {
|
||||||
/// Description message
|
/// Description message
|
||||||
@override
|
@override
|
||||||
final String message;
|
final String message;
|
||||||
|
@ -20,4 +21,7 @@ If this issue persists, please report it on the project's GitHub page.
|
||||||
Request: ${response.request}
|
Request: ${response.request}
|
||||||
Response: (${response.statusCode})
|
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}
|
Request: ${response.request}
|
||||||
Response: $response
|
Response: $response
|
||||||
''';
|
''';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => '$runtimeType: $message';
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,5 +22,5 @@ Response: $response
|
||||||
''';
|
''';
|
||||||
|
|
||||||
@override
|
@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.'
|
: message = 'Video `$videoId` is unplayable because it requires purchase.'
|
||||||
'Streams are not available for this video.'
|
'Streams are not available for this video.'
|
||||||
'There is a preview video available: `$previewVideoId`.';
|
'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
|
'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
|
'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.';
|
'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)
|
VideoUnplayableException.notLiveStream(VideoId videoId)
|
||||||
: message = 'Video \'$videoId\' is not an ongoing live stream.\n'
|
: message = 'Video \'$videoId\' is not an ongoing live stream.\n'
|
||||||
'Live stream manifest is not available for this video';
|
'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);
|
YoutubeExplodeException(this.message);
|
||||||
|
}
|
||||||
@override
|
|
||||||
String toString() => '$runtimeType: $message}';
|
|
||||||
}
|
|
|
@ -22,55 +22,31 @@ class ClosedCaptionClient {
|
||||||
|
|
||||||
/// Gets the manifest that contains information
|
/// Gets the manifest that contains information
|
||||||
/// about available closed caption tracks in the specified video.
|
/// about available closed caption tracks in the specified video.
|
||||||
Future<ClosedCaptionManifest> getManifest(dynamic videoId,
|
Future<ClosedCaptionManifest> getManifest(
|
||||||
{bool autoGenerated = false}) async {
|
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);
|
videoId = VideoId.fromString(videoId);
|
||||||
var tracks = <ClosedCaptionTrackInfo>[];
|
var tracks = <ClosedCaptionTrackInfo>{};
|
||||||
if (!autoGenerated) {
|
var videoInfoResponse =
|
||||||
var subList = await _httpClient.get(
|
await VideoInfoResponse.get(_httpClient, videoId.value);
|
||||||
'https://video.google.com/timedtext?hl=en&type=list&v=${videoId.value}',
|
var playerResponse = videoInfoResponse.playerResponse;
|
||||||
validate: true);
|
|
||||||
// ignore: deprecated_member_use
|
|
||||||
var content = xml.parse(subList.body);
|
|
||||||
|
|
||||||
var langList = <String>[];
|
for (var track in playerResponse.closedCaptionTrack) {
|
||||||
for (var track in content.findAllElements('track')) {
|
for (var ext in formats) {
|
||||||
var lang = track.getAttribute('lang_code');
|
tracks.add(ClosedCaptionTrackInfo(
|
||||||
if (langList.contains(lang)) {
|
Uri.parse(track.url)
|
||||||
continue;
|
.replaceQueryParameters({'fmt': ext.formatCode}),
|
||||||
}
|
Language(track.languageCode, track.languageName),
|
||||||
langList.add(lang);
|
isAutoGenerated: track.autoGenerated,
|
||||||
for (var ext in ClosedCaptionFormat.values) {
|
format: ext));
|
||||||
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);
|
return ClosedCaptionManifest(tracks);
|
||||||
|
|
|
@ -14,15 +14,17 @@ class ClosedCaptionManifest {
|
||||||
: tracks = UnmodifiableListView(tracks);
|
: tracks = UnmodifiableListView(tracks);
|
||||||
|
|
||||||
/// Gets all the closed caption tracks in the specified language and format.
|
/// 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.
|
/// Returns an empty list of no track is found.
|
||||||
List<ClosedCaptionTrackInfo> getByLanguage(String language,
|
List<ClosedCaptionTrackInfo> getByLanguage(String language,
|
||||||
{ClosedCaptionFormat format}) {
|
{ClosedCaptionFormat format, bool autoGenerated = false}) {
|
||||||
language = language.toLowerCase();
|
language = language.toLowerCase();
|
||||||
return tracks
|
return tracks
|
||||||
.where((e) =>
|
.where((e) =>
|
||||||
(e.language.code.toLowerCase() == language ||
|
(e.language.code.toLowerCase() == language ||
|
||||||
e.language.name.toLowerCase() == language) &&
|
e.language.name.toLowerCase() == language) &&
|
||||||
(format == null || e.format == format))
|
(format == null || e.format == format) &&
|
||||||
|
(!autoGenerated || e.isAutoGenerated))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
@ -31,7 +33,7 @@ class ClosedCaptionTrackInfo extends Equatable {
|
||||||
/// Keeping the same format.
|
/// Keeping the same format.
|
||||||
ClosedCaptionTrackInfo autoTranslate(String lang) {
|
ClosedCaptionTrackInfo autoTranslate(String lang) {
|
||||||
return ClosedCaptionTrackInfo(
|
return ClosedCaptionTrackInfo(
|
||||||
url.replaceQueryParameters({'tlang': lang}), Language(lang, ''),
|
url.replaceQueryParameters({'tlang': lang}), Language(lang, lang),
|
||||||
isAutoGenerated: isAutoGenerated, format: format);
|
isAutoGenerated: isAutoGenerated, format: format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
name: youtube_explode_dart
|
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.
|
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
|
homepage: https://github.com/Hexer10/youtube_explode_dart
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
|
|
@ -22,10 +22,17 @@ void main() {
|
||||||
|
|
||||||
expect(track.captions, isNotEmpty);
|
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 {
|
test('Get closed caption track at a specific time', () async {
|
||||||
var manifest = await yt.videos.closedCaptions
|
var manifest = await yt.videos.closedCaptions.getManifest('qfJthDvcZ08');
|
||||||
.getManifest('WOxr2dmLHLo', autoGenerated: false);
|
var trackInfo = manifest.getByLanguage('en', autoGenerated: false);
|
||||||
var trackInfo = manifest.getByLanguage('en');
|
|
||||||
var track = await yt.videos.closedCaptions.get(trackInfo.first);
|
var track = await yt.videos.closedCaptions.get(trackInfo.first);
|
||||||
var caption =
|
var caption =
|
||||||
track.getByTime(const Duration(hours: 0, minutes: 1, seconds: 48));
|
track.getByTime(const Duration(hours: 0, minutes: 1, seconds: 48));
|
||||||
|
@ -33,13 +40,12 @@ void main() {
|
||||||
expect(caption, isNotNull);
|
expect(caption, isNotNull);
|
||||||
expect(caption.parts, isEmpty);
|
expect(caption.parts, isEmpty);
|
||||||
expect(caption.text,
|
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 {
|
test('Get auto-generated closed caption track at a specific time', () async {
|
||||||
var manifest = await yt.videos.closedCaptions
|
var manifest = await yt.videos.closedCaptions.getManifest('ppJy5uGZLi4');
|
||||||
.getManifest('ppJy5uGZLi4', autoGenerated: true);
|
var trackInfo = manifest.getByLanguage('en', autoGenerated: true);
|
||||||
var trackInfo = manifest.getByLanguage('en');
|
|
||||||
var track = await yt.videos.closedCaptions.get(trackInfo.first);
|
var track = await yt.videos.closedCaptions.get(trackInfo.first);
|
||||||
var caption =
|
var caption =
|
||||||
track.getByTime(const Duration(hours: 0, minutes: 13, seconds: 22));
|
track.getByTime(const Duration(hours: 0, minutes: 13, seconds: 22));
|
||||||
|
|
Loading…
Reference in New Issue