Implement Caption API

This commit is contained in:
Hexah 2020-02-23 20:29:08 +01:00
parent 0aec322140
commit f15a8b3c23
12 changed files with 153 additions and 5 deletions

View File

@ -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);
}
}

View File

@ -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/(.*?)(?:\?|&|/|$)');

View File

@ -1,3 +1,4 @@
export 'caption_extension.dart';
export 'channel_extension.dart';
export 'helpers_extension.dart';
export 'playlist_extension.dart';

View File

@ -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=(.*?)(?:&|/|$)');

View File

@ -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 =

View File

@ -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)';
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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';

View File

@ -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;

View File

@ -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