Merge pull request #353 from KryptKode/feat/edit-metadata
Implement editing audio tags for specific media files
This commit is contained in:
commit
ab53bb009f
15 changed files with 319 additions and 81 deletions
|
@ -70,6 +70,9 @@ dependencies {
|
|||
implementation 'com.google.android.material:material:1.4.0'
|
||||
implementation 'com.airbnb.android:lottie:3.6.1'
|
||||
|
||||
// higher versions of jaudiotagger not compatible with <= API 25 devices
|
||||
// https://bitbucket.org/ijabz/jaudiotagger/issues/149/some-nio-classes-are-unavailable-while
|
||||
implementation "net.jthink:jaudiotagger:2.2.5"
|
||||
kapt "androidx.room:room-compiler:2.3.0"
|
||||
implementation "androidx.room:room-runtime:2.3.0"
|
||||
annotationProcessor "androidx.room:room-compiler:2.3.0"
|
||||
|
|
|
@ -359,6 +359,11 @@ class MainActivity : SimpleActivity() {
|
|||
playlists_fragment_holder?.setupFragment(this)
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun tracksUpdated(event: Events.RefreshTracks) {
|
||||
tracks_fragment_holder?.setupFragment(this)
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun trackDeleted(event: Events.TrackDeleted) {
|
||||
getAllFragments().forEach {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.simplemobiletools.musicplayer.adapters
|
||||
|
||||
import android.content.Intent
|
||||
import android.view.Menu
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -18,18 +19,18 @@ import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
|||
import com.simplemobiletools.commons.views.MyRecyclerView
|
||||
import com.simplemobiletools.musicplayer.R
|
||||
import com.simplemobiletools.musicplayer.activities.SimpleActivity
|
||||
import com.simplemobiletools.musicplayer.extensions.addTracksToPlaylist
|
||||
import com.simplemobiletools.musicplayer.extensions.addTracksToQueue
|
||||
import com.simplemobiletools.musicplayer.extensions.deleteTracks
|
||||
import com.simplemobiletools.musicplayer.extensions.getAlbumTracksSync
|
||||
import com.simplemobiletools.musicplayer.models.Album
|
||||
import com.simplemobiletools.musicplayer.models.AlbumSection
|
||||
import com.simplemobiletools.musicplayer.models.ListItem
|
||||
import com.simplemobiletools.musicplayer.models.Track
|
||||
import com.simplemobiletools.musicplayer.dialogs.EditDialog
|
||||
import com.simplemobiletools.musicplayer.extensions.*
|
||||
import com.simplemobiletools.musicplayer.helpers.EDIT
|
||||
import com.simplemobiletools.musicplayer.helpers.EDITED_TRACK
|
||||
import com.simplemobiletools.musicplayer.helpers.REFRESH_LIST
|
||||
import com.simplemobiletools.musicplayer.models.*
|
||||
import com.simplemobiletools.musicplayer.services.MusicService
|
||||
import kotlinx.android.synthetic.main.item_album.view.*
|
||||
import kotlinx.android.synthetic.main.item_section.view.*
|
||||
import kotlinx.android.synthetic.main.item_track.view.*
|
||||
import java.util.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
// we show both albums and individual tracks here
|
||||
class AlbumsTracksAdapter(
|
||||
|
@ -95,6 +96,7 @@ class AlbumsTracksAdapter(
|
|||
R.id.cab_add_to_playlist -> addToPlaylist()
|
||||
R.id.cab_add_to_queue -> addToQueue()
|
||||
R.id.cab_delete -> askConfirmDelete()
|
||||
R.id.cab_rename -> displayEditDialog()
|
||||
R.id.cab_select_all -> selectAll()
|
||||
}
|
||||
}
|
||||
|
@ -235,4 +237,26 @@ class AlbumsTracksAdapter(
|
|||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun displayEditDialog() {
|
||||
getSelectedTracks().firstOrNull()?.let { selectedTrack ->
|
||||
EditDialog(activity as SimpleActivity, selectedTrack) { track ->
|
||||
val trackIndex = items.indexOfFirst { (it as? Track)?.mediaStoreId == track.mediaStoreId }
|
||||
if (trackIndex != -1) {
|
||||
items[trackIndex] = track
|
||||
notifyItemChanged(trackIndex)
|
||||
finishActMode()
|
||||
}
|
||||
if (track.mediaStoreId == MusicService.mCurrTrack?.mediaStoreId) {
|
||||
Intent(activity, MusicService::class.java).apply {
|
||||
putExtra(EDITED_TRACK, track)
|
||||
action = EDIT
|
||||
activity.startService(this)
|
||||
}
|
||||
}
|
||||
activity.sendIntent(REFRESH_LIST)
|
||||
EventBus.getDefault().post(Events.RefreshTracks())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.simplemobiletools.musicplayer.adapters
|
||||
|
||||
import android.content.ContentUris
|
||||
import android.content.Intent
|
||||
import android.provider.MediaStore
|
||||
import android.view.Menu
|
||||
import android.view.View
|
||||
|
@ -18,12 +19,15 @@ import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
|||
import com.simplemobiletools.commons.views.MyRecyclerView
|
||||
import com.simplemobiletools.musicplayer.R
|
||||
import com.simplemobiletools.musicplayer.activities.SimpleActivity
|
||||
import com.simplemobiletools.musicplayer.extensions.addTracksToPlaylist
|
||||
import com.simplemobiletools.musicplayer.extensions.addTracksToQueue
|
||||
import com.simplemobiletools.musicplayer.extensions.deleteTracks
|
||||
import com.simplemobiletools.musicplayer.extensions.tracksDAO
|
||||
import com.simplemobiletools.musicplayer.dialogs.EditDialog
|
||||
import com.simplemobiletools.musicplayer.extensions.*
|
||||
import com.simplemobiletools.musicplayer.helpers.EDIT
|
||||
import com.simplemobiletools.musicplayer.helpers.EDITED_TRACK
|
||||
import com.simplemobiletools.musicplayer.helpers.REFRESH_LIST
|
||||
import com.simplemobiletools.musicplayer.helpers.TagHelper
|
||||
import com.simplemobiletools.musicplayer.models.Events
|
||||
import com.simplemobiletools.musicplayer.models.Track
|
||||
import com.simplemobiletools.musicplayer.services.MusicService
|
||||
import kotlinx.android.synthetic.main.item_track.view.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.util.*
|
||||
|
@ -32,6 +36,7 @@ class TracksAdapter(
|
|||
activity: SimpleActivity, var tracks: ArrayList<Track>, val isPlaylistContent: Boolean, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit
|
||||
) : MyRecyclerViewAdapter(activity, recyclerView, null, itemClick), RecyclerViewFastScroller.OnPopupTextUpdate {
|
||||
|
||||
private val tagHelper by lazy { TagHelper(activity) }
|
||||
private var textToHighlight = ""
|
||||
private val placeholder = resources.getColoredDrawableWithColor(R.drawable.ic_headset, textColor)
|
||||
private val cornerRadius = resources.getDimension(R.dimen.rounded_corner_radius_small).toInt()
|
||||
|
@ -57,6 +62,8 @@ class TracksAdapter(
|
|||
override fun prepareActionMode(menu: Menu) {
|
||||
menu.apply {
|
||||
findItem(R.id.cab_remove_from_playlist).isVisible = isPlaylistContent
|
||||
findItem(R.id.cab_rename).isVisible =
|
||||
isOneItemSelected() && getSelectedTracks().firstOrNull()?.let { !it.path.startsWith("content://") && tagHelper.isEditTagSupported(it) } == true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,6 +76,7 @@ class TracksAdapter(
|
|||
R.id.cab_add_to_playlist -> addToPlaylist()
|
||||
R.id.cab_add_to_queue -> addToQueue()
|
||||
R.id.cab_properties -> showProperties()
|
||||
R.id.cab_rename -> displayEditDialog()
|
||||
R.id.cab_remove_from_playlist -> removeFromPlaylist()
|
||||
R.id.cab_delete -> askConfirmDelete()
|
||||
R.id.cab_select_all -> selectAll()
|
||||
|
@ -169,7 +177,7 @@ class TracksAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getSelectedTracks(): List<Track> = tracks.filter { selectedKeys.contains(it.hashCode()) }.toList()
|
||||
private fun getSelectedTracks(): List<Track> = tracks.filter { selectedKeys.contains(it.hashCode()) }
|
||||
|
||||
fun updateItems(newItems: ArrayList<Track>, highlightText: String = "", forceUpdate: Boolean = false) {
|
||||
if (forceUpdate || newItems.hashCode() != tracks.hashCode()) {
|
||||
|
@ -209,4 +217,27 @@ class TracksAdapter(
|
|||
}
|
||||
|
||||
override fun onChange(position: Int) = tracks.getOrNull(position)?.getBubbleText() ?: ""
|
||||
|
||||
private fun displayEditDialog() {
|
||||
getSelectedTracks().firstOrNull()?.let { selectedTrack ->
|
||||
EditDialog(activity as SimpleActivity, selectedTrack) { track ->
|
||||
val trackIndex = tracks.indexOfFirst { it.mediaStoreId == track.mediaStoreId }
|
||||
tracks[trackIndex] = track
|
||||
if (trackIndex != -1) {
|
||||
tracks[trackIndex] = track
|
||||
notifyItemChanged(trackIndex)
|
||||
finishActMode()
|
||||
}
|
||||
if (track.mediaStoreId == MusicService.mCurrTrack?.mediaStoreId) {
|
||||
Intent(activity, MusicService::class.java).apply {
|
||||
putExtra(EDITED_TRACK, track)
|
||||
action = EDIT
|
||||
activity.startService(this)
|
||||
}
|
||||
}
|
||||
activity.sendIntent(REFRESH_LIST)
|
||||
EventBus.getDefault().post(Events.RefreshTracks())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.simplemobiletools.musicplayer.adapters
|
||||
|
||||
import android.content.Intent
|
||||
import android.view.Menu
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -18,15 +19,24 @@ import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
|||
import com.simplemobiletools.commons.views.MyRecyclerView
|
||||
import com.simplemobiletools.musicplayer.R
|
||||
import com.simplemobiletools.musicplayer.activities.SimpleActivity
|
||||
import com.simplemobiletools.musicplayer.dialogs.EditDialog
|
||||
import com.simplemobiletools.musicplayer.extensions.addTracksToPlaylist
|
||||
import com.simplemobiletools.musicplayer.extensions.addTracksToQueue
|
||||
import com.simplemobiletools.musicplayer.extensions.deleteTracks
|
||||
import com.simplemobiletools.musicplayer.extensions.sendIntent
|
||||
import com.simplemobiletools.musicplayer.helpers.EDIT
|
||||
import com.simplemobiletools.musicplayer.helpers.EDITED_TRACK
|
||||
import com.simplemobiletools.musicplayer.helpers.REFRESH_LIST
|
||||
import com.simplemobiletools.musicplayer.helpers.TagHelper
|
||||
import com.simplemobiletools.musicplayer.models.AlbumHeader
|
||||
import com.simplemobiletools.musicplayer.models.Events
|
||||
import com.simplemobiletools.musicplayer.models.ListItem
|
||||
import com.simplemobiletools.musicplayer.models.Track
|
||||
import com.simplemobiletools.musicplayer.services.MusicService
|
||||
import kotlinx.android.synthetic.main.item_album_header.view.*
|
||||
import kotlinx.android.synthetic.main.item_track.view.*
|
||||
import java.util.*
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
class TracksHeaderAdapter(activity: SimpleActivity, val items: ArrayList<ListItem>, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit) :
|
||||
MyRecyclerViewAdapter(activity, recyclerView, null, itemClick), RecyclerViewFastScroller.OnPopupTextUpdate {
|
||||
|
@ -36,6 +46,7 @@ class TracksHeaderAdapter(activity: SimpleActivity, val items: ArrayList<ListIte
|
|||
|
||||
private val placeholder = resources.getColoredDrawableWithColor(R.drawable.ic_headset, textColor)
|
||||
private val cornerRadius = resources.getDimension(R.dimen.rounded_corner_radius_big).toInt()
|
||||
private val tagHelper by lazy { TagHelper(activity) }
|
||||
|
||||
init {
|
||||
setupDragListener(true)
|
||||
|
@ -73,7 +84,13 @@ class TracksHeaderAdapter(activity: SimpleActivity, val items: ArrayList<ListIte
|
|||
}
|
||||
}
|
||||
|
||||
override fun prepareActionMode(menu: Menu) {}
|
||||
override fun prepareActionMode(menu: Menu) {
|
||||
menu.apply {
|
||||
val oneItemsSelected = isOneItemSelected()
|
||||
val selected = getSelectedTracks().firstOrNull()?.let { !it.path.startsWith("content://") && tagHelper.isEditTagSupported(it) } == true
|
||||
findItem(R.id.cab_rename).isVisible = oneItemsSelected && selected
|
||||
}
|
||||
}
|
||||
|
||||
override fun actionItemPressed(id: Int) {
|
||||
if (selectedKeys.isEmpty()) {
|
||||
|
@ -84,6 +101,7 @@ class TracksHeaderAdapter(activity: SimpleActivity, val items: ArrayList<ListIte
|
|||
R.id.cab_add_to_playlist -> addToPlaylist()
|
||||
R.id.cab_add_to_queue -> addToQueue()
|
||||
R.id.cab_delete -> askConfirmDelete()
|
||||
R.id.cab_rename -> displayEditDialog()
|
||||
R.id.cab_select_all -> selectAll()
|
||||
}
|
||||
}
|
||||
|
@ -192,4 +210,26 @@ class TracksHeaderAdapter(activity: SimpleActivity, val items: ArrayList<ListIte
|
|||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun displayEditDialog() {
|
||||
getSelectedTracks().firstOrNull()?.let { selectedTrack ->
|
||||
EditDialog(activity as SimpleActivity, selectedTrack) { track ->
|
||||
val trackIndex = items.indexOfFirst { (it as? Track)?.mediaStoreId == track.mediaStoreId }
|
||||
if (trackIndex != -1) {
|
||||
items[trackIndex] = track
|
||||
notifyItemChanged(trackIndex)
|
||||
finishActMode()
|
||||
}
|
||||
if (track.mediaStoreId == MusicService.mCurrTrack?.mediaStoreId) {
|
||||
Intent(activity, MusicService::class.java).apply {
|
||||
putExtra(EDITED_TRACK, track)
|
||||
action = EDIT
|
||||
activity.startService(this)
|
||||
}
|
||||
}
|
||||
activity.sendIntent(REFRESH_LIST)
|
||||
EventBus.getDefault().post(Events.RefreshTracks())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,93 +1,110 @@
|
|||
package com.simplemobiletools.musicplayer.dialogs
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.provider.MediaStore.Audio
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.simplemobiletools.commons.activities.BaseSimpleActivity
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
import com.simplemobiletools.commons.helpers.isRPlus
|
||||
import com.simplemobiletools.musicplayer.R
|
||||
import com.simplemobiletools.musicplayer.extensions.tracksDAO
|
||||
import com.simplemobiletools.musicplayer.helpers.TagHelper
|
||||
import com.simplemobiletools.musicplayer.models.Track
|
||||
import kotlinx.android.synthetic.main.dialog_rename_song.*
|
||||
import kotlinx.android.synthetic.main.dialog_rename_song.view.*
|
||||
|
||||
class EditDialog(val activity: BaseSimpleActivity, val song: Track, val callback: (Track) -> Unit) {
|
||||
class EditDialog(val activity: BaseSimpleActivity, val track: Track, val callback: (Track) -> Unit) {
|
||||
private val tagHelper by lazy { TagHelper(activity) }
|
||||
|
||||
init {
|
||||
val view = activity.layoutInflater.inflate(R.layout.dialog_rename_song, null).apply {
|
||||
song_title.setText(song.title)
|
||||
song_artist.setText(song.artist)
|
||||
|
||||
val filename = song.path.getFilenameFromPath()
|
||||
song_title.setText(track.title)
|
||||
song_artist.setText(track.artist)
|
||||
song_album.setText(track.album)
|
||||
val filename = track.path.getFilenameFromPath()
|
||||
file_name.setText(filename.substring(0, filename.lastIndexOf(".")))
|
||||
extension.setText(song.path.getFilenameExtension())
|
||||
extension.setText(track.path.getFilenameExtension())
|
||||
if (isRPlus()) {
|
||||
arrayOf(file_name_label, file_name, extension_label, extension).forEach { it.beGone() }
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog.Builder(activity)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.create().apply {
|
||||
activity.setupDialogStuff(view, this, R.string.rename_song) {
|
||||
showKeyboard(song_title)
|
||||
getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
||||
val newTitle = view.song_title.value
|
||||
val newArtist = view.song_artist.value
|
||||
val newFilename = view.file_name.value
|
||||
val newFileExtension = view.extension.value
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.create().apply {
|
||||
activity.setupDialogStuff(view, this, R.string.rename_song) {
|
||||
showKeyboard(song_title)
|
||||
getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
||||
val newTitle = view.song_title.value
|
||||
val newArtist = view.song_artist.value
|
||||
val newAlbum = view.song_album.value
|
||||
val newFilename = view.file_name.value
|
||||
val newFileExtension = view.extension.value
|
||||
|
||||
if (newTitle.isEmpty() || newArtist.isEmpty() || newFilename.isEmpty() || newFileExtension.isEmpty()) {
|
||||
activity.toast(R.string.rename_song_empty)
|
||||
return@setOnClickListener
|
||||
}
|
||||
if (newTitle.isEmpty() || newArtist.isEmpty() || newFilename.isEmpty() || newFileExtension.isEmpty()) {
|
||||
activity.toast(R.string.rename_song_empty)
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
song.artist = newArtist
|
||||
song.title = newTitle
|
||||
updateContentResolver(context, song.mediaStoreId, newTitle, newArtist)
|
||||
|
||||
val oldPath = song.path
|
||||
val newPath = "${oldPath.getParentPath()}/$newFilename.$newFileExtension"
|
||||
if (oldPath == newPath) {
|
||||
storeEditedSong(song, oldPath, newPath)
|
||||
callback(song)
|
||||
dismiss()
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
activity.renameFile(oldPath, newPath) {
|
||||
if (it) {
|
||||
storeEditedSong(song, oldPath, newPath)
|
||||
song.path = newPath
|
||||
callback(song)
|
||||
} else {
|
||||
activity.toast(R.string.rename_song_error)
|
||||
if (track.title != newTitle || track.artist != newArtist || track.album != newAlbum) {
|
||||
updateContentResolver(track, newArtist, newTitle, newAlbum) {
|
||||
track.artist = newArtist
|
||||
track.title = newTitle
|
||||
track.album = newAlbum
|
||||
val oldPath = track.path
|
||||
val newPath = "${oldPath.getParentPath()}/$newFilename.$newFileExtension"
|
||||
if (oldPath == newPath) {
|
||||
storeEditedSong(track, oldPath, newPath)
|
||||
callback(track)
|
||||
dismiss()
|
||||
return@updateContentResolver
|
||||
}
|
||||
|
||||
if (!isRPlus()) {
|
||||
activity.renameFile(oldPath, newPath) {
|
||||
if (it) {
|
||||
storeEditedSong(track, oldPath, newPath)
|
||||
track.path = newPath
|
||||
callback(track)
|
||||
} else {
|
||||
activity.toast(R.string.rename_song_error)
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
} else {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun storeEditedSong(song: Track, oldPath: String, newPath: String) {
|
||||
private fun storeEditedSong(track: Track, oldPath: String, newPath: String) {
|
||||
ensureBackgroundThread {
|
||||
try {
|
||||
activity.tracksDAO.updateSongInfo(newPath, song.artist, song.title, oldPath)
|
||||
activity.tracksDAO.updateSongInfo(newPath, track.artist, track.title, oldPath)
|
||||
} catch (e: Exception) {
|
||||
activity.showErrorToast(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateContentResolver(context: Context, songID: Long, newSongTitle: String, newSongArtist: String) {
|
||||
val uri = Audio.Media.EXTERNAL_CONTENT_URI
|
||||
val where = "${Audio.Media._ID} = ?"
|
||||
val args = arrayOf(songID.toString())
|
||||
|
||||
val values = ContentValues().apply {
|
||||
put(Audio.Media.TITLE, newSongTitle)
|
||||
put(Audio.Media.ARTIST, newSongArtist)
|
||||
private fun updateContentResolver(track: Track, newArtist: String, newTitle: String, newAlbum: String, onUpdateMediaStore: () -> Unit) {
|
||||
ensureBackgroundThread {
|
||||
try {
|
||||
activity.handleRecoverableSecurityException { granted ->
|
||||
if (granted) {
|
||||
tagHelper.writeTag(track, newArtist, newTitle, newAlbum)
|
||||
activity.runOnUiThread {
|
||||
onUpdateMediaStore.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
activity.toast(R.string.unknown_error_occurred)
|
||||
}
|
||||
}
|
||||
context.contentResolver.update(uri, values, where, args)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
package com.simplemobiletools.musicplayer.helpers
|
||||
|
||||
import android.content.ContentUris
|
||||
import android.content.ContentValues
|
||||
import android.provider.MediaStore
|
||||
import com.simplemobiletools.commons.activities.BaseSimpleActivity
|
||||
import com.simplemobiletools.commons.extensions.getFilenameExtension
|
||||
import com.simplemobiletools.commons.extensions.getFilenameFromPath
|
||||
import com.simplemobiletools.commons.extensions.getTempFile
|
||||
import com.simplemobiletools.musicplayer.models.Track
|
||||
import org.jaudiotagger.audio.AudioFileIO
|
||||
import org.jaudiotagger.audio.SupportedFileFormat
|
||||
import org.jaudiotagger.tag.FieldKey
|
||||
import org.jaudiotagger.tag.Tag
|
||||
import org.jaudiotagger.tag.TagOptionSingleton
|
||||
import org.jaudiotagger.tag.flac.FlacTag
|
||||
import org.jaudiotagger.tag.id3.ID3v24Tag
|
||||
import org.jaudiotagger.tag.mp4.Mp4Tag
|
||||
import org.jaudiotagger.tag.vorbiscomment.VorbisCommentTag
|
||||
|
||||
class TagHelper(private val activity: BaseSimpleActivity) {
|
||||
|
||||
init {
|
||||
TagOptionSingleton.getInstance().isAndroid = true
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TEMP_FOLDER = "music"
|
||||
// Editing tags in WMA and WAV files are flaky so we exclude them
|
||||
private val EXCLUDED_EXTENSIONS = listOf("wma", "wav")
|
||||
private val SUPPORTED_EXTENSIONS = SupportedFileFormat.values().map { it.filesuffix }.filter { it !in EXCLUDED_EXTENSIONS }
|
||||
}
|
||||
|
||||
fun isEditTagSupported(track: Track): Boolean {
|
||||
return SUPPORTED_EXTENSIONS.any { it == track.path.getFilenameExtension() }
|
||||
}
|
||||
|
||||
fun writeTag(track: Track, newArtist: String, newTitle: String, newAlbum: String) {
|
||||
if (isEditTagSupported(track)) {
|
||||
val uri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, track.mediaStoreId)
|
||||
val temp = activity.getTempFile(TEMP_FOLDER, track.path.getFilenameFromPath())
|
||||
activity.contentResolver.openInputStream(uri)!!.use { inputStream ->
|
||||
temp!!.outputStream().use { out ->
|
||||
inputStream.copyTo(out)
|
||||
}
|
||||
}
|
||||
|
||||
val audioFile = AudioFileIO.read(temp)
|
||||
val tag = audioFile.tag ?: createTag(track.path.getFilenameExtension()).also { audioFile.tag = it }
|
||||
tag.setField(FieldKey.TITLE, newTitle)
|
||||
tag.setField(FieldKey.ARTIST, newArtist)
|
||||
tag.setField(FieldKey.ALBUM, newAlbum)
|
||||
audioFile.commit()
|
||||
|
||||
activity.contentResolver.openOutputStream(uri, "w")!!.use { outputStream ->
|
||||
outputStream.write(temp!!.readBytes())
|
||||
}
|
||||
|
||||
temp!!.delete()
|
||||
|
||||
updateContentResolver(track)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTag(extension: String): Tag {
|
||||
return when (extension) {
|
||||
SupportedFileFormat.OGG.filesuffix -> VorbisCommentTag()
|
||||
SupportedFileFormat.M4A.filesuffix -> Mp4Tag()
|
||||
SupportedFileFormat.FLAC.filesuffix -> FlacTag()
|
||||
else -> ID3v24Tag()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateContentResolver(track: Track) {
|
||||
val uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
||||
val where = "${MediaStore.Audio.Media._ID} = ?"
|
||||
val args = arrayOf(track.mediaStoreId.toString())
|
||||
|
||||
val values = ContentValues().apply {
|
||||
put(MediaStore.Audio.Media.TITLE, track.title)
|
||||
put(MediaStore.Audio.Media.ARTIST, track.artist)
|
||||
put(MediaStore.Audio.Media.ALBUM, track.album)
|
||||
}
|
||||
activity.contentResolver.update(uri, values, where, args)
|
||||
}
|
||||
}
|
|
@ -12,4 +12,5 @@ class Events {
|
|||
class PlaylistsUpdated
|
||||
class TrackDeleted
|
||||
class NoStoragePermission
|
||||
class RefreshTracks
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ data class Track(
|
|||
@ColumnInfo(name = "artist") var artist: String,
|
||||
@ColumnInfo(name = "path") var path: String,
|
||||
@ColumnInfo(name = "duration") var duration: Int,
|
||||
@ColumnInfo(name = "album") val album: String,
|
||||
@ColumnInfo(name = "album") var album: String,
|
||||
@ColumnInfo(name = "cover_art") val coverArt: String,
|
||||
@ColumnInfo(name = "playlist_id") var playListId: Int,
|
||||
@ColumnInfo(name = "track_id") val trackId: Int // order id within the tracks' album
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/rename_song_scrollview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
@ -16,7 +15,7 @@
|
|||
android:id="@+id/song_title_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title"/>
|
||||
android:text="@string/title" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MyEditText
|
||||
android:id="@+id/song_title"
|
||||
|
@ -26,13 +25,13 @@
|
|||
android:inputType="textCapWords"
|
||||
android:singleLine="true"
|
||||
android:textCursorDrawable="@null"
|
||||
android:textSize="@dimen/normal_text_size"/>
|
||||
android:textSize="@dimen/normal_text_size" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/song_artist_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/artist"/>
|
||||
android:text="@string/artist" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MyEditText
|
||||
android:id="@+id/song_artist"
|
||||
|
@ -42,13 +41,29 @@
|
|||
android:inputType="textCapWords"
|
||||
android:singleLine="true"
|
||||
android:textCursorDrawable="@null"
|
||||
android:textSize="@dimen/normal_text_size"/>
|
||||
android:textSize="@dimen/normal_text_size" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/song_album_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/album" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MyEditText
|
||||
android:id="@+id/song_album"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/activity_margin"
|
||||
android:inputType="textCapWords"
|
||||
android:singleLine="true"
|
||||
android:textCursorDrawable="@null"
|
||||
android:textSize="@dimen/normal_text_size" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/file_name_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/filename"/>
|
||||
android:text="@string/filename" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MyEditText
|
||||
android:id="@+id/file_name"
|
||||
|
@ -57,13 +72,13 @@
|
|||
android:layout_marginBottom="@dimen/activity_margin"
|
||||
android:singleLine="true"
|
||||
android:textCursorDrawable="@null"
|
||||
android:textSize="@dimen/normal_text_size"/>
|
||||
android:textSize="@dimen/normal_text_size" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MyTextView
|
||||
android:id="@+id/extension_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/extension"/>
|
||||
android:text="@string/extension" />
|
||||
|
||||
<com.simplemobiletools.commons.views.MyEditText
|
||||
android:id="@+id/extension"
|
||||
|
@ -72,7 +87,7 @@
|
|||
android:layout_marginBottom="@dimen/activity_margin"
|
||||
android:singleLine="true"
|
||||
android:textCursorDrawable="@null"
|
||||
android:textSize="@dimen/normal_text_size"/>
|
||||
android:textSize="@dimen/normal_text_size" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
|
|
@ -6,6 +6,11 @@
|
|||
android:icon="@drawable/ic_delete_vector"
|
||||
android:title="@string/delete"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/cab_rename"
|
||||
android:icon="@drawable/ic_rename_vector"
|
||||
android:title="@string/rename"
|
||||
app:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/cab_add_to_playlist"
|
||||
android:title="@string/add_to_playlist"
|
||||
|
|
|
@ -16,6 +16,11 @@
|
|||
android:icon="@drawable/ic_info_vector"
|
||||
android:title="@string/properties"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/cab_rename"
|
||||
android:icon="@drawable/ic_rename_vector"
|
||||
android:title="@string/rename"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/cab_add_to_playlist"
|
||||
android:title="@string/add_to_playlist"
|
||||
|
|
|
@ -6,6 +6,11 @@
|
|||
android:icon="@drawable/ic_delete_vector"
|
||||
android:title="@string/delete"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/cab_rename"
|
||||
android:icon="@drawable/ic_rename_vector"
|
||||
android:title="@string/rename"
|
||||
app:showAsAction="ifRoom"/>
|
||||
<item
|
||||
android:id="@+id/cab_add_to_playlist"
|
||||
android:title="@string/add_to_playlist"
|
||||
|
|
|
@ -120,4 +120,4 @@
|
|||
Haven't found some strings? There's more at
|
||||
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
|
||||
-->
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -20,6 +20,7 @@ buildscript {
|
|||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue