diff --git a/CHANGELOG.md b/CHANGELOG.md index 7247c0a..3a8d7cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## [1.4.0] - [10.26.2021] +### Features +- **[Added]** `original` parameter to `[getUri]`. Now you can get a `original` or `modified` path. - [#10](https://github.com/LucasPJS/on_audio_edit/issues/10) + +### Fixes +- **[Fixed]** error when tring to read a `Flac` file. - [#11](https://github.com/LucasPJS/on_audio_edit/issues/11) +- **[Fixed]** error when tring to read a file without `artwork`. - [#12](https://github.com/LucasPJS/on_audio_edit/issues/12) + +### Documentation +- Updated `README` documentation. + ## [1.3.0] - [10.23.2021] ### Features - **[Added]** `[getUri]` used to retrive the user selected folder path. - [#10](https://github.com/LucasPJS/on_audio_edit/issues/10) diff --git a/DEPRECATED.md b/DEPRECATED.md index 784666b..b4ea2b8 100644 --- a/DEPRECATED.md +++ b/DEPRECATED.md @@ -1,4 +1,4 @@ -## [1.2.0] - [10.20.2021] -> [X.X.X] - [XX.XX.XXXX] +## [1.2.0] - [10.20.2021] -> [1.4.0] - [26.10.2021] ### Deprecated - `[readAllAudio]`. - Use `[readAudio]` instead. @@ -6,7 +6,7 @@ - Use `[getImage]` instead. - `[id]` from `[AudioModel]`. -## [1.1.0] - [10.20.2021] -> [X.X.X] - [XX.XX.XXXX] +## [1.1.0] - [10.20.2021] -> [1.4.0] - [26.10.2021] ### Deprecated - `[AudiosTagModel]`. - Use `[AudioModel]` instead. \ No newline at end of file diff --git a/README.md b/README.md index efa358b..cd15540 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ NOTE: Feel free to help with readme translations Add the following code to your `pubspec.yaml`: ```yaml dependencies: - on_audio_edit: ^1.3.0 + on_audio_edit: ^1.4.0 ``` #### Request Permission: diff --git a/README.pt-BR.md b/README.pt-BR.md index 9150888..7ced461 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -34,7 +34,7 @@ NOTE: Fique à vontade para ajudar nas traduções Adicione o seguinte codigo para seu `pubspec.yaml`: ```yaml dependencies: - on_audio_edit: ^1.3.0 + on_audio_edit: ^1.4.0 ``` #### Solicitar Permissões: diff --git a/android/src/main/kotlin/com/lucasjosino/on_audio_edit/extensions/OnHelperExtension.kt b/android/src/main/kotlin/com/lucasjosino/on_audio_edit/extensions/OnHelperExtension.kt index 4731721..ae5142f 100644 --- a/android/src/main/kotlin/com/lucasjosino/on_audio_edit/extensions/OnHelperExtension.kt +++ b/android/src/main/kotlin/com/lucasjosino/on_audio_edit/extensions/OnHelperExtension.kt @@ -1,5 +1,8 @@ package com.lucasjosino.on_audio_edit.extensions +import android.util.Log +import org.jaudiotagger.tag.FieldKey + fun String.tryInt(key: Int): Any? { return when (key) { 1, @@ -30,3 +33,11 @@ fun String.tryInt(key: Int): Any? { else -> this } } + +fun ArrayList.checkFlac(data: String): ArrayList { + val tmpArray: ArrayList = this + if (data.endsWith(".flac")) { + tmpArray.removeAt(19) + } + return tmpArray +} diff --git a/android/src/main/kotlin/com/lucasjosino/on_audio_edit/methods/edits/OnArtworkEdit10.kt b/android/src/main/kotlin/com/lucasjosino/on_audio_edit/methods/edits/OnArtworkEdit10.kt index 7f3e798..babe87d 100644 --- a/android/src/main/kotlin/com/lucasjosino/on_audio_edit/methods/edits/OnArtworkEdit10.kt +++ b/android/src/main/kotlin/com/lucasjosino/on_audio_edit/methods/edits/OnArtworkEdit10.kt @@ -31,17 +31,23 @@ class OnArtworkEdit10(private val context: Context, private val activity: Activi // Main parameters private val channelError = "on_audio_error" private val onSharedPrefKeyUriCode = "on_audio_edit_uri" + private var searchInsideFolders: Boolean = false private lateinit var uri: Uri private lateinit var call: MethodCall // Check if plugin already has uri. - private fun getUri() : String? = activity.getSharedPreferences("on_audio_edit", - Context.MODE_PRIVATE).getString(onSharedPrefKeyUriCode, "") + private fun getUri(): String? = activity.getSharedPreferences( + "on_audio_edit", + Context.MODE_PRIVATE + ).getString(onSharedPrefKeyUriCode, "") // fun editArtwork(result: MethodChannel.Result, call: MethodCall, uri: Uri?) { // This will write in file removing all unnecessary info. TagOptionSingleton.getInstance().isId3v2PaddingWillShorten = true + + // + this.searchInsideFolders = call.argument("searchInsideFolders")!! this.uri = uri ?: Uri.parse(call.argument("imagePath")!!) this.call = call @@ -56,7 +62,7 @@ class OnArtworkEdit10(private val context: Context, private val activity: Activi } @Suppress("BlockingMethodInNonBlockingContext") - private suspend fun doEverythingInBackground() : Boolean = withContext(Dispatchers.IO) { + private suspend fun doEverythingInBackground(): Boolean = withContext(Dispatchers.IO) { // Get all information from Dart. val data = call.argument("data")!! val type = checkArtworkFormat(call.argument("type")!!) @@ -66,16 +72,35 @@ class OnArtworkEdit10(private val context: Context, private val activity: Activi // val internalData = File(data) - // Get and check if uri is null. - val uriFolder = Uri.parse(getUri()) ?: return@withContext false + // At this point, we already requested the 'special' path to user folder. + // If not, return false and a warn. + if (getUri() == null) { + Log.w("on_audio_exception", "Uri to folder path doesn't exist!") + return@withContext false + } + + // Get the 'extra' uri. + val uriFolder: Uri = Uri.parse(getUri()) // Use DocumentFile to navigate and find specific data inside specific folder. // We need this, cuz google blocked some access in Android >= 10/Q - var pUri: Uri = Uri.parse("") // This can be null but, we use inside try / catch - val dFile = DocumentFile.fromTreeUri(context, uriFolder) - // [findFile] will give a slow performance, so, we use Kotlin Coroutines and "doEverythingInBackground" - val fileList = dFile!!.findFile(internalData.name) - if (fileList != null) pUri = fileList.uri + val pUri: Uri + val dFile = DocumentFile.fromTreeUri(context, uriFolder) ?: return@withContext false + // [getFile] will give a slow performance, so, we use Kotlin Coroutines and "doEverythingInBackground" + val file = getFile(dFile, internalData)?.uri + + // + if (file == null) { + Log.w( + "on_audio_exception", + "File: $data not found!\n " + + "Call [resetComplexPermission] and let the user choose the \"Root\" folder." + ) + return@withContext false + } + + // After checking that [file] is valid, keep the editing. + pUri = file // Temp file just to write(rewrite) file path. Produce the same result as "scan" val temp = File.createTempFile("tmp-media", '.' + Utils.getExtension(internalData)) @@ -97,7 +122,7 @@ class OnArtworkEdit10(private val context: Context, private val activity: Activi artwork.pictureType = PictureTypes.DEFAULT_ID artwork.mimeType = type artwork.description = description - artwork.height = size ; artwork.width = size + artwork.height = size; artwork.width = size // audioTag.setField(artwork) @@ -105,7 +130,9 @@ class OnArtworkEdit10(private val context: Context, private val activity: Activi try { AudioFileIO.write(audioFile) - } catch (e: Exception) { Log.i(channelError, e.toString()) } + } catch (e: Exception) { + Log.i(channelError, "$e") + } // Start setup to write in folder // Until this moment we only write inside audio file, but we need tell android that this file has some change. @@ -129,17 +156,11 @@ class OnArtworkEdit10(private val context: Context, private val activity: Activi } } } catch (e: Exception) { - Log.i("on_audio_exception", e.toString()) - temp.delete() - return@withContext false + Log.i("on_audio_exception", "$e") } catch (f: FileNotFoundException) { - Log.i("on_audio_FileNotFound", f.toString()) - temp.delete() - return@withContext false + Log.i("on_audio_FileNotFound", "$f") } catch (io: IOException) { - Log.i("on_audio_IOException", io.toString()) - temp.delete() - return@withContext false + Log.i("on_audio_IOException", "$io") } // Delete temp folder. @@ -149,7 +170,7 @@ class OnArtworkEdit10(private val context: Context, private val activity: Activi @Suppress("DEPRECATION") // - private fun findImage(uri: Uri) : String { + private fun findImage(uri: Uri): String { val projection = arrayOf(MediaStore.Images.Media.DATA) val cursor = context.contentResolver.query(uri, projection, null, null, null) @@ -162,4 +183,18 @@ class OnArtworkEdit10(private val context: Context, private val activity: Activi cursor?.close() return imageData } + + private fun getFile(directory: DocumentFile, specificFile: File): DocumentFile? { + val files = directory.listFiles() + for (file in files) { + val data: DocumentFile? = if (file.isDirectory) { + // If [searchInsideFolders] is true, we keep searching, if not, no file was found. + if (searchInsideFolders) getFile(file, specificFile) else return null + } else { + if (file.name == specificFile.name) file else null + } + if (data != null) return data + } + return null + } } \ No newline at end of file diff --git a/android/src/main/kotlin/com/lucasjosino/on_audio_edit/methods/edits/OnAudioEdit10.kt b/android/src/main/kotlin/com/lucasjosino/on_audio_edit/methods/edits/OnAudioEdit10.kt index 63a6ae3..2e76f78 100644 --- a/android/src/main/kotlin/com/lucasjosino/on_audio_edit/methods/edits/OnAudioEdit10.kt +++ b/android/src/main/kotlin/com/lucasjosino/on_audio_edit/methods/edits/OnAudioEdit10.kt @@ -114,7 +114,7 @@ class OnAudioEdit10(private val context: Context, private val activity: Activity // Setting tags for (info in getTagsAndInfo) { // If value is null, ignore. - val value = info.value.toString() + val value = "${info.value}" if (value.isNotEmpty()) audioTag.setField(info.key, value) } audioFile.file = temp @@ -122,7 +122,7 @@ class OnAudioEdit10(private val context: Context, private val activity: Activity try { AudioFileIO.write(audioFile) } catch (e: Exception) { - Log.i(channelError, e.toString()) + Log.i(channelError, "$e") } // Start setup to write in folder @@ -140,17 +140,11 @@ class OnAudioEdit10(private val context: Context, private val activity: Activity } } } catch (e: Exception) { - Log.i("on_audio_exception", e.toString()) - temp.delete() - return@withContext false + Log.i("on_audio_exception", "$e") } catch (f: FileNotFoundException) { - Log.i("on_audio_FileNotFound", f.toString()) - temp.delete() - return@withContext false + Log.i("on_audio_FileNotFound", "$f") } catch (io: IOException) { - Log.i("on_audio_IOException", io.toString()) - temp.delete() - return@withContext false + Log.i("on_audio_IOException", "$io") } // Delete temp folder. diff --git a/android/src/main/kotlin/com/lucasjosino/on_audio_edit/methods/read/OnAudioRead.kt b/android/src/main/kotlin/com/lucasjosino/on_audio_edit/methods/read/OnAudioRead.kt index 5c1842b..c5651c2 100644 --- a/android/src/main/kotlin/com/lucasjosino/on_audio_edit/methods/read/OnAudioRead.kt +++ b/android/src/main/kotlin/com/lucasjosino/on_audio_edit/methods/read/OnAudioRead.kt @@ -1,5 +1,6 @@ package com.lucasjosino.on_audio_edit.methods.read +import com.lucasjosino.on_audio_edit.extensions.checkFlac import com.lucasjosino.on_audio_edit.extensions.tryInt import com.lucasjosino.on_audio_edit.types.checkTag import com.lucasjosino.on_audio_edit.utils.checkAndGetExtraInfo @@ -25,7 +26,7 @@ class OnAudioRead { // Getting all tags val tagsData: MutableMap = HashMap() - for (tag in getAllProjection()) { + for (tag in getAllProjection().checkFlac(data)) { val value = audioTag.getValue(tag, 0) if (!value.isNullOrEmpty()) { tagsData[tag.name] = value.tryInt(tag.ordinal) @@ -56,7 +57,7 @@ class OnAudioRead { // Getting all tags val tagsData: MutableMap = HashMap() - for (tag in getAllProjection()) { + for (tag in getAllProjection().checkFlac(pathData)) { val value = audioTag.getValue(tag, 0) if (!value.isNullOrEmpty()) { tagsData[tag.name] = value.tryInt(tag.ordinal) @@ -133,4 +134,6 @@ class OnAudioRead { // Sending to Dart result.success(tagsData) } + + } \ No newline at end of file diff --git a/android/src/main/kotlin/com/lucasjosino/on_audio_edit/utils/OnExtraInfo.kt b/android/src/main/kotlin/com/lucasjosino/on_audio_edit/utils/OnExtraInfo.kt index 33ce960..ea633fc 100644 --- a/android/src/main/kotlin/com/lucasjosino/on_audio_edit/utils/OnExtraInfo.kt +++ b/android/src/main/kotlin/com/lucasjosino/on_audio_edit/utils/OnExtraInfo.kt @@ -11,7 +11,7 @@ fun checkAndGetExtraInfo(audioFile: AudioFile) : MutableMap { extraInfo["CHANNELS"] = audioFile.audioHeader.channels extraInfo["TYPE"] = audioFile.audioHeader.encodingType extraInfo["LENGTH"] = audioFile.file.length() - extraInfo["FIRST_ARTWORK"] = audioFile.tag.firstArtwork.binaryData + extraInfo["FIRST_ARTWORK"] = audioFile.tag?.firstArtwork?.binaryData return extraInfo } diff --git a/android/src/main/kotlin/com/lucasjosino/on_audio_edit/utils/OnTagsProjections.kt b/android/src/main/kotlin/com/lucasjosino/on_audio_edit/utils/OnTagsProjections.kt index 4b5fe33..50815cb 100644 --- a/android/src/main/kotlin/com/lucasjosino/on_audio_edit/utils/OnTagsProjections.kt +++ b/android/src/main/kotlin/com/lucasjosino/on_audio_edit/utils/OnTagsProjections.kt @@ -2,9 +2,9 @@ package com.lucasjosino.on_audio_edit.utils import org.jaudiotagger.tag.FieldKey -fun getAllProjection() : Array = allProjection +fun getAllProjection() : ArrayList = allProjection -private var allProjection = arrayOf( +private var allProjection = arrayListOf( FieldKey.ACOUSTID_FINGERPRINT, // 0 FieldKey.ACOUSTID_ID, // 1 FieldKey.ALBUM, // 2 diff --git a/lib/details/models/audio_model.dart b/lib/details/models/audio_model.dart index d3f7e20..ed37ea4 100644 --- a/lib/details/models/audio_model.dart +++ b/lib/details/models/audio_model.dart @@ -94,10 +94,6 @@ class AudioModel { /// Return song [grouping] String? get grouping => _info["GROUPING"]; - /// Deprecated after [1.2.0] - @Deprecated("This method will be removed soon") - int? get id => _info["ID"]; - /// Return song [isrc] String? get isrc => _info["ISRC"]; @@ -272,7 +268,9 @@ class AudioModel { @override String toString() { var tmpInfo = _info; - tmpInfo.update("FIRST_ARTWORK", (value) => "${value.length} (Bytes)"); + if (tmpInfo["FIRST_ARTWORK"] != null) { + tmpInfo.update("FIRST_ARTWORK", (value) => "${value.length} (Bytes)"); + } return tmpInfo.toString(); } } diff --git a/lib/details/models/audios_model.dart b/lib/details/models/audios_model.dart deleted file mode 100644 index cb0275f..0000000 --- a/lib/details/models/audios_model.dart +++ /dev/null @@ -1,112 +0,0 @@ -part of on_audio_edit; - -// Deprecated after [1.1.0] -@Deprecated('Use [AudioModel] instead.') -class AudiosTagModel { - AudiosTagModel(this._info); - - /// The type dynamic is used for both but, the map is always based in [String, dynamic] - final Map _info; - - /// Return song [albumArtist] - String get albumArtist => _info["ALBUM_ARTIST"]; - - /// Return song [artist] - String get artist => _info["ARTIST"]; - - /// Return song [artists] - String get artists => _info["ARTISTS"]; - - /// Return song [beatsPerMinutes] - String get beatsPerMinutes => _info["BEATS_PER_MINUTE"]; - - /// Return song [bitrate] - String get bitrate => _info["BITRATE"]; - - /// Return song [channels] - String get channels => _info["CHANNELS"]; - - /// Return song [composer] - String get composer => _info["COMPOSER"]; - - /// Return song [country] - String get country => _info["COUNTRY"]; - - /// Return song [country] - String get coverArt => _info["COVER_ART"]; - - /// Return song [country] - String get firstArtwork => _info["FIRST_ARTWORK"]; - - /// Return song [format] - String get format => _info["FORMAT"]; - - /// Return song [genre] - String get genre => _info["GENRE"]; - - /// Return song [id] - String get id => _info["ID"]; - - /// Return song [isrc] - String get isrc => _info["ISRC"]; - - /// Return song [key] - String get key => _info["KEY"]; - - /// Return song [language] - String get language => _info["LANGUAGE"]; - - /// Return song [length] - String get length => _info["LENGTH"]; - - /// Return song [lyrics] - String get lyrics => _info["LYRICS"]; - - /// Return song [originalAlbum] - String get originalAlbum => _info["ORIGINAL_ALBUM"]; - - /// Return song [originalAlbum] - String get originalArtist => _info["ORIGINAL_ARTIST"]; - - /// Return song [originalLyricist] - String get originalLyricist => _info["ORIGINAL_LYRICIST"]; - - /// Return song [originalYear] - String get originalYear => _info["ORIGINAL_YEAR"]; - - /// Return song [producer] - String get producer => _info["PRODUCER"]; - - /// Return song [quality] - String get quality => _info["QUALITY"]; - - /// Return song [rating] - String get rating => _info["RATING"]; - - /// Return song [recordLabel] - String get recordLabel => _info["RECORD_LABEL"]; - - /// Return song [sampleRate] - String get sampleRate => _info["SAMPLE_RATE"]; - - /// Return song [subTitle] - String get subTitle => _info["SUBTITLE"]; - - /// Return song [tags] - String get tags => _info["TAGS"]; - - /// Return song [tempo] - String get tempo => _info["TEMPO"]; - - /// Return song [title] - String get title => _info["TITLE"]; - - /// Return song [track] - String get track => _info["TRACK"]; - - /// Return song [type] - String get type => _info["TYPE"]; - - /// Return song [year] - String get year => _info["YEAR"]; -} diff --git a/lib/details/on_audio_edit_controller.dart b/lib/details/on_audio_edit_controller.dart index d8e2931..9962733 100644 --- a/lib/details/on_audio_edit_controller.dart +++ b/lib/details/on_audio_edit_controller.dart @@ -30,7 +30,7 @@ class OnAudioEdit { case TagType.TRACK_LENGTH: case TagType.SAMPLE_RATE: case TagType.ENCODING_TYPE: - log('Cannot modifiy [$tag]. Ignoring request'); + log('Cannot modifiy [$tag]. Removing tag'); break; default: break; @@ -63,15 +63,6 @@ class OnAudioEdit { return AudioModel(resultReadAudio); } - /// Deprecated after [1.2.0] - @Deprecated('Use [readAudio] instead') - Future readAllAudio( - String data, { - bool searchInsideFolders = false, - }) async { - return readAudio(data); - } - /// Used to return multiples songs info. /// /// Parameters: @@ -176,8 +167,8 @@ class OnAudioEdit { /// /// * [data] is used for find specific audio data. /// * [tags] is used to define what tags and values you want edit. - /// * [searchInsideFolders] is used for find specific audio data even inside - /// the folders. **(Only required when using Android 10 or above)** + /// * [searchInsideFolders] is used for find specific audio data inside the + /// folders. **(Only required when using Android 10 or above)** /// /// Usage: /// @@ -208,7 +199,7 @@ class OnAudioEdit { Future editAudio( String data, Map tags, { - bool searchInsideFolders = false, + bool? searchInsideFolders, }) async { Map finalTags = {}; tags.forEach((key, value) { @@ -218,7 +209,7 @@ class OnAudioEdit { final bool resultEditAudio = await _channel.invokeMethod("editAudio", { "data": data, "tags": finalTags, - "searchInsideFolders": searchInsideFolders, + "searchInsideFolders": searchInsideFolders ?? false, }); return resultEditAudio; } @@ -303,6 +294,8 @@ class OnAudioEdit { /// * [format] is used to define image type: [PNG] or [JPEG]. /// * [size] is used to define image quality. /// * [description] is used to define artwork description. + /// * [searchInsideFolders] is used for find specific audio data inside the + /// folders. **(Only required when using Android 10 or above)** /// /// Important: /// @@ -314,13 +307,14 @@ class OnAudioEdit { /// * If [size] is null, will be set to [24]. /// * If [description] is null, will be set to ["artwork"]. Future editArtwork( - String data, [ + String data, { bool? openFilePicker, String? imagePath, ArtworkFormat? format, int? size, String? description, - ]) async { + bool? searchInsideFolders, + }) async { assert( openFilePicker == false || imagePath == null, "Cannot change artwork image without image.\n" @@ -331,7 +325,8 @@ class OnAudioEdit { "size": size ?? 24, "description": description ?? "artwork", "openFilePicker": openFilePicker ?? true, - "imagePath": imagePath + "imagePath": imagePath, + "searchInsideFolders": searchInsideFolders ?? false, }); return resultEditArt; } @@ -444,12 +439,6 @@ class OnAudioEdit { return resultReset; } - /// Deprecated after [1.2.0]. - @Deprecated('Use [getImage] instead.') - Future getImagePath({ArtworkFormat? format, int? quality}) async { - return getImage(format: format, quality: quality); - } - /// Used to open image folder to user select image and return this [ImageModel]. Future getImage({ArtworkFormat? format, int? quality}) async { final Map resultImage = await _channel.invokeMethod("getImagePath", { @@ -462,8 +451,15 @@ class OnAudioEdit { /// Used to return the uri(if exist) from the folder selected from user. /// This uri will be avalible after [requestComplexPermission] or [editAudio] when /// using Android 10 or above. - Future getUri() async { + Future getUri({bool originalPath = false}) async { final String? resultUri = await _channel.invokeMethod('getUri'); + if (!originalPath) { + resultUri?.replaceAll( + "content://com.android.externalstorage.documents/tree", + "", + ); + resultUri?.replaceAll("%3A", "/"); + } return resultUri; } diff --git a/lib/on_audio_edit.dart b/lib/on_audio_edit.dart index 3706ef9..335aee9 100644 --- a/lib/on_audio_edit.dart +++ b/lib/on_audio_edit.dart @@ -28,6 +28,5 @@ part 'details/types/tag_type.dart'; part 'details/types/artwork_type.dart'; // Models -part 'details/models/audios_model.dart'; part 'details/models/audio_model.dart'; part 'details/models/image_model.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 55f3730..10f6c94 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: on_audio_edit description: Flutter Plugin used to edit and read audios/songs infos/tags [Mp3, OggVorbis, Wav, etc...]. -version: 1.3.0 +version: 1.4.0 homepage: https://github.com/LucasPJS/on_audio_edit # pub.dev: https://pub.dev/packages/on_audio_query # ========