[dl] downloader playlist
This commit is contained in:
parent
c25256b13b
commit
ddbf9a6aae
Binary file not shown.
|
|
@ -6,3 +6,29 @@ const colorMainGrey = Color(0xFF333333);
|
||||||
const colorMainRed = Color.fromRGBO(204, 0, 0, 1);
|
const colorMainRed = Color.fromRGBO(204, 0, 0, 1);
|
||||||
const colorDarkRed = Color.fromRGBO(153, 0, 0, 1);
|
const colorDarkRed = Color.fromRGBO(153, 0, 0, 1);
|
||||||
const colorMainBlue = Color(0xFF1f74ad);
|
const colorMainBlue = Color(0xFF1f74ad);
|
||||||
|
|
||||||
|
enum Format {
|
||||||
|
mp3(format: 'MP3'),
|
||||||
|
mp3HD(format: 'MP3 HD'),
|
||||||
|
mp4(format: 'MP4'),
|
||||||
|
mp4HD(
|
||||||
|
ytCmd:
|
||||||
|
"bestvideo[height=1080][ext=mp4][vcodec~='^(avc|h264)']+bestaudio[ext=m4a]/bestvideo[height<=1080][ext=mp4]+bestaudio[ext=m4a]/bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best",
|
||||||
|
format: 'MP4 HD'),
|
||||||
|
mp42K(
|
||||||
|
ytCmd:
|
||||||
|
"bestvideo[height=1440][ext=mp4]+bestaudio[ext=m4a]/bestvideo[height<=1080][ext=mp4][vcodec~='^(avc|h264)']+bestaudio[ext=m4a]/bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best",
|
||||||
|
format: 'MP4 2K'),
|
||||||
|
tGP(format: '3GP'),
|
||||||
|
flv(format: 'FLV'),
|
||||||
|
m4a;
|
||||||
|
|
||||||
|
final String ytCmd;
|
||||||
|
final String format;
|
||||||
|
|
||||||
|
const Format({
|
||||||
|
this.ytCmd =
|
||||||
|
'"18/22/bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best"',
|
||||||
|
this.format = 'MP4',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:notube/models/Video.dart';
|
||||||
import 'package:notube/services/download.dart';
|
import 'package:notube/services/download.dart';
|
||||||
|
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
@ -24,8 +27,41 @@ class DlFormCubit extends Cubit<DlFormState> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void download() async {
|
void parseUrl() async {
|
||||||
dlService = await DLServices.init();
|
dlService = await DLServices.init();
|
||||||
await dlService.analyseUrl(state.url!);
|
var shellStream = await dlService.analyseUrl(state.url!);
|
||||||
|
|
||||||
|
shellStream.listen((line) {
|
||||||
|
if (line[0] == '{') {
|
||||||
|
var dataInfos = jsonDecode(line);
|
||||||
|
var extractor = dataInfos['extractor_key'];
|
||||||
|
var isPlaylist = dataInfos['_type'] == 'playlist';
|
||||||
|
var videos = <Video>[];
|
||||||
|
if (isPlaylist) {
|
||||||
|
var playlistTitle = dataInfos['title'];
|
||||||
|
for (var videoTmp in dataInfos['entries']) {
|
||||||
|
var video = Video.fromJson(videoTmp);
|
||||||
|
video.format = state.format;
|
||||||
|
videos.add(video);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var video = Video.fromJson(dataInfos);
|
||||||
|
video.format = state.format;
|
||||||
|
videos.add(video);
|
||||||
|
}
|
||||||
|
emit(state.copyWith(
|
||||||
|
isParsed: true,
|
||||||
|
videos: videos,
|
||||||
|
extractor: extractor,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearVideos() {
|
||||||
|
emit(state.copyWith(
|
||||||
|
videos: [],
|
||||||
|
isParsed: false,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,23 +4,35 @@ class DlFormState extends Equatable {
|
||||||
const DlFormState({
|
const DlFormState({
|
||||||
this.url = '',
|
this.url = '',
|
||||||
this.format = 'MP3',
|
this.format = 'MP3',
|
||||||
|
this.videos = const [],
|
||||||
|
this.isParsed = false,
|
||||||
|
this.extractor = '',
|
||||||
});
|
});
|
||||||
|
|
||||||
final String url;
|
final String url;
|
||||||
final String format;
|
final String format;
|
||||||
|
final bool isParsed;
|
||||||
|
final List<Video> videos;
|
||||||
|
final String extractor;
|
||||||
|
|
||||||
DlFormState copyWith({
|
DlFormState copyWith({
|
||||||
String? url,
|
String? url,
|
||||||
String? format,
|
String? format,
|
||||||
|
List<Video>? videos,
|
||||||
|
bool? isParsed,
|
||||||
|
String? extractor,
|
||||||
}) {
|
}) {
|
||||||
return DlFormState(
|
return DlFormState(
|
||||||
url: url ?? this.url,
|
url: url ?? this.url,
|
||||||
format: format ?? this.format,
|
format: format ?? this.format,
|
||||||
|
videos: videos ?? this.videos,
|
||||||
|
isParsed: isParsed ?? this.isParsed,
|
||||||
|
extractor: extractor ?? this.extractor,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [url, format];
|
List<Object> get props => [url, format, isParsed, videos];
|
||||||
}
|
}
|
||||||
|
|
||||||
final class DlFormInitial extends DlFormState {
|
final class DlFormInitial extends DlFormState {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:notube/constants.dart';
|
import 'package:notube/constants.dart';
|
||||||
|
import 'package:notube/models/Video.dart';
|
||||||
|
import 'package:notube/videoList/cubit/videos_cubit.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:notube/dlForm/cubit/dl_form_cubit.dart';
|
import 'package:notube/dlForm/cubit/dl_form_cubit.dart';
|
||||||
|
|
||||||
|
|
@ -14,15 +17,28 @@ class SubmitButton extends StatefulWidget {
|
||||||
class _SubmitButtonState extends State<SubmitButton> {
|
class _SubmitButtonState extends State<SubmitButton> {
|
||||||
bool isHovering = false;
|
bool isHovering = false;
|
||||||
|
|
||||||
|
void addVideosListener(BuildContext context, DlFormState state) {
|
||||||
|
if (state.isParsed) {
|
||||||
|
for (Video video in state.videos) {
|
||||||
|
debugPrint('Adding video: $video');
|
||||||
|
context.read<VideosCubit>().addVideo(video);
|
||||||
|
}
|
||||||
|
context.read<DlFormCubit>().clearVideos();
|
||||||
|
context.read<VideosCubit>().startDownloader();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ConstrainedBox(
|
return BlocListener<DlFormCubit, DlFormState>(
|
||||||
|
listener: addVideosListener,
|
||||||
|
child: ConstrainedBox(
|
||||||
constraints: BoxConstraints.tightFor(width: 200),
|
constraints: BoxConstraints.tightFor(width: 200),
|
||||||
child: MouseRegion(
|
child: MouseRegion(
|
||||||
cursor: SystemMouseCursors.click,
|
cursor: SystemMouseCursors.click,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.read<DlFormCubit>().download();
|
context.read<DlFormCubit>().parseUrl();
|
||||||
},
|
},
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onHover: (hovering) {
|
onHover: (hovering) {
|
||||||
|
|
@ -37,6 +53,6 @@ class _SubmitButtonState extends State<SubmitButton> {
|
||||||
child: const Text('Ok', textAlign: TextAlign.center).tr(),
|
child: const Text('Ok', textAlign: TextAlign.center).tr(),
|
||||||
),
|
),
|
||||||
))),
|
))),
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,26 +2,41 @@ import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
class Video extends Equatable {
|
class Video extends Equatable {
|
||||||
Video({
|
Video({
|
||||||
required this.id,
|
this.id = '',
|
||||||
required this.title,
|
this.url = '',
|
||||||
|
this.extractor = '',
|
||||||
|
this.isParsed = false,
|
||||||
|
this.status = 'pending',
|
||||||
|
this.format = 'MP4 HD',
|
||||||
|
this.title = '',
|
||||||
this.thumbnail = '',
|
this.thumbnail = '',
|
||||||
this.description = '',
|
this.description = '',
|
||||||
this.duration = '0:00',
|
this.duration = '0:00',
|
||||||
required this.uploader,
|
this.uploader = '',
|
||||||
required this.uploadDate,
|
this.uploadDate = '',
|
||||||
|
this.filename = '',
|
||||||
});
|
});
|
||||||
|
|
||||||
String id;
|
String id;
|
||||||
|
String url;
|
||||||
|
String extractor;
|
||||||
|
bool isParsed;
|
||||||
|
String status;
|
||||||
|
String format;
|
||||||
String title;
|
String title;
|
||||||
String thumbnail;
|
String thumbnail;
|
||||||
String description;
|
String description;
|
||||||
String duration;
|
String duration;
|
||||||
String uploader;
|
String uploader;
|
||||||
String uploadDate;
|
String uploadDate;
|
||||||
|
String filename;
|
||||||
|
|
||||||
factory Video.fromJson(Map<String, dynamic> json) {
|
factory Video.fromJson(Map<String, dynamic> json) {
|
||||||
return Video(
|
return Video(
|
||||||
id: json['id'],
|
id: json['id'],
|
||||||
|
isParsed: true,
|
||||||
|
url: json['url'] ?? json['original_url'],
|
||||||
|
extractor: json['extractor_key'] ?? '',
|
||||||
title: json['title'],
|
title: json['title'],
|
||||||
thumbnail: json['thumbnails']![0]['url'],
|
thumbnail: json['thumbnails']![0]['url'],
|
||||||
description: json['description'] ?? '',
|
description: json['description'] ?? '',
|
||||||
|
|
@ -31,6 +46,18 @@ class Video extends Equatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props =>
|
List<Object> get props => [
|
||||||
[id, title, thumbnail, description, duration, uploader, uploadDate];
|
id,
|
||||||
|
title,
|
||||||
|
thumbnail,
|
||||||
|
description,
|
||||||
|
duration,
|
||||||
|
uploader,
|
||||||
|
uploadDate,
|
||||||
|
format,
|
||||||
|
status,
|
||||||
|
url,
|
||||||
|
isParsed,
|
||||||
|
filename,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,12 @@ class Home extends StatelessWidget {
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('home').tr(),
|
title: Text('home').tr(),
|
||||||
),
|
),
|
||||||
body: Center(
|
body: LayoutBuilder(builder:
|
||||||
|
(BuildContext context, BoxConstraints viewportConstraints) {
|
||||||
|
return viewportConstraints.maxHeight < double.infinity
|
||||||
|
? Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints.expand(),
|
constraints: viewportConstraints,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
image: DecorationImage(
|
image: DecorationImage(
|
||||||
image: AssetImage("assets/images/wallpaper.webp"),
|
image: AssetImage("assets/images/wallpaper.webp"),
|
||||||
|
|
@ -32,20 +35,39 @@ class Home extends StatelessWidget {
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20),
|
padding: EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: DlForm()),
|
child: DlForm()),
|
||||||
BlocConsumer<VideosCubit, VideosState>(
|
Flexible(
|
||||||
|
child: BlocConsumer<VideosCubit, VideosState>(
|
||||||
listener: (context, state) {},
|
listener: (context, state) {},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Column(
|
return ListView.builder(
|
||||||
children: <Widget>[
|
itemCount: state.videoList.length,
|
||||||
for (Video video
|
itemBuilder: (context, index) {
|
||||||
in context.read<VideosCubit>().videoList)
|
var currentVideo = state.videoList[index];
|
||||||
Text(video.title),
|
return ListTile(
|
||||||
],
|
leading: SizedBox(
|
||||||
|
width: 72,
|
||||||
|
height: 48,
|
||||||
|
child: Image.network(
|
||||||
|
currentVideo.thumbnail,
|
||||||
|
fit: BoxFit.cover),
|
||||||
|
),
|
||||||
|
title: Text(currentVideo.title),
|
||||||
|
subtitle: Text(currentVideo.status));
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}),
|
})),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
));
|
)
|
||||||
|
: CircularProgressIndicator();
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
SingleChildScrollView(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minHeight: viewportConstraints.maxHeight),
|
||||||
|
child:
|
||||||
|
*/
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:process_run/process_run.dart';
|
||||||
|
|
||||||
|
class ConverterService {
|
||||||
|
late Directory tempDir;
|
||||||
|
late String assetName;
|
||||||
|
late String ffmpegPath;
|
||||||
|
|
||||||
|
ConverterService._();
|
||||||
|
|
||||||
|
static Future<ConverterService> init() async {
|
||||||
|
debugPrint('Initializing DLServices');
|
||||||
|
var dlService = ConverterService._();
|
||||||
|
await dlService._init();
|
||||||
|
return dlService;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _init() async {
|
||||||
|
tempDir = await getTemporaryDirectory();
|
||||||
|
assetName = 'ffmpeg.exe';
|
||||||
|
await copyExecutable();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> copyExecutable() async {
|
||||||
|
File tempFile = File('${tempDir.path}/$assetName');
|
||||||
|
|
||||||
|
ByteData data = await rootBundle.load('assets/executable/$assetName');
|
||||||
|
await tempFile.exists().then((value) {
|
||||||
|
if (!value) {
|
||||||
|
debugPrint('Copying $assetName to ${tempFile.path}');
|
||||||
|
tempFile.writeAsBytes(
|
||||||
|
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ffmpegPath = tempFile.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Stream> convertFile(String inputPath, String outputPath) async {
|
||||||
|
debugPrint('Converting $inputPath to $outputPath');
|
||||||
|
|
||||||
|
var command = ' -q --flat-playlist -J';
|
||||||
|
|
||||||
|
var shellLinesController = ShellLinesController();
|
||||||
|
var shell = Shell(stdout: shellLinesController.sink);
|
||||||
|
await shell.run('''
|
||||||
|
$ffmpegPath $command
|
||||||
|
''').then((result) {
|
||||||
|
debugPrint('Analyse result: ${result}');
|
||||||
|
});
|
||||||
|
|
||||||
|
return shellLinesController.stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:notube/constants.dart';
|
||||||
import 'package:notube/models/Video.dart';
|
import 'package:notube/models/Video.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:notube/videoList/cubit/videos_cubit.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:process_run/shell.dart';
|
import 'package:process_run/shell.dart';
|
||||||
|
|
||||||
|
|
@ -56,34 +56,33 @@ class DLServices {
|
||||||
ytDlpPath = tempFile.path;
|
ytDlpPath = tempFile.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> downloadFile(String url, String format) async {
|
Future<Stream> downloadFile(Video video) async {
|
||||||
debugPrint('Downloading $url in $format format');
|
debugPrint('Downloading $url in $video.format format');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
https://youtu.be/playlist?list=PLk1fi9OrZmvGBdh9BdWIhZImGVDUqls1X
|
https://youtu.be/playlist?list=PLk1fi9OrZmvGBdh9BdWIhZImGVDUqls1X
|
||||||
|
https://youtube.com/watch?v=8bJBX-rrrn4
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// get the format code from the Format enum
|
||||||
|
var formatCmd =
|
||||||
|
Format.values.firstWhere((e) => e.format == video.format).ytCmd;
|
||||||
|
debugPrint('Format code: $formatCmd');
|
||||||
|
|
||||||
|
var cleanTitle = video.title.replaceAll(RegExp(r'[^\w\s]+'), '');
|
||||||
var command =
|
var command =
|
||||||
'${url.trim()} --sub-langs "all,-live_chat" --embed-subs --embed-thumbnail --embed-metadata --windows-filenames --progress -o "temp/%(playlist)s/%(title)s"';
|
'${video.url.trim()} --sub-langs "all,-live_chat" --embed-subs --embed-thumbnail --embed-metadata --progress -o "temp/%(playlist)s/${cleanTitle}_${video.format}_tmp.%(ext)s" -f "$formatCmd"';
|
||||||
|
|
||||||
var shellLinesController = ShellLinesController();
|
var shellLinesController = ShellLinesController();
|
||||||
var shell = Shell(stdout: shellLinesController.sink);
|
var shell = Shell(stdout: shellLinesController.sink, verbose: false);
|
||||||
|
|
||||||
shellLinesController.stream.listen((line) {
|
|
||||||
if (line.contains('[download]')) {
|
|
||||||
debugPrint('___Download___');
|
|
||||||
debugPrint('%=${line.split('[download]')[1].split(' of')[0].trim()}');
|
|
||||||
debugPrint('______');
|
|
||||||
}
|
|
||||||
debugPrint(line);
|
|
||||||
});
|
|
||||||
|
|
||||||
debugPrint('Running $ytDlpPath $command');
|
debugPrint('Running $ytDlpPath $command');
|
||||||
await shell.run('''
|
shell.run('$ytDlpPath $command');
|
||||||
$ytDlpPath $command
|
|
||||||
''');
|
return shellLinesController.stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> analyseUrl(String url) async {
|
Future analyseUrl(String url) async {
|
||||||
debugPrint('Analyse $url');
|
debugPrint('Analyse $url');
|
||||||
|
|
||||||
var command = '${url.trim()} -q --flat-playlist -J';
|
var command = '${url.trim()} -q --flat-playlist -J';
|
||||||
|
|
@ -91,24 +90,12 @@ class DLServices {
|
||||||
var shellLinesController = ShellLinesController();
|
var shellLinesController = ShellLinesController();
|
||||||
var shell = Shell(stdout: shellLinesController.sink);
|
var shell = Shell(stdout: shellLinesController.sink);
|
||||||
|
|
||||||
shellLinesController.stream.listen((line) {
|
|
||||||
if (line[0] == '{') {
|
|
||||||
var dataInfos = jsonDecode(line);
|
|
||||||
extractor = dataInfos['extractor_key'];
|
|
||||||
isPlaylist = dataInfos['_type'] == 'playlist';
|
|
||||||
if (isPlaylist) {
|
|
||||||
playlistTitle = dataInfos['title'];
|
|
||||||
for (var video in dataInfos['entries']) {
|
|
||||||
videos.add(Video.fromJson(video));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
videos.add(Video.fromJson(dataInfos));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await shell.run('''
|
await shell.run('''
|
||||||
$ytDlpPath $command
|
$ytDlpPath $command
|
||||||
''');
|
''').then((result) {
|
||||||
|
debugPrint('Analyse result: $result');
|
||||||
|
});
|
||||||
|
|
||||||
|
return shellLinesController.stream;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,27 @@
|
||||||
|
import 'dart:math';
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:notube/models/Video.dart';
|
import 'package:notube/models/Video.dart';
|
||||||
|
import 'package:notube/services/converter.dart';
|
||||||
|
import 'package:notube/services/download.dart';
|
||||||
|
|
||||||
part 'videos_state.dart';
|
part 'videos_state.dart';
|
||||||
|
|
||||||
class VideosCubit extends Cubit<VideosState> {
|
class VideosCubit extends Cubit<VideosState> {
|
||||||
VideosCubit() : super(VideosState());
|
VideosCubit() : super(VideosState());
|
||||||
|
|
||||||
|
late DLServices dlService;
|
||||||
|
late ConverterService converterService;
|
||||||
|
|
||||||
final List<Video> _videoList = [];
|
final List<Video> _videoList = [];
|
||||||
List<Video> get videoList => _videoList;
|
List<Video> get videoList => _videoList;
|
||||||
|
Iterable<Video> downloadingVid = [];
|
||||||
|
|
||||||
|
int runningTasks = 0;
|
||||||
|
int maxConcurrentTasks = 3;
|
||||||
|
|
||||||
void addVideo(Video video) {
|
void addVideo(Video video) {
|
||||||
_videoList.add(video);
|
_videoList.add(video);
|
||||||
|
|
@ -24,4 +37,85 @@ class VideosCubit extends Cubit<VideosState> {
|
||||||
_videoList.remove(video);
|
_videoList.remove(video);
|
||||||
emit(VideosState(videoList: _videoList));
|
emit(VideosState(videoList: _videoList));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> startDownloader() async {
|
||||||
|
dlService = await DLServices.init();
|
||||||
|
downloadingVid =
|
||||||
|
_videoList.where((video) => video.status != 'downloaded').take(3);
|
||||||
|
|
||||||
|
debugPrint('Videos to download: ${downloadingVid.length}');
|
||||||
|
while (downloadingVid.isNotEmpty && runningTasks < maxConcurrentTasks) {
|
||||||
|
runningTasks++;
|
||||||
|
debugPrint('Concurrent workers: $runningTasks');
|
||||||
|
|
||||||
|
await downloadVideo(downloadingVid.first);
|
||||||
|
downloadingVid =
|
||||||
|
_videoList.where((video) => video.status != 'downloaded');
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
for (Video video in videosLeft) {
|
||||||
|
final shellStream = await dlService.downloadFile(video.url, video.format);
|
||||||
|
shellStream.listen((line) {
|
||||||
|
if (line.contains('[download]')) {
|
||||||
|
debugPrint('___Download___');
|
||||||
|
debugPrint('%=${line.split('[download]')[1].split(' of')[0].trim()}');
|
||||||
|
debugPrint('______');
|
||||||
|
}
|
||||||
|
debugPrint(line);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
Future convertVideo(Video video) async {
|
||||||
|
converterService = await ConverterService.init();
|
||||||
|
debugPrint('Converting ${video.title} to $video.format ');
|
||||||
|
final shellStream =
|
||||||
|
await converterService.convertFile(video.url, video.format);
|
||||||
|
shellStream.listen((line) {
|
||||||
|
debugPrint(line);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future downloadVideo(Video video) async {
|
||||||
|
debugPrint(
|
||||||
|
'Downloading ${video.title} in ${video.format} format ${video.url}');
|
||||||
|
final shellStream = await dlService.downloadFile(video);
|
||||||
|
var completer = Completer<void>();
|
||||||
|
|
||||||
|
shellStream.listen(
|
||||||
|
(line) {
|
||||||
|
if (line.contains('[download]') &&
|
||||||
|
line.contains('of') &&
|
||||||
|
line.contains('%')) {
|
||||||
|
var percentString =
|
||||||
|
line.split('[download]')[1].split(' of')[0].split('%')[0].trim();
|
||||||
|
var percent = percentString == null ? 0 : double.parse(percentString);
|
||||||
|
video.status = 'downloading $percent%';
|
||||||
|
emit(VideosState(videoList: _videoList));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.contains('[EmbedThumbnail]')) {
|
||||||
|
video.status = 'downloaded';
|
||||||
|
video.filename =
|
||||||
|
line.split('Adding thumbnail to "')[1].split('"')[0].trim() ?? '';
|
||||||
|
emit(VideosState(videoList: _videoList));
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
debugPrint('Download completed');
|
||||||
|
runningTasks--;
|
||||||
|
completer.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debugPrint(line);
|
||||||
|
},
|
||||||
|
onError: (error) {
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer
|
||||||
|
.completeError(error); // Complete with error if an error occurs
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
part of 'videos_cubit.dart';
|
part of 'videos_cubit.dart';
|
||||||
|
|
||||||
final class VideosState extends Equatable {
|
final class VideosState {
|
||||||
final List<Video> videoList;
|
final List<Video> videoList;
|
||||||
|
|
||||||
VideosState({this.videoList = const []});
|
VideosState({this.videoList = const []});
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [videoList];
|
List<Object> get props => [videoList];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
pubspec.lock
24
pubspec.lock
|
|
@ -241,18 +241,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
|
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.4"
|
version: "10.0.5"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_flutter_testing
|
name: leak_tracker_flutter_testing
|
||||||
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
|
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.5"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -281,18 +281,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.0"
|
version: "0.11.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.12.0"
|
version: "1.15.0"
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -566,10 +566,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
|
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.0"
|
version: "0.7.2"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -670,10 +670,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.2.1"
|
version: "14.2.4"
|
||||||
web:
|
web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue