(Mastodon 3.2)音声や動画の添付メディアにカスタムサムネイルをアップロードできる。

This commit is contained in:
tateisu 2020-07-19 22:39:39 +09:00
parent bab7bfdfd8
commit c073c4e8ed
5 changed files with 215 additions and 47 deletions

View File

@ -92,6 +92,7 @@ class ActPost : AsyncActivity(),
private const val REQUEST_CODE_VIDEO = 4
private const val REQUEST_CODE_ATTACHMENT_OLD = 5
private const val REQUEST_CODE_SOUND = 6
private const val REQUEST_CODE_CUSTOM_THUMBNAIL = 7
private const val PERMISSION_REQUEST_CODE = 1
@ -532,6 +533,12 @@ class ActPost : AsyncActivity(),
}
} else {
when(requestCode) {
REQUEST_CODE_CUSTOM_THUMBNAIL -> checkCustomThumbnail(
data?.handleGetContentResult(
contentResolver
)
)
REQUEST_CODE_ATTACHMENT_OLD -> checkAttachments(
data?.handleGetContentResult(
contentResolver
@ -1674,6 +1681,17 @@ class ActPost : AsyncActivity(),
openFocusPoint(pa)
}
}
if(account?.isMastodon == true) {
when(pa.attachment?.type) {
TootAttachmentType.Audio, TootAttachmentType.GIFV, TootAttachmentType.Video ->
a.addAction(getString(R.string.custom_thumbnail)) {
openCustomThumbnail(pa)
}
else -> {
}
}
}
a.addAction(getString(R.string.delete)) {
deleteAttachment(pa)
@ -1683,51 +1701,196 @@ class ActPost : AsyncActivity(),
}
private fun openFocusPoint(pa : PostAttachment) {
val attachment = pa.attachment
if(attachment != null) {
DlgFocusPoint(this, attachment)
.setCallback(object : FocusPointView.Callback {
override fun onFocusPointUpdate(x : Float, y : Float) {
val account = this@ActPost.account ?: return
TootTaskRunner(this@ActPost, TootTaskRunner.PROGRESS_NONE).run(account,
object : TootTask {
override fun background(client : TootApiClient) : TootApiResult? {
try {
val result = client.request(
"/api/v1/media/${attachment.id}",
jsonObject {
put("focus", "%.2f,%.2f".format(x, y))
}
.toPutRequestBuilder()
val attachment = pa.attachment ?: return
DlgFocusPoint(this, attachment)
.setCallback(object : FocusPointView.Callback {
override fun onFocusPointUpdate(x : Float, y : Float) {
val account = this@ActPost.account ?: return
TootTaskRunner(this@ActPost, TootTaskRunner.PROGRESS_NONE).run(account,
object : TootTask {
override fun background(client : TootApiClient) : TootApiResult? {
try {
val result = client.request(
"/api/v1/media/${attachment.id}",
jsonObject {
put("focus", "%.2f,%.2f".format(x, y))
}
.toPutRequestBuilder()
)
new_attachment =
parseItem(
::TootAttachment,
ServiceType.MASTODON,
result?.jsonObject
)
new_attachment =
parseItem(
::TootAttachment,
ServiceType.MASTODON,
result?.jsonObject
)
return result
} catch(ex : Throwable) {
return TootApiResult(ex.withCaption("set focus point failed."))
}
return result
} catch(ex : Throwable) {
return TootApiResult(ex.withCaption("set focus point failed."))
}
var new_attachment : TootAttachment? = null
override fun handleResult(result : TootApiResult?) {
result ?: return
if(new_attachment != null) {
pa.attachment = attachment
} else {
showToast(this@ActPost, true, result.error)
}
}
var new_attachment : TootAttachment? = null
override fun handleResult(result : TootApiResult?) {
result ?: return
if(new_attachment != null) {
pa.attachment = attachment
} else {
showToast(this@ActPost, true, result.error)
}
})
}
})
.show()
}
})
}
})
.show()
}
private fun openCustomThumbnail(pa : PostAttachment) {
lastPostAttachment = pa
val permissionCheck =
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
if(permissionCheck != PackageManager.PERMISSION_GRANTED) {
preparePermission()
return
}
// SAFのIntentで開く
try {
val intent =
intentGetContent(false, getString(R.string.pick_images), arrayOf("image/*"))
startActivityForResult(intent, REQUEST_CODE_CUSTOM_THUMBNAIL)
} catch(ex : Throwable) {
log.trace(ex)
showToast(this, ex, "ACTION_GET_CONTENT failed.")
}
}
private var lastPostAttachment : PostAttachment? = null
private fun checkCustomThumbnail(srcList : ArrayList<GetContentResultEntry>?) {
val src = srcList?.elementAtOrNull(0) ?: return
val account = this@ActPost.account
if(account == null) {
showToast(this, false, R.string.account_select_please)
return
}
val mime_type = getMimeType(src.uri, src.mimeType)
if(mime_type?.isEmpty() != false) {
showToast(this, false, R.string.mime_type_missing)
return
}
val pa = lastPostAttachment
if( pa == null || !attachment_list.contains(pa)){
showToast(this, true, "lost attachment information")
return
}
TootTaskRunner(this).run(account,object:TootTask{
override fun background(client : TootApiClient) : TootApiResult? {
try {
val (ti, tiResult) = TootInstance.get(client)
ti ?: return tiResult
val resizeConfig = ResizeConfig(ResizeType.SquarePixel,400)
val opener = createOpener(src.uri,mime_type, resizeConfig)
val media_size_max = 1000000
val content_length = getStreamSize(true, opener.open())
if(content_length > media_size_max) {
return TootApiResult(
getString(
R.string.file_size_too_big,
media_size_max / 1000000
)
)
}
fun fixDocumentName(s : String) : String {
val s_length = s.length
val m = """([^\x20-\x7f])""".asciiPattern().matcher(s)
m.reset()
val sb = StringBuilder(s_length)
var lastEnd = 0
while(m.find()) {
sb.append(s.substring(lastEnd, m.start()))
val escaped = m.groupEx(1) !!.encodeUTF8().encodeHex()
sb.append(escaped)
lastEnd = m.end()
}
if(lastEnd < s_length) sb.append(s.substring(lastEnd, s_length))
return sb.toString()
}
val fileName = fixDocumentName(getDocumentName(contentResolver, src.uri))
return if(account.isMisskey) {
opener.deleteTempFile()
TootApiResult("custom thumbnail is not supported on misskey account.")
} else {
val result =client.request(
"/api/v1/media/${pa.attachment?.id}",
MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart(
"thumbnail",
fileName,
object : RequestBody() {
override fun contentType() : MediaType? {
return opener.mimeType.toMediaType()
}
@Throws(IOException::class)
override fun contentLength() : Long {
return content_length
}
@Throws(IOException::class)
override fun writeTo(sink : BufferedSink) {
opener.open().use { inData ->
val tmp = ByteArray(4096)
while(true) {
val r = inData.read(tmp, 0, tmp.size)
if(r <= 0) break
sink.write(tmp, 0, r)
}
}
}
}
)
.build().toPut()
)
opener.deleteTempFile()
val jsonObject = result?.jsonObject
if(jsonObject != null) {
val a = parseItem(::TootAttachment, ServiceType.MASTODON, jsonObject)
if(a == null) {
result.error = "TootAttachment.parse failed"
} else {
pa.attachment = a
}
}
result
}
} catch(ex : Throwable) {
return TootApiResult(ex.withCaption("read failed."))
}
}
override fun handleResult(result : TootApiResult?) {
showMediaAttachment()
result?.error?.let{ showToast(this@ActPost,true,it)}
}
})
}
private fun deleteAttachment(pa : PostAttachment) {
@ -1885,7 +2048,7 @@ class ActPost : AsyncActivity(),
fun deleteTempFile()
}
private fun createOpener(uri : Uri, mime_type : String) : InputStreamOpener {
private fun createOpener(uri : Uri, mime_type : String,resizeConfig: ResizeConfig) : InputStreamOpener {
while(true) {
try {
@ -1898,8 +2061,6 @@ class ActPost : AsyncActivity(),
break
}
// 設定からリサイズ指定を読む
val resizeConfig = resizeConfigList[Pref.ipResizeImage(pref)]
val bitmap = createResizedBitmap(
this,
@ -2137,8 +2298,10 @@ class ActPost : AsyncActivity(),
return TootApiResult(getString(R.string.mime_type_not_acceptable, mimeType))
}
}
// 設定からリサイズ指定を読む
val resizeConfig = resizeConfigList[Pref.ipResizeImage(pref)]
val opener = createOpener(uri, mimeType)
val opener = createOpener(uri, mimeType, resizeConfig)
val media_size_max = when {

View File

@ -1723,7 +1723,7 @@ internal class ItemViewHolder(
TootAttachmentType.Audio -> {
iv.setMediaType(0)
iv.setDefaultImage(defaultColorIcon(activity, R.drawable.wide_music))
iv.setImageUrl(activity.pref, 0f, null)
iv.setImageUrl(activity.pref, 0f, ta.urlForThumbnail)
showUrl = true
}

View File

@ -97,7 +97,10 @@ class TootAttachment : TootAttachmentLike {
get() = remote_url.notEmpty() ?: url
override val urlForThumbnail : String?
get() = preview_url.notEmpty() ?: remote_url.notEmpty() ?: url
get() = preview_url.notEmpty() ?: when(type){
TootAttachmentType.Image -> remote_url.notEmpty() ?: url
else -> null
}
constructor(serviceType : ServiceType, src : JsonObject) {

View File

@ -1030,4 +1030,5 @@
<string name="screen_bottom_padding">画面下部の余白(単位:dp。デフォルト:0。アプリ再起動が必要)</string>
<string name="personal_notes">覚え書き</string>
<string name="personal_notes_of">%1$sに関する覚え書き</string>
<string name="custom_thumbnail">カスタムサムネイル</string>
</resources>

View File

@ -1037,4 +1037,5 @@
<string name="screen_bottom_padding">Screen bottom padding (Unit: dp. Default:0. App restart required)</string>
<string name="personal_notes">Personal notes</string>
<string name="personal_notes_of">Personal notes of %1$s</string>
<string name="custom_thumbnail">Custom thumbnail</string>
</resources>