You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

302 lines
11 KiB

package dev.lowrespalmtree.zmingz
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.AsyncTask
import android.os.Bundle
import android.os.Environment
import android.os.Parcelable
import android.provider.MediaStore
import android.util.Log
import android.view.View
import android.widget.PopupMenu
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import com.arthenica.mobileffmpeg.Config.RETURN_CODE_CANCEL
import com.arthenica.mobileffmpeg.Config.RETURN_CODE_SUCCESS
import com.arthenica.mobileffmpeg.FFmpeg
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.lang.ref.WeakReference
class MainActivity: AppCompatActivity() {
/** Path for first image/video. */
private var path1 = ""
/** Path for second image/video. */
private var path2 = ""
/** Temporary path for the media to save, to use after perms being granted. */
private var savingPath = ""
/** Temporary type for the media to save, to use after perms being granted. */
private var savingType = MediaType.IMAGE
enum class MediaType { IMAGE, VIDEO }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (intent.action == Intent.ACTION_SEND) {
val type = if (intent.type?.startsWith("image") == true) {
MediaType.IMAGE
} else if (intent.type?.startsWith("video") == true) {
MediaType.VIDEO
} else {
null
}
val uri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri
if (type != null && uri != null)
processMedia(uri, type)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQ_PICK_IMG, REQ_PICK_VID -> handlePickResult(requestCode, resultCode, data)
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == REQ_WRITE_PERM) {
if (permissions[0] == Manifest.permission.WRITE_EXTERNAL_STORAGE) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
saveWithPerm(savingPath, savingType)
return
}
}
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
fun openFile(v: View) {
val req: Int
val pickIntent = when (v.id) {
buttonImage.id -> {
req = REQ_PICK_IMG
Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
}
buttonVideo.id -> {
req = REQ_PICK_VID
Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
}
else -> return
}
startActivityForResult(pickIntent, req)
}
fun openViewMenu(v: View) {
val path = when (v.id) {
imageView1.id, videoView1.id -> path1
imageView2.id, videoView2.id -> path2
else -> return
}
val type = mediaTypeFromViewId(v.id)
?: return
val menu = PopupMenu(this, v)
menu.menu.add(R.string.save).setOnMenuItemClickListener {
save(path, type)
true
}
menu.menu.add(R.string.share).setOnMenuItemClickListener {
share(path, type)
true
}
menu.show()
}
private fun mediaTypeFromViewId(id: Int): MediaType? =
when (id) {
imageView1.id, imageView2.id -> MediaType.IMAGE
videoView1.id, videoView2.id -> MediaType.VIDEO
else -> null
}
@Suppress("UNUSED_PARAMETER")
private fun handlePickResult(requestCode: Int, resultCode: Int, data: Intent?) {
val uri = data?.data
?: return Unit.also { Log.e(TAG, "No intent or data") }
val type = when (requestCode) {
REQ_PICK_IMG -> MediaType.IMAGE
REQ_PICK_VID -> MediaType.VIDEO
else -> return
}
processMedia(uri, type)
}
private fun processMedia(uri: Uri, type: MediaType) {
val uriPath = uri.path
?: return Unit.also { Log.e(TAG, "No path in URI") }
val extension = getFileExtension(uriPath)
// Copy picked file to cache dir for FFmpeg.
val inputFile = File.createTempFile("input", ".$extension", cacheDir)
contentResolver.openInputStream(uri).use { inputStream ->
if (inputStream == null)
return Unit .also { Log.e(TAG, "Could not open input file") }
FileOutputStream(inputFile).use { fos ->
fos.buffered().write(inputStream.buffered().readBytes())
}
}
val mediaCacheDir = File(cacheDir, "media")
if (!mediaCacheDir.exists() && !mediaCacheDir.mkdir()) {
Log.e(TAG, "Could not create cache media dir")
Toast.makeText(this, R.string.write_error, Toast.LENGTH_SHORT).show()
return
}
imageLayout.visibility = View.GONE
videoLayout.visibility = View.GONE
Toast.makeText(this, R.string.please_wait, Toast.LENGTH_SHORT).show()
val outputFile1 = File.createTempFile("output1", ".$extension", mediaCacheDir)
MirrorTask(WeakReference(this), type, 1)
.execute(inputFile.canonicalPath, outputFile1.canonicalPath, VF1)
val outputFile2 = File.createTempFile("output2", ".$extension", mediaCacheDir)
MirrorTask(WeakReference(this), type, 2)
.execute(inputFile.canonicalPath, outputFile2.canonicalPath, VF2)
}
class MirrorTask(
private val activity: WeakReference<MainActivity>,
private val type: MediaType,
private val index: Int
): AsyncTask<String, Void, Boolean>() {
private lateinit var outputPath: String
override fun doInBackground(vararg params: String?): Boolean {
val inputPath = params[0]!!
outputPath = params[1]!!
val vf = params[2]!!
val command = "-i $inputPath -vf \"$vf\" -y $outputPath"
return when (val rc = FFmpeg.execute(command)) {
RETURN_CODE_SUCCESS -> true. also { Log.d(TAG, "FFmpeg success") }
RETURN_CODE_CANCEL -> false .also { Log.d(TAG, "FFmpeg canceled") }
else -> false .also { Log.d(TAG, "FFmpeg failed with rc $rc") }
}
}
override fun onPostExecute(result: Boolean?) {
if (result == true) {
activity.get()?.updateViews(type, index, outputPath)
}
}
}
internal fun updateViews(type: MediaType, viewIndex: Int, outputPath: String) {
when (type) {
MediaType.IMAGE -> {
if (imageLayout.visibility != View.VISIBLE)
imageLayout.visibility = View.VISIBLE
val mirrored = BitmapFactory.decodeFile(outputPath)
val view = when (viewIndex) {
1 -> { path1 = outputPath; imageView1 }
2 -> { path2 = outputPath; imageView2 }
else -> return
}
view.setImageBitmap(mirrored)
}
MediaType.VIDEO -> {
if (videoLayout.visibility != View.VISIBLE)
videoLayout.visibility = View.VISIBLE
val view = when (viewIndex) {
1 -> { path1 = outputPath; videoView1 }
2 -> { path2 = outputPath; videoView2 }
else -> return
}
view.setVideoPath(outputPath)
view.setOnPreparedListener { mp -> mp.isLooping = true }
view.start()
}
}
}
private fun save(path: String, type: MediaType) {
val writePermission = Manifest.permission.WRITE_EXTERNAL_STORAGE
val permissionStatus = checkSelfPermission(writePermission)
if (permissionStatus == PackageManager.PERMISSION_GRANTED) {
saveWithPerm(path, type)
} else {
savingPath = path
savingType = type
requestPermissions(arrayOf(writePermission), REQ_WRITE_PERM)
}
}
private fun saveWithPerm(path: String, type: MediaType) {
// Ensure media directories for the app exist.
val envDirId = when (type) {
MediaType.IMAGE -> Environment.DIRECTORY_PICTURES
MediaType.VIDEO -> Environment.DIRECTORY_MOVIES
}
val externalMediaDir = getExternalFilesDir(envDirId)
?: return Unit .also { Log.e(TAG, "Can't find an external dir") }
val mediaDir = File(externalMediaDir, getString(R.string.app_name))
if (!mediaDir.exists() && !mediaDir.mkdir()) {
Log.e(TAG, "Failed to create app media dir: $mediaDir")
Toast.makeText(this, R.string.write_error, Toast.LENGTH_SHORT).show()
return
}
val extension = getFileExtension(path).let { if (it.isEmpty()) "xxx" else it }
val outputFile = File(mediaDir.canonicalPath, "${System.currentTimeMillis()}.$extension")
if (!outputFile.createNewFile()) {
Log.e(TAG, "Failed to create new file: $outputFile")
Toast.makeText(this, R.string.write_error, Toast.LENGTH_SHORT).show()
return
}
FileInputStream(path).use { fis ->
FileOutputStream(outputFile).use { fos ->
fos.buffered().write(fis.buffered().readBytes())
}
}
Log.i(TAG, "File saved at $outputFile")
}
private fun share(path: String, type: MediaType) {
val uri = FileProvider.getUriForFile(
this,
"dev.lowrespalmtree.fileprovider",
File(path)
)
Log.i(TAG, "share with uri $uri")
val shareIntent = Intent().also {
it.action = Intent.ACTION_SEND
it.putExtra(Intent.EXTRA_STREAM, uri)
it.type = when (type) {
MediaType.IMAGE -> "image/*"
MediaType.VIDEO -> "video/*"
}
}
startActivity(Intent.createChooser(shareIntent, getString(R.string.send)))
}
companion object {
private const val TAG = "ZMINGZ"
private const val REQ_PICK_IMG = 1
private const val REQ_PICK_VID = 2
private const val REQ_WRITE_PERM = 3
private const val VF1 = "crop=iw/2:ih:0:0,split[left][tmp];[tmp]hflip[right];[left][right] hstack"
private const val VF2 = "crop=iw/2:ih:iw/2:0,split[left][tmp];[tmp]hflip[right];[right][left] hstack"
private fun getFileExtension(path: String): String {
if (!path.contains('.'))
return ""
return path.substring(path.lastIndexOf('.') + 1)
}
}
}