From d72fc56ad336bdcde50287332396876350ab6ca9 Mon Sep 17 00:00:00 2001 From: Jesus Acosta Date: Tue, 14 Apr 2020 23:56:29 -0400 Subject: [PATCH] FFmpeg: First implementation to convert downloaded Songs --- android/app/build.gradle | 2 +- .../com/example/songtube/MainActivity.java | 14 +++ lib/internal/native.dart | 5 ++ lib/internal/songtube_classes.dart | 87 ++++++++++++++++++- lib/library.dart | 7 ++ lib/tabs/hometab.dart | 2 +- 6 files changed, 114 insertions(+), 3 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index d5804e61..af9c085f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -34,7 +34,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.songtube" - minSdkVersion 16 + minSdkVersion 24 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/android/app/src/main/java/com/example/songtube/MainActivity.java b/android/app/src/main/java/com/example/songtube/MainActivity.java index 81bc5ddf..b4745de6 100644 --- a/android/app/src/main/java/com/example/songtube/MainActivity.java +++ b/android/app/src/main/java/com/example/songtube/MainActivity.java @@ -8,10 +8,14 @@ import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; +import android.content.Context; +import android.net.Uri; +import java.io.File; public class MainActivity extends FlutterActivity { String sharedText; private static final String CHANNEL = "sharedTextChannel"; + private static final String CONVERTERCHANNEL = "registerMedia"; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine); @@ -23,6 +27,16 @@ public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { } } ); + new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CONVERTERCHANNEL) + .setMethodCallHandler( + (call, result) -> { + if (call.method.equals("registerFile")) { + String argument = call.argument("file"); + File file = new File(argument); + sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file))); + } + } + ); } @Override diff --git a/lib/internal/native.dart b/lib/internal/native.dart index 6128efd8..a3089080 100644 --- a/lib/internal/native.dart +++ b/lib/internal/native.dart @@ -5,6 +5,7 @@ NativeMethod method; class NativeMethod { + static const media = const MethodChannel("registerMedia"); static const platform = const MethodChannel("sharedTextChannel"); String _sharedIntent; @@ -22,4 +23,8 @@ class NativeMethod { } } + void registerFile(String file) async { + await media.invokeMethod('registerFile', {"file":file}); + } + } \ No newline at end of file diff --git a/lib/internal/songtube_classes.dart b/lib/internal/songtube_classes.dart index 01013bf0..abe9d2a5 100644 --- a/lib/internal/songtube_classes.dart +++ b/lib/internal/songtube_classes.dart @@ -2,12 +2,89 @@ import 'dart:async'; import 'dart:io'; import 'package:ext_storage/ext_storage.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart'; +import 'package:flutter_ffmpeg/flutter_ffmpeg.dart'; +import 'native.dart'; +import 'package:image_downloader/image_downloader.dart'; Downloader downloader; AppStreams appdata; +Converter converter; + +enum FFmpegArgs { argsToACC } + +class Converter { + + // Declare our FFmpeg instances + final FlutterFFmpeg flutterFFmpeg = new FlutterFFmpeg(); + final FlutterFFprobe flutterFFprobe = new FlutterFFprobe(); + + // Get the data we need before declaring our Argument lists + Future> getArgumentsList(FFmpegArgs type, MediaMetaData metadata, [String path]) async { + + List _argsList; + String _path; + String _finalPath; + + if (path != null) _path = path; + if (path == null) _path = await ExternalPath().externalMusic; + _finalPath = _path + "/" + metadata.title; + if (type == FFmpegArgs.argsToACC) { + _argsList = [ + "-i", + appdata.getLastFilePath, + "-c:a", "aac", + "-profile:a", "aac_low", + "-metadata", "title=${metadata.title}", + "-metadata", "album=${metadata.album}", + "-metadata", "artist=${metadata.artist}", + "-metadata", "genre=${metadata.genre}", + "-metadata", "date=${metadata.date}", + "-metadata", "disk=${metadata.disk}", + "-metadata", "track=${metadata.track}", + "$_finalPath.m4a", + ]; + } + print("Full path: " + _argsList.last); + return _argsList; + + } + + // Functions to convert media + Future convertAudio(List arguments) async { + print("Converter: Starting audio file convertion..."); + int _result; + await flutterFFmpeg.executeWithArguments(arguments).then( + (value) { + File(appdata.getLastFilePath).delete(); + method.registerFile(arguments.last); + _result = value; + } + ); + print("Converter: Done, result: " + _result.toString()); + return _result; + } + +} + +class MediaMetaData { + + String title; + String album; + String artist; + String genre; + String coverurl; + String date; + String disk; + String track; + + MediaMetaData(this.title, this.album, this.artist, this.genre, + this.coverurl, this.date, this.disk, this.track); + +} class Downloader { String id; + MediaMetaData defaultMetaData; Downloader() { appdata.isDownloading.add(false); @@ -28,6 +105,10 @@ class Downloader { appdata.audioDuration.add(mediaStream.videoDetails.duration); appdata.audioSize.add(audio.size); appdata.linkReady.add(true); + defaultMetaData = MediaMetaData(mediaStream.videoDetails.title, mediaStream.videoDetails.author, + mediaStream.videoDetails.author, "Any", mediaStream.videoDetails.thumbnailSet.maxResUrl, + mediaStream.videoDetails.uploadDate.toString(), + "Any", "Any"); id = _id; yt.close(); return 1; @@ -44,7 +125,7 @@ class Downloader { var audio = mediaStream.audio.last; // Compose the file name removing the unallowed characters in windows. - var fileName = '${mediaStream.videoDetails.title}.m4a' + var fileName = '${mediaStream.videoDetails.title}' .replaceAll('Container.', '') .replaceAll(r'\', '') .replaceAll('/', '') @@ -79,6 +160,8 @@ class Downloader { } await output.close(); appdata.getLastFilePath = "$directory/$fileName"; + appdata.getFileName = "$fileName"; + appdata.getDownloadPath = directory; appdata.isDownloading.add(false); print("Downloader: All done"); } @@ -109,7 +192,9 @@ class AppStreams { StreamController audioTitle = new StreamController.broadcast(); StreamController audioArtist = new StreamController.broadcast(); + String getFileName; String getLastFilePath; + String getDownloadPath; void unloadStreams() { audioSize.add(null); diff --git a/lib/library.dart b/lib/library.dart index 2555a455..93659938 100644 --- a/lib/library.dart +++ b/lib/library.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:image_downloader/image_downloader.dart'; import 'tabs/downloadtab.dart'; import 'tabs/hometab.dart'; import 'tabs/settingstab.dart'; @@ -20,6 +21,7 @@ class _LibraryState extends State { appdata = AppStreams(); downloader = Downloader(); method = NativeMethod(); + converter = Converter(); super.initState(); } @@ -110,6 +112,11 @@ class _LibraryState extends State { child: FloatingActionButton( onPressed: () async { await downloader.download(); + List list = await converter.getArgumentsList(FFmpegArgs.argsToACC, + downloader.defaultMetaData); + int result = await converter.convertAudio(list); + if (result == 0) print("Library: Audio convertion done successful"); + if (result == 1) print("Library: Audio convertion failed"); }, child: Icon( Icons.file_download, diff --git a/lib/tabs/hometab.dart b/lib/tabs/hometab.dart index 2eb21687..e311e197 100644 --- a/lib/tabs/hometab.dart +++ b/lib/tabs/hometab.dart @@ -11,7 +11,7 @@ class HomeTab extends StatefulWidget { } class _HomeTabState extends State with AutomaticKeepAliveClientMixin, WidgetsBindingObserver { - + @override void didChangeAppLifecycleState(AppLifecycleState state) async { switch (state){