Pass InputStream instead of song file to JaudioTagger
Is there a way for me to pass InputStream instead of song file for JaudioTagger to edit tags? It could solve many of the write permission problems in Android.
Comments (12)
-
repo owner -
reporter In Android 5.0 & above, using Java IO API to open InputStream doesn't work most of the time. Even thought I have write permissions, it won't work.
But using URI it is possible.I can get URI from song file. I can simply use openInputStream(URI uri) and it will return InputStream
Edit: Using Java I/O to open InputStream works, but doesn't work with OutputStream.
-
repo owner Why is this a bug in the Java implementation or something else.
-
reporter It's a result of poor api decisions taken by google android developer. They introduced Storage Access Framework on Android 4.4 which changed how developers work with filesystem.
You can learn more about it here: https://developer.android.com/guide/topics/providers/document-provider.html
-
You can work around this problem, but it is ugly! Using the Storage Access Framework, copy the file you want to edit into your app private area. Save your edits to that file and then copy the file back to its original location using SAF again.
Here's some pseudocode you'll need to refactor.
public static boolean copyFile(@NonNull final File source, @NonNull final File target) { try { // First try the normal way if (isWritable(target)) { copy(source, target); } else { Uri outputUri = Uri.EMPTY; try (FileInputStream inStream = new FileInputStream(source)) { if (Is.atLeast(SdkVersion.LOLLIPOP)) { // Storage Access Framework DocumentFile targetDocument = getDocumentFile(target, false, true); if (targetDocument != null) { outputUri = nullToEmptyUri(targetDocument.getUri()); } } else if (Is.atLeast(SdkVersion.KITKAT)) { // Workaround for Kitkat ext SD card outputUri = nullToEmptyUri(MediaStoreUtil.getUriFromFile(target.getAbsolutePath())); } else { return false; } if (Uri.EMPTY != outputUri) { try (OutputStream outStream = App.context().getContentResolver().openOutputStream(outputUri)) { byte[] buffer = new byte[4096]; // MAGIC_NUMBER int bytesRead; while ((bytesRead = inStream.read(buffer)) != -1) { //noinspection ConstantConditions outStream.write(buffer, 0, bytesRead); // in unlikely case outStream is null, we'll catch it below } } } } } } catch (Exception e) { Timber.e(e, "Error when copying file from %s to %s", source.getAbsolutePath(), target.getAbsolutePath()); return false; } return true; }
-
reporter Not an elegant solution, but works. Thanks.
-
reporter Some of the problems I faced while using the above solution:
- When batch tagging song files, its CPU intensive and takes lot of time.
- Sometimes the app private storage is an internal storage. Usually internal storage has low memory therefore copying files causes OutOfMemory error, I can check the size of the storage before copying but the song will not be tagged.
- I have remove album art of all songs option in my app, and it takes more than half an hour to complete with 100 songs.
- Cannot tag podcast because takes lot of time.
-
I never use it in batch mode (yet). I'm not surprised it performs very poorly. However, that seems excessive. I'd do some profiling to see where the problem is. File copying shouldn't be that slow. Jaudiotagger does give the GC a lot of exercise and we feel it on Android more so than other platforms. I don't have time to do tests myself, but I'd be interested in results. Especially comparing files your app owns vs having to go through SAF.
One thing, private storage can be external. Use getExternalFilesDir(). I immediately clean files to avoid this. Seems space is very tight on internal if 1 extra file is causing that. I'd think failure would happen almost immediately.
For batch tagging your app needs to own the files or they need to be in a public area. If you have write permission you don't need to go through SAF and do all that copying. Maybe not feasible, depends on the other app and the user requirements. Well-behaved apps should put things like general audio (music, podcasts...) and video in public areas.
-
Vouch for this
-
reporter @fillobotto How did you solve this problem in your app?
-
No, it's not possible without moving to a different tagging library.
-
Do you have any plans about support of passing input stream instead of a file? jaudiotagger is an amazing audio tag framework, but it can't be used with amazon S3, because S3 objects are streams, not the files.
- Log in to comment
Not currently, can you give more details of the android write permissions