youtube_explode/lib/src/reverse_engineering/responses/video_info_response.dart

149 lines
3.9 KiB
Dart

import 'package:http_parser/http_parser.dart';
import '../../exceptions/exceptions.dart';
import '../../retry.dart';
import '../youtube_http_client.dart';
import 'player_response.dart';
import 'stream_info_provider.dart';
///
class VideoInfoResponse {
final Map<String, String> _root;
String _status;
bool _isVideoAvailable;
PlayerResponse _playerResponse;
Iterable<_StreamInfo> _muxedStreams;
Iterable<_StreamInfo> _adaptiveStreams;
Iterable<_StreamInfo> _streams;
///
String get status => _status ??= _root['status'];
///
bool get isVideoAvailable =>
_isVideoAvailable ??= status.toLowerCase() != 'fail';
///
PlayerResponse get playerResponse =>
_playerResponse ??= PlayerResponse.parse(_root['player_response']);
///
Iterable<_StreamInfo> get muxedStreams =>
_muxedStreams ??= _root['url_encoded_fmt_stream_map']
?.split(',')
?.map(Uri.splitQueryString)
?.map((e) => _StreamInfo(e)) ??
const [];
///
Iterable<_StreamInfo> get adaptiveStreams =>
_adaptiveStreams ??= _root['adaptive_fmts']
?.split(',')
?.map(Uri.splitQueryString)
?.map((e) => _StreamInfo(e)) ??
const [];
///
Iterable<_StreamInfo> get streams =>
_streams ??= [...muxedStreams, ...adaptiveStreams];
///
VideoInfoResponse(this._root);
///
VideoInfoResponse.parse(String raw) : _root = Uri.splitQueryString(raw);
///
static Future<VideoInfoResponse> get(
YoutubeHttpClient httpClient, String videoId,
[String sts]) {
var eurl = Uri.encodeFull('https://youtube.googleapis.com/v/$videoId');
var url =
'https://youtube.com/get_video_info?video_id=$videoId&el=embedded&eurl=$eurl&hl=en${sts != null ? '&sts=$sts' : ''}';
return retry(() async {
var raw = await httpClient.getString(url);
var result = VideoInfoResponse.parse(raw);
if (!result.isVideoAvailable || !result.playerResponse.isVideoAvailable) {
throw VideoUnplayableException(videoId);
}
return result;
});
}
}
class _StreamInfo extends StreamInfoProvider {
final Map<String, String> _root;
int _tag;
String _url;
String _signature;
String _signatureParameter;
int _contentLength;
int _bitrate;
MediaType _mimeType;
String _container;
List<String> _codecs;
String _audioCodec;
String _videoCodec;
bool _isAudioOnly;
String _videoQualityLabel;
List<int> __size;
int _videoWidth;
int _videoHeight;
int _framerate;
@override
int get tag => _tag ??= int.parse(_root['itag']);
@override
String get url => _url ??= _root['url'];
@override
String get signature => _signature ??= _root['s'];
@override
String get signatureParameter => _signatureParameter ??= _root['sp'];
@override
int get contentLength => _contentLength ??= int.tryParse(_root['clen'] ??
StreamInfoProvider.contentLenExp.firstMatch(url).group(1));
@override
int get bitrate => _bitrate ??= int.parse(_root['bitrate']);
MediaType get mimeType => _mimeType ??= MediaType.parse(_root['type']);
@override
String get container => _container ??= mimeType.subtype;
List<String> get codecs =>
_codecs ??= mimeType.parameters['codecs'].split(',').map((e) => e.trim());
@override
String get audioCodec => _audioCodec ??= codecs.last;
@override
String get videoCodec => _videoCodec ??= isAudioOnly ? null : codecs.first;
bool get isAudioOnly => _isAudioOnly ??= mimeType.type == 'audio';
@override
String get videoQualityLabel => _videoQualityLabel ??= _root['quality_label'];
List<int> get _size =>
__size ??= _root['size'].split(',').map((e) => int.tryParse(e ?? ''));
@override
int get videoWidth => _videoWidth ??= _size.first;
@override
int get videoHeight => _videoHeight ??= _size.last;
@override
int get framerate => _framerate ??= int.tryParse(_root['fps'] ?? '');
_StreamInfo(this._root);
}