[conv] ffmpeg full conversion

This commit is contained in:
jscampucci 2024-07-02 11:23:14 +02:00
parent 3eb4d7b905
commit f97b20da49
17 changed files with 320 additions and 58 deletions

0
-vcodec Normal file
View File

View File

@ -8,8 +8,14 @@ const colorDarkRed = Color.fromRGBO(153, 0, 0, 1);
const colorMainBlue = Color(0xFF1f74ad); const colorMainBlue = Color(0xFF1f74ad);
enum Format { enum Format {
mp3(format: 'MP3'), mp3(
mp3HD(format: 'MP3 HD'), format: 'MP3',
ffmpegCmd: '-f mp3 -loglevel quiet -ab 192k -vn',
extension: 'mp3'),
mp3HD(
format: 'MP3 HD',
ffmpegCmd: '-f mp3 -loglevel quiet -ab 320k -vn',
extension: 'mp3'),
mp4(format: 'MP4'), mp4(format: 'MP4'),
mp4HD( mp4HD(
ytCmd: ytCmd:
@ -19,16 +25,36 @@ enum Format {
ytCmd: 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", "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'), format: 'MP4 2K'),
tGP(format: '3GP'), tGP(
flv(format: 'FLV'), format: '3GP',
m4a; ffmpegCmd:
'-movflags frag_keyframe+empty_moov -r 20 -s 352x288 -vb 400k -acodec aac -strict experimental -ac 1 -ar 8000 -ab 24k -f 3gp',
extension: '3gp'),
flv(
format: 'FLV',
ffmpegCmd:
'-vcodec libx264 -preset slower -b 512k -bt 512k -threads 0 -s 640x360 -aspect 16:9 -acodec libmp3lame -ar 44100 -ab 32 -progress pipe:1',
extension: 'flv'),
m4a(format: 'M4A'),
;
final String ytCmd; final String ytCmd;
final String format; final String format;
final String ffmpegCmd;
final String extension;
const Format({ const Format({
this.ytCmd = this.ytCmd =
'"18/22/bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best"', '"18/22/bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best"',
this.format = 'MP4', this.format = 'MP4',
this.ffmpegCmd = '',
this.extension = '',
}); });
} }
const convertedFormats = [
Format.mp3,
Format.mp3HD,
Format.tGP,
Format.flv,
];

View File

@ -2,6 +2,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:flutter/material.dart';
import 'package:notube/constants.dart';
import 'package:notube/models/video.dart'; import 'package:notube/models/video.dart';
import 'package:notube/services/download.dart'; import 'package:notube/services/download.dart';
@ -12,7 +14,7 @@ class DlFormCubit extends Cubit<DlFormState> {
late DLServices dlService; late DLServices dlService;
void setFormat(String newFormat) { void setFormat(Format newFormat) {
emit(state.copyWith( emit(state.copyWith(
format: newFormat, format: newFormat,
)); ));
@ -39,17 +41,15 @@ class DlFormCubit extends Cubit<DlFormState> {
var playlistTitle = dataInfos['title']; var playlistTitle = dataInfos['title'];
for (var videoTmp in dataInfos['entries']) { for (var videoTmp in dataInfos['entries']) {
var video = Video.fromJson(videoTmp); var video = Video.fromJson(videoTmp);
video.copyWith( videos.add(video.copyWith(
format: state.format, format: state.format,
); filename: '${video.id}_${state.format.format}'));
videos.add(video);
} }
} else { } else {
var video = Video.fromJson(dataInfos); var video = Video.fromJson(dataInfos);
video.copyWith( videos.add(video.copyWith(
format: state.format, format: state.format,
); filename: '${video.id}_${state.format.format}'));
videos.add(video);
} }
emit(state.copyWith( emit(state.copyWith(
isParsed: true, isParsed: true,
@ -60,7 +60,7 @@ class DlFormCubit extends Cubit<DlFormState> {
}); });
} }
void clearVideos() { void clearForm() {
emit(state.copyWith( emit(state.copyWith(
videos: [], videos: [],
isParsed: false, isParsed: false,

View File

@ -3,21 +3,21 @@ part of 'dl_form_cubit.dart';
class DlFormState extends Equatable { class DlFormState extends Equatable {
const DlFormState({ const DlFormState({
this.url = '', this.url = '',
this.format = 'MP3', this.format = Format.mp4,
this.videos = const [], this.videos = const [],
this.isParsed = false, this.isParsed = false,
this.extractor = '', this.extractor = '',
}); });
final String url; final String url;
final String format; final Format format;
final bool isParsed; final bool isParsed;
final List<Video> videos; final List<Video> videos;
final String extractor; final String extractor;
DlFormState copyWith({ DlFormState copyWith({
String? url, String? url,
String? format, Format? format,
List<Video>? videos, List<Video>? videos,
bool? isParsed, bool? isParsed,
String? extractor, String? extractor,

View File

@ -70,7 +70,7 @@ class DebugDlFormState extends StatelessWidget {
return Column( return Column(
children: <Widget>[ children: <Widget>[
Text('Url: $url'), Text('Url: $url'),
Text('Format: $format'), Text('Format: ${format.format}'),
], ],
); );
}); });

View File

@ -18,12 +18,14 @@ class DropdownFormat extends StatelessWidget {
// dropdown below.. // dropdown below..
child: DropdownButton<String>( child: DropdownButton<String>(
value: dlFormState.format, value: dlFormState.format.format,
elevation: 16, elevation: 16,
style: const TextStyle(color: colorMainWhite), style: const TextStyle(color: colorMainWhite),
dropdownColor: colorMainBlue, dropdownColor: colorMainBlue,
onChanged: (String? value) { onChanged: (String? value) {
context.read<DlFormCubit>().setFormat(value!); var format =
Format.values.firstWhere((element) => element.format == value);
context.read<DlFormCubit>().setFormat(format!);
}, },
underline: Container(), underline: Container(),
items: Format.values.map<DropdownMenuItem<String>>((Format format) { items: Format.values.map<DropdownMenuItem<String>>((Format format) {

View File

@ -22,7 +22,7 @@ class _SubmitButtonState extends State<SubmitButton> {
debugPrint('Adding video: $video'); debugPrint('Adding video: $video');
context.read<VideosCubit>().addVideo(video); context.read<VideosCubit>().addVideo(video);
} }
context.read<DlFormCubit>().clearVideos(); context.read<DlFormCubit>().clearForm();
context.read<VideosCubit>().startDownloader(); context.read<VideosCubit>().startDownloader();
} }
} }

View File

@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:notube/constants.dart';
@immutable @immutable
class Video extends Equatable { class Video extends Equatable {
@ -9,7 +10,7 @@ class Video extends Equatable {
this.extractor = '', this.extractor = '',
this.isParsed = false, this.isParsed = false,
this.status = 'pending', this.status = 'pending',
this.format = 'MP4 HD', this.format = Format.mp4,
this.title = '', this.title = '',
this.thumbnail = '', this.thumbnail = '',
this.description = '', this.description = '',
@ -24,7 +25,7 @@ class Video extends Equatable {
final String extractor; final String extractor;
final bool isParsed; final bool isParsed;
final String status; final String status;
final String format; final Format format;
final String title; final String title;
final String thumbnail; final String thumbnail;
final String description; final String description;
@ -54,7 +55,7 @@ class Video extends Equatable {
String? extractor, String? extractor,
bool? isParsed, bool? isParsed,
String? status, String? status,
String? format, Format? format,
String? title, String? title,
String? thumbnail, String? thumbnail,
String? description, String? description,

View File

@ -51,7 +51,7 @@ class Home extends StatelessWidget {
), ),
title: Text(currentVideo.title), title: Text(currentVideo.title),
subtitle: Text( subtitle: Text(
'${currentVideo.status} - ${currentVideo.format}'), '${currentVideo.status} - ${currentVideo.format.format}'),
); );
}, },
); );

View File

@ -1,8 +1,11 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:process_run/process_run.dart'; import 'package:process_run/process_run.dart';
import 'package:notube/models/video.dart';
import 'package:path/path.dart' as p;
class ConverterService { class ConverterService {
late Directory tempDir; late Directory tempDir;
@ -39,18 +42,47 @@ class ConverterService {
ffmpegPath = tempFile.path; ffmpegPath = tempFile.path;
} }
Future<Stream> convertFile(String inputPath, String outputPath) async { Future<File?> getTmpFile(String filename) async {
debugPrint('Converting $inputPath to $outputPath'); final Directory directory = Directory('temp');
final List<FileSystemEntity> files = directory.listSync();
for (FileSystemEntity file in files) {
final String dirFilename = p.basenameWithoutExtension(file.path);
debugPrint('dirFilename $dirFilename');
debugPrint('filename $filename');
if (dirFilename == '${filename}_tmp') {
return File(file.path);
}
}
return null;
}
var command = ' -q --flat-playlist -J'; Future<Stream> convertFile(Video video) async {
debugPrint(
'____Converting ${video.title} to ${video.format.format} format_____');
File tmpFile = File('temp/${video.filename}_tmp.mp4');
if (!tmpFile.existsSync()) {
tmpFile = await getTmpFile(video.filename) ??
File('temp/${video.filename}_tmp.mp4');
}
File doneFile =
File('temp/${video.filename}_done.${video.format.extension}');
if (doneFile.existsSync()) {
debugPrint('File already converted');
return Stream.fromIterable(['progress=end']);
}
var command =
'-i "${tmpFile.path}" ${video.format.ffmpegCmd} "temp/${video.filename}_done.${video.format.extension}"';
var shellLinesController = ShellLinesController(); var shellLinesController = ShellLinesController();
var shell = Shell(stdout: shellLinesController.sink); var shell = Shell(
stdout: shellLinesController.sink, stderr: shellLinesController.sink);
await shell.run(''' await shell.run('''
$ffmpegPath $command $ffmpegPath $command
''').then((result) { ''');
debugPrint('Analyse result: $result');
});
return shellLinesController.stream; return shellLinesController.stream;
} }

View File

@ -53,7 +53,8 @@ class DLServices {
} }
Future<Stream> downloadFile(Video video) async { Future<Stream> downloadFile(Video video) async {
debugPrint('Downloading $url in $video.format format'); debugPrint(
'Downloading $url in ${video.format.format} format (${video.filename})');
/* /*
https://youtu.be/playlist?list=PLk1fi9OrZmvGBdh9BdWIhZImGVDUqls1X https://youtu.be/playlist?list=PLk1fi9OrZmvGBdh9BdWIhZImGVDUqls1X
@ -62,12 +63,18 @@ class DLServices {
// get the format code from the Format enum // get the format code from the Format enum
var formatCmd = var formatCmd =
Format.values.firstWhere((e) => e.format == video.format).ytCmd; Format.values.firstWhere((e) => e.format == video.format.format).ytCmd;
debugPrint('Format code: $formatCmd'); debugPrint('Format code: $formatCmd');
var cleanTitle = video.title.replaceAll(RegExp(r'[^\w\s]+'), ''); File doneFile = File('temp/${video.filename}_tmp.mp4');
if (doneFile.existsSync()) {
debugPrint('File already downloaded');
return Stream.fromIterable(['[EmbedThumbnail]']);
}
var strType = convertedFormats.contains(video.format) ? 'tmp' : 'done';
var command = var command =
'${video.url.trim()} --sub-langs "all,-live_chat" --embed-subs --embed-thumbnail --embed-metadata --progress -o "temp/${video.id}_${video.format}_tmp.%(ext)s" -f "$formatCmd"'; '${video.url.trim()} --sub-langs "all,-live_chat" --embed-subs --embed-thumbnail --embed-metadata --progress -o "temp/${video.filename}_$strType.%(ext)s" -f "$formatCmd"';
var shellLinesController = ShellLinesController(); var shellLinesController = ShellLinesController();
var shell = Shell(stdout: shellLinesController.sink, verbose: false); var shell = Shell(stdout: shellLinesController.sink, verbose: false);
@ -84,7 +91,7 @@ class DLServices {
var command = '${url.trim()} -q --flat-playlist -J'; var command = '${url.trim()} -q --flat-playlist -J';
var shellLinesController = ShellLinesController(); var shellLinesController = ShellLinesController();
var shell = Shell(stdout: shellLinesController.sink); var shell = Shell(stdout: shellLinesController.sink, verbose: false);
await shell.run(''' await shell.run('''
$ytDlpPath $command $ytDlpPath $command

View File

@ -1,12 +1,15 @@
import 'dart:math';
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.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/converter.dart';
import 'package:notube/services/download.dart'; import 'package:notube/services/download.dart';
import 'package:notube/constants.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:downloadsfolder/downloadsfolder.dart';
import 'package:path/path.dart' as p;
part 'videos_state.dart'; part 'videos_state.dart';
@ -28,6 +31,17 @@ class VideosCubit extends Cubit<VideosState> {
emit(VideosState(videoList: _videoList)); emit(VideosState(videoList: _videoList));
} }
void changeStatus(Video video, String status) {
int index = _videoList.indexWhere((v) => v.id == video.id);
if (index != -1) {
_videoList[index] = video.copyWith(status: status);
emit(VideosState(videoList: _videoList));
} else {
debugPrint('Video not found in the list');
}
}
void clearVideos() { void clearVideos() {
_videoList.clear(); _videoList.clear();
emit(VideosState(videoList: _videoList)); emit(VideosState(videoList: _videoList));
@ -38,10 +52,20 @@ class VideosCubit extends Cubit<VideosState> {
emit(VideosState(videoList: _videoList)); emit(VideosState(videoList: _videoList));
} }
Future<void> startConverter() async {
var videosToConvert = _videoList.where((video) =>
video.status == 'downloaded' &&
convertedFormats.contains(video.format));
for (Video video in videosToConvert) {
await convertVideo(video);
}
debugPrint('All videos converted');
}
Future<void> startDownloader() async { Future<void> startDownloader() async {
dlService = await DLServices.init(); dlService = await DLServices.init();
downloadingVid = downloadingVid =
_videoList.where((video) => video.status != 'downloaded').take(3); _videoList.where((video) => video.status == 'pending').take(3);
debugPrint('Videos to download: ${downloadingVid.length}'); debugPrint('Videos to download: ${downloadingVid.length}');
while (downloadingVid.isNotEmpty && runningTasks < maxConcurrentTasks) { while (downloadingVid.isNotEmpty && runningTasks < maxConcurrentTasks) {
@ -53,6 +77,37 @@ class VideosCubit extends Cubit<VideosState> {
_videoList.where((video) => video.status != 'downloaded'); _videoList.where((video) => video.status != 'downloaded');
} }
await startConverter();
final prefs = await SharedPreferences.getInstance();
Directory defaultDownloadDirectory = await getDownloadDirectory();
prefs.get('downloadFolder') ??
await prefs.setString('downloadFolder', defaultDownloadDirectory.path);
final downloadDirectory = Directory(
prefs.getString('downloadFolder') ?? defaultDownloadDirectory.path);
for (Video video in _videoList) {
var cleanTitle = video.title.replaceAll(RegExp(r'[^\w\s]+'), '');
final tmpFile =
File('temp/${video.filename}_done.${video.format.extension}');
final newFile = File(
'${downloadDirectory.path}/${cleanTitle}.${video.format.extension}');
await tmpFile.rename(newFile.path);
changeStatus(video, 'done');
debugPrint('File moved to ${newFile.path}');
}
final Directory directory = Directory('temp');
final List<FileSystemEntity> files = directory.listSync();
for (FileSystemEntity file in files) {
final String dirFilename = p.basenameWithoutExtension(file.path);
if (dirFilename.contains('_tmp')) {
file.delete();
}
}
debugPrint('All videos downloaded');
/* /*
for (Video video in videosLeft) { for (Video video in videosLeft) {
final shellStream = await dlService.downloadFile(video.url, video.format); final shellStream = await dlService.downloadFile(video.url, video.format);
@ -70,17 +125,59 @@ class VideosCubit extends Cubit<VideosState> {
Future convertVideo(Video video) async { Future convertVideo(Video video) async {
converterService = await ConverterService.init(); converterService = await ConverterService.init();
debugPrint('Converting ${video.title} to $video.format '); debugPrint('Converting ${video.title} to ${video.format.format}');
final shellStream = changeStatus(video, 'converting');
await converterService.convertFile(video.url, video.format); final shellStream = await converterService.convertFile(video);
shellStream.listen((line) { var duration = '0.0';
debugPrint(line); var completer = Completer<void>();
});
shellStream.listen(
(line) {
debugPrint(line);
//FFmpeg doesn't return any output for audio conversion
if (video.format.extension == "mp3") {
changeStatus(video, 'converted');
if (!completer.isCompleted) {
debugPrint('____Conversion audio completed___');
runningTasks--;
completer.complete();
}
} else {
if (line.contains('Duration: ')) {
var durationString =
line.split('Duration: ')[1].split(',')[0].trim();
duration = durationString;
debugPrint('Duration: $duration');
}
if (line.contains('out_time_ms=')) {
var timeString = line.split('out_time_ms=')[1];
var time = timeString;
debugPrint('Time: $time');
changeStatus(video, 'converting $time on $duration');
}
if (line.contains('progress=end')) {
changeStatus(video, 'converted');
if (!completer.isCompleted) {
debugPrint('____Conversion completed___');
runningTasks--;
completer.complete();
}
}
}
},
onError: (error) {
if (!completer.isCompleted) {
completer
.completeError(error); // Complete with error if an error occurs
}
},
);
return completer.future;
} }
Future downloadVideo(Video video) async { Future downloadVideo(Video video) async {
debugPrint( debugPrint(
'Downloading ${video.title} in ${video.format} format ${video.url}'); '___Downloading ${video.title} in ${video.format} format ${video.filename}____');
final shellStream = await dlService.downloadFile(video); final shellStream = await dlService.downloadFile(video);
var completer = Completer<void>(); var completer = Completer<void>();
@ -92,22 +189,19 @@ class VideosCubit extends Cubit<VideosState> {
var percentString = var percentString =
line.split('[download]')[1].split(' of')[0].split('%')[0].trim(); line.split('[download]')[1].split(' of')[0].split('%')[0].trim();
var percent = percentString == null ? 0 : double.parse(percentString); var percent = percentString == null ? 0 : double.parse(percentString);
video.status = 'downloading $percent%'; changeStatus(video, 'downloading $percent%');
emit(VideosState(videoList: _videoList));
} }
if (line.contains('[EmbedThumbnail]')) { if (line.contains('[EmbedThumbnail]')) {
video.status = 'downloaded'; changeStatus(video, 'downloaded');
video.filename =
line.split('Adding thumbnail to "')[1].split('"')[0].trim() ?? '';
emit(VideosState(videoList: _videoList));
if (!completer.isCompleted) { if (!completer.isCompleted) {
debugPrint('Download completed'); debugPrint('____Download completed___');
runningTasks--; runningTasks--;
completer.complete(); completer.complete();
} }
} }
debugPrint(line); //debugPrint(line);
}, },
onError: (error) { onError: (error) {
if (!completer.isCompleted) { if (!completer.isCompleted) {

View File

@ -6,6 +6,7 @@ import FlutterMacOS
import Foundation import Foundation
import device_info_plus import device_info_plus
import downloadsfolder
import flutter_localization import flutter_localization
import package_info_plus import package_info_plus
import path_provider_foundation import path_provider_foundation
@ -16,6 +17,7 @@ import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
DownloadsfolderPlugin.register(with: registry.registrar(forPlugin: "DownloadsfolderPlugin"))
FlutterLocalizationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalizationPlugin")) FlutterLocalizationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalizationPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))

View File

@ -57,6 +57,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.18.0" version: "1.18.0"
crypto:
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted
version: "3.0.3"
csslib: csslib:
dependency: transitive dependency: transitive
description: description:
@ -65,6 +73,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
dartx:
dependency: transitive
description:
name: dartx
sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
device_info_plus: device_info_plus:
dependency: transitive dependency: transitive
description: description:
@ -81,6 +97,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0" version: "7.0.0"
diacritic:
dependency: transitive
description:
name: diacritic
sha256: "96db5db6149cbe4aa3cfcbfd170aca9b7648639be7e48025f9d458517f807fe4"
url: "https://pub.dev"
source: hosted
version: "0.1.5"
downloadsfolder:
dependency: "direct main"
description:
name: downloadsfolder
sha256: e9987e56b998e3788047f977d31a1b50b4af58c388aefc3d157b9ac5c5d786a2
url: "https://pub.dev"
source: hosted
version: "1.1.0"
easy_localization: easy_localization:
dependency: "direct main" dependency: "direct main"
description: description:
@ -381,6 +413,54 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.2.1"
permission_handler:
dependency: transitive
description:
name: permission_handler
sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
url: "https://pub.dev"
source: hosted
version: "11.3.1"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: b29a799ca03be9f999aa6c39f7de5209482d638e6f857f6b93b0875c618b7e54
url: "https://pub.dev"
source: hosted
version: "12.0.7"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0
url: "https://pub.dev"
source: hosted
version: "9.4.5"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: "54bf176b90f6eddd4ece307e2c06cf977fb3973719c35a93b85cc7093eb6070d"
url: "https://pub.dev"
source: hosted
version: "0.1.1"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: "48d4fcf201a1dad93ee869ab0d4101d084f49136ec82a8a06ed9cfeacab9fd20"
url: "https://pub.dev"
source: hosted
version: "4.2.1"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@ -454,7 +534,7 @@ packages:
source: hosted source: hosted
version: "0.1.9" version: "0.1.9"
shared_preferences: shared_preferences:
dependency: transitive dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
@ -570,6 +650,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.2" version: "0.7.2"
time:
dependency: transitive
description:
name: time
sha256: ad8e018a6c9db36cb917a031853a1aae49467a93e0d464683e029537d848c221
url: "https://pub.dev"
source: hosted
version: "2.1.4"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:

View File

@ -28,6 +28,8 @@ dependencies:
equatable: ^2.0.5 equatable: ^2.0.5
json_annotation: ^4.9.0 json_annotation: ^4.9.0
bloc: ^8.1.4 bloc: ^8.1.4
shared_preferences: ^2.2.3
downloadsfolder: ^1.1.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@ -6,14 +6,20 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <downloadsfolder/downloadsfolder_plugin_c_api.h>
#include <flutter_localization/flutter_localization_plugin_c_api.h> #include <flutter_localization/flutter_localization_plugin_c_api.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h> #include <screen_retriever/screen_retriever_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
#include <window_manager/window_manager_plugin.h> #include <window_manager/window_manager_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
DownloadsfolderPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DownloadsfolderPluginCApi"));
FlutterLocalizationPluginCApiRegisterWithRegistrar( FlutterLocalizationPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterLocalizationPluginCApi")); registry->GetRegistrarForPlugin("FlutterLocalizationPluginCApi"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
ScreenRetrieverPluginRegisterWithRegistrar( ScreenRetrieverPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
UrlLauncherWindowsRegisterWithRegistrar( UrlLauncherWindowsRegisterWithRegistrar(

View File

@ -3,7 +3,9 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
downloadsfolder
flutter_localization flutter_localization
permission_handler_windows
screen_retriever screen_retriever
url_launcher_windows url_launcher_windows
window_manager window_manager