Implement Caption API
This commit is contained in:
parent
0aec322140
commit
f15a8b3c23
|
@ -0,0 +1,77 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:xml/xml.dart' as xml;
|
||||
|
||||
import '../models/models.dart';
|
||||
import '../youtube_explode_base.dart';
|
||||
import 'helpers_extension.dart';
|
||||
|
||||
/// Caption extension for [YoutubeExplode]
|
||||
extension CaptionExtension on YoutubeExplode {
|
||||
/// Gets all available closed caption track infos for given video.
|
||||
Future<List<ClosedCaptionTrackInfo>> getVideoClosedCaptionTrackInfos(
|
||||
String videoId) async {
|
||||
if (!YoutubeExplode.validateVideoId(videoId)) {
|
||||
throw ArgumentError.value(videoId, 'videoId', 'Invalid video id');
|
||||
}
|
||||
|
||||
var videoInfoDic = await getVideoInfoDictionary(videoId);
|
||||
|
||||
var playerResponseJson = json.decode(videoInfoDic['player_response']);
|
||||
|
||||
var playAbility = playerResponseJson['playabilityStatus'];
|
||||
|
||||
if (playAbility['status'].toLowerCase() == 'error') {
|
||||
throw Exception('Video [$videoId] is unavailable');
|
||||
}
|
||||
|
||||
var trackInfos = <ClosedCaptionTrackInfo>[];
|
||||
for (var trackJson in playerResponseJson['captions']
|
||||
['playerCaptionsTracklistRenderer']['captionTracks']) {
|
||||
var url = Uri.parse(trackJson['baseUrl']);
|
||||
|
||||
var query = Map<String, String>.from(url.queryParameters);
|
||||
query['format'] = '3';
|
||||
|
||||
url = url.replace(queryParameters: query);
|
||||
|
||||
var languageCode = trackJson['languageCode'];
|
||||
var languageName = trackJson['name']['simpleText'];
|
||||
var language = Language(languageCode, languageName);
|
||||
|
||||
var isAutoGenerated = trackJson['vssId'].toLowerCase().startsWith('a.');
|
||||
|
||||
trackInfos.add(ClosedCaptionTrackInfo(url, language, isAutoGenerated));
|
||||
}
|
||||
return trackInfos;
|
||||
}
|
||||
|
||||
Future<xml.XmlDocument> _getClosedCaptionTrackXml(Uri url) async {
|
||||
var raw = (await client.get(url)).body;
|
||||
|
||||
return xml.parse(raw);
|
||||
}
|
||||
|
||||
/// Gets the closed caption track associated with given metadata.
|
||||
Future<ClosedCaptionTrack> getClosedCaptionTrack(
|
||||
ClosedCaptionTrackInfo info) async {
|
||||
var trackXml = await _getClosedCaptionTrackXml(info.url);
|
||||
|
||||
var captions = <ClosedCaption>[];
|
||||
for (var captionXml in trackXml.findAllElements('p')) {
|
||||
var text = captionXml.text;
|
||||
if (text.isNullOrWhiteSpace) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var offset =
|
||||
Duration(milliseconds: int.parse(captionXml.getAttribute('t')));
|
||||
var duration = Duration(
|
||||
milliseconds: int.parse(captionXml.getAttribute('d') ?? '-1'));
|
||||
|
||||
captions.add(ClosedCaption(text, offset, duration));
|
||||
}
|
||||
|
||||
return ClosedCaptionTrack(info, captions);
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ import '../youtube_explode_base.dart';
|
|||
import 'helpers_extension.dart';
|
||||
import 'playlist_extension.dart';
|
||||
|
||||
/// Channel extension for YoutubeExplode
|
||||
/// Channel extension for [YoutubeExplode]
|
||||
extension ChannelExtension on YoutubeExplode {
|
||||
static final _usernameRegMatchExp =
|
||||
RegExp(r'youtube\..+?/user/(.*?)(?:\?|&|/|$)');
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export 'caption_extension.dart';
|
||||
export 'channel_extension.dart';
|
||||
export 'helpers_extension.dart';
|
||||
export 'playlist_extension.dart';
|
||||
|
|
|
@ -5,7 +5,7 @@ import '../parser.dart' as parser;
|
|||
import '../youtube_explode_base.dart';
|
||||
import 'helpers_extension.dart';
|
||||
|
||||
/// Playlist extension for YoutubeExplode
|
||||
/// Playlist extension for [YoutubeExplode]
|
||||
extension PlaylistExtension on YoutubeExplode {
|
||||
static final _regMatchExp =
|
||||
RegExp(r'youtube\..+?/playlist.*?list=(.*?)(?:&|/|$)');
|
||||
|
|
|
@ -4,7 +4,7 @@ import '../models/models.dart';
|
|||
import '../youtube_explode_base.dart';
|
||||
import 'helpers_extension.dart';
|
||||
|
||||
/// Search extension for YoutubeExplode
|
||||
/// Search extension for [YoutubeExplode]
|
||||
extension SearchExtension on YoutubeExplode {
|
||||
Future<Map<String, dynamic>> _getSearchResults(String query, int page) async {
|
||||
var url =
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/// Text that gets displayed at specific time during video playback, as part of a <see cref="ClosedCaptionTrack"/>.
|
||||
class ClosedCaption {
|
||||
/// Text displayed by this caption.
|
||||
final String text;
|
||||
|
||||
/// Time at which this caption starts being displayed.
|
||||
final Duration offset;
|
||||
|
||||
/// Duration this caption is displayed.
|
||||
/// Negative if not found.
|
||||
final Duration duration;
|
||||
|
||||
/// Initializes an instance of [ClosedCaption]
|
||||
const ClosedCaption(this.text, this.offset, this.duration);
|
||||
|
||||
/// Time at which this caption starts being displayed.
|
||||
Duration get start => offset;
|
||||
|
||||
/// Time at which this caption ends being displayed.
|
||||
Duration get end => duration + offset;
|
||||
|
||||
@override
|
||||
String toString() => 'Caption: $text ($offset - $end)';
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import 'models.dart';
|
||||
|
||||
/// Set of captions that get displayed during video playback.
|
||||
class ClosedCaptionTrack {
|
||||
/// Metadata associated with this track.
|
||||
final ClosedCaptionTrackInfo info;
|
||||
|
||||
/// Collection of closed captions that belong to this track.
|
||||
final List<ClosedCaption> captions;
|
||||
|
||||
/// Initializes an instance of [ClosedCaptionTrack]
|
||||
const ClosedCaptionTrack(this.info, this.captions);
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import 'models.dart';
|
||||
|
||||
/// Metadata associated with a certain [ClosedCaptionTrack]
|
||||
class ClosedCaptionTrackInfo {
|
||||
|
||||
/// Manifest URL of the associated track.
|
||||
final Uri url;
|
||||
|
||||
/// Language of the associated track.
|
||||
final Language language;
|
||||
|
||||
/// Whether the associated track was automatically generated.
|
||||
final bool isAutoGenerated;
|
||||
|
||||
/// Initializes an instance of [ClosedCaptionTrackInfo]
|
||||
const ClosedCaptionTrackInfo(this.url, this.language, this.isAutoGenerated);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
/// Language information.
|
||||
class Language {
|
||||
/// ISO 639-1 code of this language.
|
||||
final String code;
|
||||
|
||||
/// Full English name of this language.
|
||||
final String name;
|
||||
|
||||
/// Initializes an instance of [Language]
|
||||
const Language(this.code, this.name);
|
||||
}
|
|
@ -3,7 +3,11 @@ library youtube_explode.models;
|
|||
export 'audio_encoding.dart';
|
||||
export 'audio_stream_info.dart';
|
||||
export 'channel.dart';
|
||||
export 'closed_caption.dart';
|
||||
export 'closed_caption_track.dart';
|
||||
export 'closed_caption_track_info.dart';
|
||||
export 'container.dart';
|
||||
export 'language.dart';
|
||||
export 'media_stream_info.dart';
|
||||
export 'media_stream_info_set.dart';
|
||||
export 'muxed_stream_info.dart';
|
||||
|
|
|
@ -267,7 +267,7 @@ class YoutubeExplode {
|
|||
|
||||
/// Returns the video info dictionary for a given vide.
|
||||
Future<Map<String, String>> getVideoInfoDictionary(String videoId) async {
|
||||
var eurl = Uri.encodeFull('https://youtube.googleapis.com/v/$videoId');
|
||||
var eurl = Uri.encodeComponent('https://youtube.googleapis.com/v/$videoId');
|
||||
var url = 'https://youtube.com/get_video_info?video_id=$videoId'
|
||||
'&el=embedded&eurl=$eurl&hl=en';
|
||||
var raw = (await client.get(url)).body;
|
||||
|
|
|
@ -4,12 +4,13 @@ version: 0.0.5
|
|||
homepage: https://github.com/Hexer10/youtube_explode_dart
|
||||
|
||||
environment:
|
||||
sdk: '>=2.7.0 <3.0.0'
|
||||
sdk: '>=2.6.0 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
html: ^0.14.0+3
|
||||
http: ^0.12.0+4
|
||||
http_parser: ^3.1.3
|
||||
xml: ^3.7.0
|
||||
|
||||
dev_dependencies:
|
||||
effective_dart: ^1.2.1
|
||||
|
|
Loading…
Reference in New Issue