mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-03-22 15:09:22 +01:00
Enhance original file instead of cached preview
This commit is contained in:
parent
192fe923a2
commit
6d0f612c7b
7 changed files with 123 additions and 40 deletions
|
@ -2,9 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/api.dart';
|
import 'package:nc_photos/api/api.dart';
|
||||||
import 'package:nc_photos/api/api_util.dart' as api_util;
|
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/cache_manager_util.dart';
|
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/help_utils.dart';
|
import 'package:nc_photos/help_utils.dart';
|
||||||
|
@ -64,10 +62,15 @@ class EnhanceHandler {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_log.info("[call] Selected: ${selected.name}");
|
_log.info("[call] Selected: ${selected.name}");
|
||||||
final imageUri = await _getFileUri();
|
|
||||||
switch (selected) {
|
switch (selected) {
|
||||||
case _Algorithm.zeroDce:
|
case _Algorithm.zeroDce:
|
||||||
await ImageProcessor.zeroDce(imageUri.toString(), file.filename);
|
await ImageProcessor.zeroDce(
|
||||||
|
"${account.url}/${file.path}",
|
||||||
|
file.filename,
|
||||||
|
headers: {
|
||||||
|
"Authorization": Api.getAuthorizationHeaderValue(account),
|
||||||
|
},
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,22 +107,6 @@ class EnhanceHandler {
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
Future<Uri> _getFileUri() async {
|
|
||||||
final f = await LargeImageCacheManager.inst.getSingleFile(
|
|
||||||
api_util.getFilePreviewUrl(
|
|
||||||
account,
|
|
||||||
file,
|
|
||||||
width: k.photoLargeSize,
|
|
||||||
height: k.photoLargeSize,
|
|
||||||
a: true,
|
|
||||||
),
|
|
||||||
headers: {
|
|
||||||
"Authorization": Api.getAuthorizationHeaderValue(account),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return f.absolute.uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
final File file;
|
final File file;
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,9 @@ import android.net.Uri
|
||||||
interface MessageEvent
|
interface MessageEvent
|
||||||
|
|
||||||
data class ImageProcessorCompletedEvent(
|
data class ImageProcessorCompletedEvent(
|
||||||
val image: Uri,
|
|
||||||
val result: Uri,
|
val result: Uri,
|
||||||
) : MessageEvent
|
) : MessageEvent
|
||||||
|
|
||||||
data class ImageProcessorFailedEvent(
|
data class ImageProcessorFailedEvent(
|
||||||
val image: Uri,
|
|
||||||
val exception: Throwable,
|
val exception: Throwable,
|
||||||
) : MessageEvent
|
) : MessageEvent
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
package com.nkming.nc_photos.plugin
|
package com.nkming.nc_photos.plugin
|
||||||
|
|
||||||
class PermissionException(message: String) : Exception(message)
|
class PermissionException(message: String) : Exception(message)
|
||||||
|
|
||||||
|
class HttpException(statusCode: Int, message: String): Exception(message)
|
||||||
|
|
|
@ -20,7 +20,8 @@ class ImageProcessorChannelHandler(context: Context) :
|
||||||
"zeroDce" -> {
|
"zeroDce" -> {
|
||||||
try {
|
try {
|
||||||
zeroDce(
|
zeroDce(
|
||||||
call.argument("image")!!,
|
call.argument("fileUrl")!!,
|
||||||
|
call.argument("headers"),
|
||||||
call.argument("filename")!!,
|
call.argument("filename")!!,
|
||||||
result
|
result
|
||||||
)
|
)
|
||||||
|
@ -42,14 +43,18 @@ class ImageProcessorChannelHandler(context: Context) :
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun zeroDce(
|
private fun zeroDce(
|
||||||
image: String, filename: String, result: MethodChannel.Result
|
fileUrl: String, headers: Map<String, String>?, filename: String,
|
||||||
|
result: MethodChannel.Result
|
||||||
) {
|
) {
|
||||||
val intent = Intent(context, ImageProcessorService::class.java).apply {
|
val intent = Intent(context, ImageProcessorService::class.java).apply {
|
||||||
putExtra(
|
putExtra(
|
||||||
ImageProcessorService.EXTRA_METHOD,
|
ImageProcessorService.EXTRA_METHOD,
|
||||||
ImageProcessorService.METHOD_ZERO_DCE
|
ImageProcessorService.METHOD_ZERO_DCE
|
||||||
)
|
)
|
||||||
putExtra(ImageProcessorService.EXTRA_IMAGE, image)
|
putExtra(ImageProcessorService.EXTRA_FILE_URL, fileUrl)
|
||||||
|
putExtra(
|
||||||
|
ImageProcessorService.EXTRA_HEADERS,
|
||||||
|
headers?.let { HashMap(it) })
|
||||||
putExtra(ImageProcessorService.EXTRA_FILENAME, filename)
|
putExtra(ImageProcessorService.EXTRA_FILENAME, filename)
|
||||||
}
|
}
|
||||||
ContextCompat.startForegroundService(context, intent)
|
ContextCompat.startForegroundService(context, intent)
|
||||||
|
|
|
@ -18,12 +18,16 @@ import androidx.core.app.NotificationChannelCompat
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import com.nkming.nc_photos.plugin.image_processor.ZeroDce
|
import com.nkming.nc_photos.plugin.image_processor.ZeroDce
|
||||||
|
import java.io.File
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
class ImageProcessorService : Service() {
|
class ImageProcessorService : Service() {
|
||||||
companion object {
|
companion object {
|
||||||
const val EXTRA_METHOD = "method"
|
const val EXTRA_METHOD = "method"
|
||||||
const val METHOD_ZERO_DCE = "zero-dce"
|
const val METHOD_ZERO_DCE = "zero-dce"
|
||||||
const val EXTRA_IMAGE = "image"
|
const val EXTRA_FILE_URL = "fileUrl"
|
||||||
|
const val EXTRA_HEADERS = "headers"
|
||||||
const val EXTRA_FILENAME = "filename"
|
const val EXTRA_FILENAME = "filename"
|
||||||
|
|
||||||
private const val NOTIFICATION_ID =
|
private const val NOTIFICATION_ID =
|
||||||
|
@ -45,6 +49,7 @@ class ImageProcessorService : Service() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
wakeLock.acquire()
|
wakeLock.acquire()
|
||||||
createNotificationChannel()
|
createNotificationChannel()
|
||||||
|
cleanUp()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
@ -55,7 +60,7 @@ class ImageProcessorService : Service() {
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||||
assert(intent.hasExtra(EXTRA_METHOD))
|
assert(intent.hasExtra(EXTRA_METHOD))
|
||||||
assert(intent.hasExtra(EXTRA_IMAGE))
|
assert(intent.hasExtra(EXTRA_FILE_URL))
|
||||||
if (!isForeground) {
|
if (!isForeground) {
|
||||||
try {
|
try {
|
||||||
startForeground(NOTIFICATION_ID, buildNotification())
|
startForeground(NOTIFICATION_ID, buildNotification())
|
||||||
|
@ -73,19 +78,23 @@ class ImageProcessorService : Service() {
|
||||||
Log.e(TAG, "Unknown method: $method")
|
Log.e(TAG, "Unknown method: $method")
|
||||||
// we can't call stopSelf here as it'll stop the service even if
|
// we can't call stopSelf here as it'll stop the service even if
|
||||||
// there are commands running in the bg
|
// there are commands running in the bg
|
||||||
addCommand(
|
addCommand(ImageProcessorCommand(startId, "null", "", null, ""))
|
||||||
ImageProcessorCommand(startId, "null", Uri.EMPTY, "")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return START_REDELIVER_INTENT
|
return START_REDELIVER_INTENT
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onZeroDce(startId: Int, extras: Bundle) {
|
private fun onZeroDce(startId: Int, extras: Bundle) {
|
||||||
val imageUri = Uri.parse(extras.getString(EXTRA_IMAGE)!!)
|
val fileUrl = extras.getString(EXTRA_FILE_URL)!!
|
||||||
|
|
||||||
|
@Suppress("Unchecked_cast")
|
||||||
|
val headers =
|
||||||
|
extras.getSerializable(EXTRA_HEADERS) as HashMap<String, String>?
|
||||||
val filename = extras.getString(EXTRA_FILENAME)!!
|
val filename = extras.getString(EXTRA_FILENAME)!!
|
||||||
addCommand(
|
addCommand(
|
||||||
ImageProcessorCommand(startId, METHOD_ZERO_DCE, imageUri, filename)
|
ImageProcessorCommand(
|
||||||
|
startId, METHOD_ZERO_DCE, fileUrl, headers, filename
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,6 +195,17 @@ class ImageProcessorService : Service() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up temp files in case the service ended prematurely last time
|
||||||
|
*/
|
||||||
|
private fun cleanUp() {
|
||||||
|
try {
|
||||||
|
getTempDir(this).deleteRecursively()
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Log.e(TAG, "[cleanUp] Failed while cleanUp", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var isForeground = false
|
private var isForeground = false
|
||||||
private val cmds = mutableListOf<ImageProcessorCommand>()
|
private val cmds = mutableListOf<ImageProcessorCommand>()
|
||||||
private var cmdTask: ImageProcessorCommandTask? = null
|
private var cmdTask: ImageProcessorCommandTask? = null
|
||||||
|
@ -205,7 +225,8 @@ class ImageProcessorService : Service() {
|
||||||
private data class ImageProcessorCommand(
|
private data class ImageProcessorCommand(
|
||||||
val startId: Int,
|
val startId: Int,
|
||||||
val method: String,
|
val method: String,
|
||||||
val uri: Uri,
|
val fileUrl: String,
|
||||||
|
val headers: Map<String, String>?,
|
||||||
val filename: String,
|
val filename: String,
|
||||||
val args: Map<String, Any> = mapOf(),
|
val args: Map<String, Any> = mapOf(),
|
||||||
)
|
)
|
||||||
|
@ -222,18 +243,62 @@ private open class ImageProcessorCommandTask(context: Context) :
|
||||||
): MessageEvent {
|
): MessageEvent {
|
||||||
val cmd = params[0]!!
|
val cmd = params[0]!!
|
||||||
return try {
|
return try {
|
||||||
|
val outUri = handleCommand(cmd)
|
||||||
|
ImageProcessorCompletedEvent(outUri)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Log.e(TAG, "[doInBackground] Failed while handleCommand", e)
|
||||||
|
ImageProcessorFailedEvent(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleCommand(cmd: ImageProcessorCommand): Uri {
|
||||||
|
val file = downloadFile(cmd.fileUrl, cmd.headers)
|
||||||
|
return try {
|
||||||
|
val fileUri = Uri.fromFile(file)
|
||||||
val output = when (cmd.method) {
|
val output = when (cmd.method) {
|
||||||
ImageProcessorService.METHOD_ZERO_DCE -> ZeroDce(context).infer(
|
ImageProcessorService.METHOD_ZERO_DCE -> ZeroDce(context).infer(
|
||||||
cmd.uri
|
fileUri
|
||||||
)
|
)
|
||||||
else -> throw IllegalArgumentException(
|
else -> throw IllegalArgumentException(
|
||||||
"Unknown method: ${cmd.method}"
|
"Unknown method: ${cmd.method}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val uri = saveBitmap(output, cmd.filename)
|
saveBitmap(output, cmd.filename)
|
||||||
ImageProcessorCompletedEvent(cmd.uri, uri)
|
} finally {
|
||||||
} catch (e: Throwable) {
|
file.delete()
|
||||||
ImageProcessorFailedEvent(cmd.uri, e)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun downloadFile(
|
||||||
|
fileUrl: String, headers: Map<String, String>?
|
||||||
|
): File {
|
||||||
|
Log.i(TAG, "[downloadFile] $fileUrl")
|
||||||
|
return (URL(fileUrl).openConnection() as HttpURLConnection).apply {
|
||||||
|
requestMethod = "GET"
|
||||||
|
instanceFollowRedirects = true
|
||||||
|
connectTimeout = 8000
|
||||||
|
readTimeout = 15000
|
||||||
|
for (entry in (headers ?: mapOf()).entries) {
|
||||||
|
setRequestProperty(entry.key, entry.value)
|
||||||
|
}
|
||||||
|
}.use {
|
||||||
|
val responseCode = it.responseCode
|
||||||
|
if (responseCode / 100 == 2) {
|
||||||
|
val file =
|
||||||
|
File.createTempFile("img", null, getTempDir(context))
|
||||||
|
file.outputStream().use { oStream ->
|
||||||
|
it.inputStream.copyTo(oStream)
|
||||||
|
}
|
||||||
|
file
|
||||||
|
} else {
|
||||||
|
Log.e(
|
||||||
|
TAG,
|
||||||
|
"[downloadFile] Failed downloading file: HTTP$responseCode"
|
||||||
|
)
|
||||||
|
throw HttpException(
|
||||||
|
responseCode, "Failed downloading file (HTTP$responseCode)"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,3 +313,14 @@ private open class ImageProcessorCommandTask(context: Context) :
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
private val context = context
|
private val context = context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getTempDir(context: Context): File {
|
||||||
|
val f = File(context.cacheDir, "imageProcessor")
|
||||||
|
if (!f.exists()) {
|
||||||
|
f.mkdirs()
|
||||||
|
} else if (!f.isDirectory) {
|
||||||
|
f.delete()
|
||||||
|
f.mkdirs()
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.nkming.nc_photos.plugin
|
||||||
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
|
||||||
fun getPendingIntentFlagImmutable(): Int {
|
fun getPendingIntentFlagImmutable(): Int {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||||
|
@ -12,3 +13,12 @@ fun getPendingIntentFlagMutable(): Int {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||||
PendingIntent.FLAG_MUTABLE else 0
|
PendingIntent.FLAG_MUTABLE else 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <T> HttpURLConnection.use(block: (HttpURLConnection) -> T): T {
|
||||||
|
try {
|
||||||
|
connect()
|
||||||
|
return block(this)
|
||||||
|
} finally {
|
||||||
|
disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,9 +4,14 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:nc_photos_plugin/src/k.dart' as k;
|
import 'package:nc_photos_plugin/src/k.dart' as k;
|
||||||
|
|
||||||
class ImageProcessor {
|
class ImageProcessor {
|
||||||
static Future<void> zeroDce(String image, String filename) =>
|
static Future<void> zeroDce(
|
||||||
|
String fileUrl,
|
||||||
|
String filename, {
|
||||||
|
Map<String, String>? headers,
|
||||||
|
}) =>
|
||||||
_methodChannel.invokeMethod("zeroDce", <String, dynamic>{
|
_methodChannel.invokeMethod("zeroDce", <String, dynamic>{
|
||||||
"image": image,
|
"fileUrl": fileUrl,
|
||||||
|
"headers": headers,
|
||||||
"filename": filename,
|
"filename": filename,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue