Pass InputStream instead of song file to JaudioTagger

Issue #173 new
Denimsun Basumatary created an issue

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)

  1. Denimsun Basumatary 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.

  2. Eric Snell

    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;
        }
    
  3. Denimsun Basumatary reporter

    Some of the problems I faced while using the above solution:

    1. When batch tagging song files, its CPU intensive and takes lot of time.
    2. 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.
    3. 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.
    4. Cannot tag podcast because takes lot of time.
  4. Eric Snell

    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.

  5. Guram Savinov

    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.

  6. Log in to comment