I need to merge (concatenate) two audio files in a Flutter app.
I am trying to use Flutter_ffmpeg package .
https://pub.dev/packages/flutter_ffmpeg
Ffmpeg is powerful tool for audio and video.
flutter_cache_manager package to store the files that came from https
https://pub.dev/packages/flutter_cache_manager
And path provided to deal with the paths
https://pub.dev/packages/path_provider
For the part of caching I already made some successful tests,
But, I need your help to understand how can I use ffmpeg package.
According to the readme I need to do something like
import 'package:flutter_ffmpeg/flutter_ffmpeg.dart';
final FlutterFFmpeg _flutterFFmpeg = new FlutterFFmpeg();
var fileCached1 = await DefaultCacheManager().getSingleFile(url1);
var fileCached2 = await DefaultCacheManager().getSingleFile(url2);
var arguments = ["-i", "concat:fileCached1.wav|fileCached2.wav", "-c copy", "output.way"];
_flutterFFmpeg.executeWithArguments(arguments ).then((rc) => print("FFmpeg process exited with rc $rc"));
How can I have access to the output.wav to play? How can I use the path_provider?
I really need you help. :)
There is little information about flutter_ffmpeg. This question might be useful for someone else to.
Thanks in advance,
Ricardo
The path_provider package let you to access appDir & temp directory of your application, let say that you want to access the application documents directory :
Directory appDocumentDir = await getApplicationDocumentsDirectory();
String rawDocumentPath = appDocumentDir.path;
and if your output's file name is "output.wav"
String outputPath = Strings.concatAll([rawDocumentPath, "/output.wav"]);
now outputPath contain the path to output file that will be generated by FFMPEG and you can use it later when you want to play/copy/upload or whatever you want to.
but in the FFMPEG part, the command line for concat two input file is:
ffmpeg -i input1.wav -i input2.wav -filter_complex '[0:0][1:0]concat=n=2:v=0:a=1[out]' -map '[out]' output.wav
If you want to have 3 input files, use n=3 and change the other parts in command.
usage of Flutter_ffmpeg will be something like this:
import 'package:flutter_ffmpeg/flutter_ffmpeg.dart';
final FlutterFFmpeg _flutterFFmpeg = new FlutterFFmpeg();
_flutterFFmpeg.execute("-i input1.wav -i input2.wav -filter_complex '[0:0][1:0]concat=n=2:v=0:a=1[out]' -map '[out]' output.wav").then((rc) => print("FFmpeg process exited with rc $rc"));
ideally we want to use outputPath here so do something like this :
String commandToExecute = Strings.concatAll(["-i input1.wav -i input2.wav -filter_complex '[0:0][1:0]concat=n=2:v=0:a=1[out]' -map '[out]' ", outputPath]);
and your final statement for executing the process will be :
_flutterFFmpeg.execute(commandToExecute).then((rc) => print("FFmpeg process exited with rc $rc"));
after execution you can check execution output. Zero represents successful execution, non-zero values represent failure :
final FlutterFFmpegConfig _flutterFFmpegConfig = new FlutterFFmpegConfig();
_flutterFFmpegConfig.getLastReturnCode().then((rc) => print("Last rc: $rc"));
_flutterFFmpegConfig.getLastCommandOutput().then((output) => print("Last command output: $output"));
so in case of failure or getting error, you can check the command output for fixing bugs
you can find more information about flutter_ffmpeg in here :
https://pub.dev/packages/flutter_ffmpeg
also for using ffmpeg and find more about filters you can check this out :
https://ffmpeg.org/ffmpeg-filters.html
First get the directory
final appDirectory = await getExternalStorageDirectory();
Then in the execute commands append the output file with directory.
_flutterFFmpeg.execute("-i $videoURL -i $audioURL -c copy ${appDirectory?.path}/output.mp4");
Related
I am using ffmpeg to cut video via android, all working fine but only issue come when if the source directory contain space the command is not working by saying file not found
-i /storage/emulated/0/ISave Videos/Br0gv9anKKg.mp4 -acodec copy -vcodec copy -ss 00:00:10 -t 00:00:20 /storage/emulated/0/1547813275685ChunkClip.mp4
file not found is Like"File not found -i /storage/emulated/0/ISave"
But for other path no issue and all works fine only if space is there in file path the issue comes
Cool Question
Its very confusing to solve there type of issues even if we get a solution
The following Link show some fix for handling issues like this
Quoting and escaping
So for the issue you have there is fix Add each word in ffmpeg comments to an LinkedList and then convert to command array so that ffmpeg will execute it
As follows (As per you requirement command)
List<String> cmdList = new LinkedList<>();
cmdList.add("-i");
cmdList.add("/storage/emulated/0/ISave Videos/Br0gv9anKKg.mp4");
cmdList.add("-acodec");
cmdList.add("copy");
cmdList.add("-vcodec");
cmdList.add("copy");
cmdList.add("-ss");
cmdList.add("00:00:10");
cmdList.add("-t");
cmdList.add("00:00:20");
cmdList.add("/storage/emulated/0/1547813275685ChunkClip.mp4");
String[] command = cmdList.toArray(new String[cmdList.size()]);
Hope your issue will be get resolved by this solution
Background
So far, I was able to install APK files using root (within the app), via this code:
pm install -t -f fullPathToApkFile
and if I want to (try to) install to sd-card :
pm install -t -s fullPathToApkFile
The problem
Recently, not sure from which Android version (issue exists on Android P beta, at least), the above method fails, showing me this message:
avc: denied { read } for scontext=u:r:system_server:s0 tcontext=u:object_r:sdcardfs:s0 tclass=file permissive=0
System server has no access to read file context u:object_r:sdcardfs:s0 (from path /storage/emulated/0/Download/FDroid.apk, context u:r:system_server:s0)
Error: Unable to open file: /storage/emulated/0/Download/FDroid.apk
Consider using a file under /data/local/tmp/
Error: Can't open file: /storage/emulated/0/Download/FDroid.apk
Exception occurred while executing:
java.lang.IllegalArgumentException: Error: Can't open file: /storage/emulated/0/Download/FDroid.apk
at com.android.server.pm.PackageManagerShellCommand.setParamsSize(PackageManagerShellCommand.java:306)
at com.android.server.pm.PackageManagerShellCommand.runInstall(PackageManagerShellCommand.java:884)
at com.android.server.pm.PackageManagerShellCommand.onCommand(PackageManagerShellCommand.java:138)
at android.os.ShellCommand.exec(ShellCommand.java:103)
at com.android.server.pm.PackageManagerService.onShellCommand(PackageManagerService.java:21125)
at android.os.Binder.shellCommand(Binder.java:634)
at android.os.Binder.onTransact(Binder.java:532)
at android.content.pm.IPackageManager$Stub.onTransact(IPackageManager.java:2806)
at com.android.server.pm.PackageManagerService.onTransact(PackageManagerService.java:3841)
at android.os.Binder.execTransact(Binder.java:731)
This seems to also affect popular apps such as "Titanium backup (pro)", which fails to restore apps.
What I've tried
Looking at what's written, it appears it lacks permission to install APK files that are not in /data/local/tmp/.
So I tried the next things, to see if I can overcome it:
set the access to the file (chmod 777) - didn't help.
grant permissions to my app, of both storage and REQUEST_INSTALL_PACKAGES (using ACTION_MANAGE_UNKNOWN_APP_SOURCES Intent) - didn't help.
create a symlink to the file, so that it will be inside the /data/local/tmp/, using official API:
Os.symlink(fullPathToApkFile, symLinkFilePath)
This didn't do anything.
create a symlink using this :
ln -sf $fullPathToApkFile $symLinkFilePath
This partially worked. The file is there, as I can see it in Total Commander app, but when I try to check if it exists there, and when I try to install the APK from there, it fails.
Copy/move (using cp or mv) the file to the /data/local/tmp/ path, and then install from there. This worked, but it has disadvantages: moving is risky because it temporarily hides the original file, and it changes the timestamp of the original file. Copying is bad because of using extra space just for installing (even temporarily) and because it wastes time in doing so.
Copy the APK file, telling it to avoid actual copy (meaning hard link), using this command (taken from here) :
cp -p -r -l $fullPathToApkFile $tempFileParentPath"
This didn't work. It got me this error:
cp: /data/local/tmp/test.apk: Cross-device link
Checking what happens in other cases of installing apps. When you install via via the IDE, it actually does create the APK file in this special path, but if you install via the Play Store, simple APK install (via Intent) or adb (via PC), it doesn't.
Wrote about this here too: https://issuetracker.google.com/issues/80270303
The questions
Is there any way to overcome the disadvantages of installing the APK using root on this special path? Maybe even avoid handling this path at all?
Why does the OS suddenly require to use this path? Why not use the original path instead, just like in the other methods of installing apps? What do the other methods of installing apps do, that somehow avoids using the spacial path?
One solution, in case you don't mind the moving procedure, is to also save&restore the timestamp of the original file, as such:
val tempFileParentPath = "/data/local/tmp/"
val tempFilePath = tempFileParentPath + File(fullPathToApkFile).name
val apkTimestampTempFile = File(context.cacheDir, "apkTimestamp")
apkTimestampTempFile.delete()
apkTimestampTempFile.mkdirs()
apkTimestampTempFile.createNewFile()
root.runCommands("touch -r $fullPathToApkFile ${apkTimestampTempFile.absolutePath}")
root.runCommands("mv $fullPathToApkFile $tempFileParentPath")
root.runCommands("pm install -t -f $tempFilePath")
root.runCommands("mv $tempFilePath $fullPathToApkFile")
root.runCommands("touch -r ${apkTimestampTempFile.absolutePath} $fullPathToApkFile")
apkTimestampTempFile.delete()
It's still a bit dangerous, but better than copying files...
EDIT: Google has shown me a nice workaround for this (here) :
We don't support installation of APKs from random directories on the device. They either need to be installed directly from the host using 'adb install' or you have to stream the contents to install --
$ cat foo.apk | pm install -S APK_SIZE
While I think this is incorrect that they don't support installing of APK files from random paths (always worked before), the workaround does seem to work. All I needed to change in the code of installing an APK file is as such:
val length = File(fullPathToApkFile ).length()
commands.add("cat $fullPathToApkFile | pm install -S $length")
Thing is, now I have some other questions about it :
Does this workaround avoid the moving/copying of the APK into storage, and without affecting the original file ? - seems it does
Will this support any APK file, even large ones? - seems it succeeds in doing it for an APK that takes 433MB, so I think it's safe to use for all sizes.
This is needed only from Android P, right? - so far seems so.
Why does it need the file size as a parameter ? - No idea, but if I remove it, it won't work
Thanks for the answers! I looked everywhere else as well to get a whole setup for OTA to work for Android 10 and so on. It 100% works on Samsung Galaxy Tab 10.1 running Android 10.
Here is a medium article with the code:
https://medium.com/#jnishu1996/over-the-air-ota-updates-for-android-apps-download-apk-silent-apk-installation-auto-launch-8ee6f342197c
The magic is running this command with root access:
process = Runtime.getRuntime().exec("su");
out = process.getOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream(out);
// Get all file permissions
dataOutputStream.writeBytes("chmod 777 " + file.getPath() + "\n");
// Perform silent installation command, all flags are necessary for some reason, only this works reliably post Android 10
String installCommand = "cat " + file.getAbsolutePath() + "| pm install -d -t -S " + file.length();
// Data to send to the LaunchActivity to the app knows it got updated and performs necessary functions to notify backend
// es stands for extraString
// In LaunchActivity onCreate(), you can get this data by running -> if (getIntent().getStringExtra("OTA").equals("true"))
String launchCommandIntentArguments = "--es OTA true --es messageId " + MyApplication.mLastSQSMessage.receiptHandle();
// Start a background thread to wait for 8 seconds before reopening the app's LaunchActivity, and pass necessary arguments
String launchCommand = "(sleep 8; am start -n co.getpresso.Presso/.activities.LaunchActivity " + launchCommandIntentArguments + ")&";
// The entire command is deployed with a ";" in the middle to launchCommand run after installCommand
String installAndLaunchCommand = installCommand + "; " + launchCommand;
// begins the installation
dataOutputStream.writeBytes(installAndLaunchCommand);
dataOutputStream.flush();
// Close the stream operation
dataOutputStream.close();
out.close();
int value = process.waitFor();
I go though many site and search regarding "FFMPEG" implementation for android project.
Most solution founded are using NDK.
but i want to use FFmpeg without using NDK as i found in This Link
I have used this project
https://github.com/guardianproject/android-ffmpeg-java
It has already compiled for android version of FFMPEG library and this file will be in res/raw folder (you can update this file if you need newer version). You need to add this project as a library to your's. And after thst you can write your own function in java for example like this:
public Clip convert (Clip mediaIn, String outPath, ShellCallback sc) throws Exception
{
ArrayList<String> cmd = new ArrayList<String>();
cmd.add(mFfmpegBin);
cmd.add("-y");
cmd.add("-i");
cmd.add(new File(mediaIn.path).getCanonicalPath());
if (mediaIn.startTime != null)
{
cmd.add("-ss");
cmd.add(mediaIn.startTime);
}
if (mediaIn.duration != -1)
{
cmd.add("-t");
cmd.add(String.format(Locale.US,"%f",mediaIn.duration));
}
Clip mediaOut = new Clip();
File fileOut = new File(outPath);
mediaOut.path = fileOut.getCanonicalPath();
cmd.add(mediaOut.path);
execFFMPEG(cmd, sc);
return mediaOut;
}
and execute it using FfmpegController Object.
Please notice me if you have any questions or if this is what you want.
EDIT:
I hope you connect this github code as a library for your project.
There is FfmpegController.java class in src folder. It's a wrapper for using command line ffmpeg exe file. If you want for example execute command like this one
ffmpeg -i source.wav -b:a 128k output.mp3
you need to add function to FfmpegController.java class. Something like this:
public Clip convert(Clip mediaIn, String outPath, ShellCallback sc) throws Exception
{
ArrayList<String> cmd = new ArrayList<String>();
Clip mediaOut = new Clip();
String mediaPath = mediaIn.path;
cmd = new ArrayList<String>();
cmd.add(mFfmpegBin);
cmd.add("-i");
cmd.add(mediaPath);
cmd.add("-b:a");
cmd.add("128k");
mediaOut.path = outPath;
cmd.add(mediaOut.path);
execFFMPEG(cmd, sc);
return mediaOut; // this is not importatnt because file will be put in outPath
}
Now in your project initialize FfmpegController object and run your function.
I have used this FFmpeg sample which is library that used without NDK
First of download sample example FFmpeg Sample
Download FFmpeg library FFmpeg Library
Extract both in one folder and import project from Android Studio
Now, Calling FFmpeg command
This command is for rotate (/sdcard/videokit/in.mp4) video in 90 Angle and generate out.mp4 in specific location in SD card
ffmpeg -y -i /sdcard/videokit/in.mp4 -strict experimental -vf transpose=1 -s 160x120 -r 30 -aspect 4:3 -ab 48000 -ac 2 -ar 22050 -b 2097k /sdcard/videokit/out.mp4
Now run this command with predefined method in library and add listeners of GeneralUtils
GeneralUtils.copyLicenseFromAssetsToSDIfNeeded(this, workFolder);
GeneralUtils.copyDemoVideoFromAssetsToSDIfNeeded(this, demoVideoFolder);
//demoVideoFolder where your Input file path
//workFolder Absolute path
// workFolder = getApplicationContext().getFilesDir().getAbsolutePath() + "/";
LoadJNI vk = new LoadJNI();
try {
vk.run(GeneralUtils.utilConvertToComplex(commandStr), workFolder, getApplicationContext());
// copying vk.log (internal native log) to the videokit folder
GeneralUtils.copyFileToFolder(vkLogPath, demoVideoFolder);
} catch (Throwable e) {
Log.e(Prefs.TAG, "vk run exeption.", e);
}
Run this and check in File Manager for Output . I hope it works Good :) Enjoy
As I'm running an old Python version on android which gives incorrect file sizes for files > 4 GB I tried writing a workaround to get the correct sizes, code:
def getsize_workaround( filename ):
import subprocess as s
output = s.Popen("ls -l " + filename, shell=True, executable="/system/bin/sh", stdout=s.PIPE).communicate()[0]
size = long(re.split(r'\s+', output)[3])
return size
This works well when I try to call it using a simple python script:
print(getsize_workaround(path))
However, when I try to use it in my NZBGet VideoSort script it can't find ls and pops this error at: output = s.Popen("ls -l " + filename, shell=True, executable="/system/bin/sh", stdout=s.PIPE).communicate()[0]-> : /bin/sh: ls: not found. (function is called at line 824, see dropbox link below).
Haven't got a clue why it can't find ls anymore, anyone help is much appreciated. You can find the VideoSort script here: https://db.tt/oM3U5gZR.
PATH variable didn't include the correct directories when run from NZBGet. Fixed by setting os.environ['PATH'] manually. Thanks to abernert for the tip.
also try adding an alias in your bash script for the python version you want to use
alias python=<python version you want to use>
I am working on an Android app where I want to create a video from a
list of static images. After doing some search on internet, it made me
realized that using "FFMPEG" is the way to go in getting this thing
done. So I got hold of this site:
https://github.com/guardianproject/android-ffmpeg-java from where I
downloaded the C library and the Java wrapper. I was able to compile the
C library - of course not the way the instruction was laid out - but
still I was able to get "ffmpeg" executable under
/external/android-ffmpeg/ffmpeg directory. I copied that executable in
my current directory and then copied it to a directory under Android
where my app can access it. Then I called the provided Java wrapper but
I am seeing some errors in the log file like follows:
08-13 11:55:37.848: D/FFMPEG(29598): /data/data/com.sample/app_bin/ffmpeg -y -loop 1 -i /storage/emulated/0/usersnapshot/ffmpeg/image%03d.jpg -r 25 -t 2 -qscale 5 /storage/emulated/0/video/snapshot-video.mp4
08-13 11:55:37.898: I/ShellCallback : shellOut()(29598): /data/data/com.sample/app_bin/ffmpeg[1]: syntax error: '(' unexpected
08-13 11:55:37.938: I/ShellCallback : processComplete()(29598): 1
And following is the code snippet (where targetDirectoryForFFMPEG = directory where the images are stored):
FfmpegController ffmpegController = new FfmpegController(this, targetDirectoryForFFMPEG);
String out = videoOutPutFile.getPath();
MediaDesc mediaIn = new MediaDesc();
mediaIn.path = targetDirectoryForFFMPEG+"/image%03d.jpg";
mediaIn.videoFps = "25";
ffmpegController.convertImageToMP4(mediaIn, 2, out,new ShellCallback() {
#Override
public void shellOut(String shellLine) {
Log.i("ShellCallback : shellOut()", shellLine);
}
#Override
public void processComplete(int exitValue) {
Log.i("ShellCallback : processComplete()", exitValue+"");
}
});
Has anybody implemented this before? If yes, can you point me to what am I doing incorrect? I will provide more information if needed.
Do you have root on the device?
Mount '/data' and then enter your same 'ffmpeg' command in the shell and see whether the error is the same.
Try using the shell to test out different command expressions.
Try 'ffmpeg' alone and with just one input file. See whether those commands produce expected output.
My wild guess would be that there is an issue with calling 'ffmpeg.main()' that relates to the details of your build.
Your ffmpeg might not be compiled properly for the arm. I was getting the same error when not using a correctly compiled ffmpeg.