mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-20 15:24:45 +08:00
AudioToSilkService (#1591)
* AudioToSilkService * Update mirai-core-api/src/commonMain/kotlin/utils/ExternalResource.kt Co-authored-by: Eritque arcus <1930893235@qq.com> * `@since`; `@Experimental` Co-authored-by: Eritque arcus <1930893235@qq.com>
This commit is contained in:
parent
8bd3d94537
commit
b31f7b1ba7
@ -5709,6 +5709,7 @@ public abstract interface class net/mamoe/mirai/utils/ExternalResource : java/io
|
||||
public abstract fun getClosed ()Lkotlinx/coroutines/Deferred;
|
||||
public abstract fun getFormatName ()Ljava/lang/String;
|
||||
public abstract fun getMd5 ()[B
|
||||
public fun getOrigin ()Ljava/lang/Object;
|
||||
public fun getSha1 ()[B
|
||||
public abstract fun getSize ()J
|
||||
public abstract fun inputStream ()Ljava/io/InputStream;
|
||||
|
@ -5709,6 +5709,7 @@ public abstract interface class net/mamoe/mirai/utils/ExternalResource : java/io
|
||||
public abstract fun getClosed ()Lkotlinx/coroutines/Deferred;
|
||||
public abstract fun getFormatName ()Ljava/lang/String;
|
||||
public abstract fun getMd5 ()[B
|
||||
public fun getOrigin ()Ljava/lang/Object;
|
||||
public fun getSha1 ()[B
|
||||
public abstract fun getSize ()J
|
||||
public abstract fun inputStream ()Ljava/io/InputStream;
|
||||
|
@ -40,6 +40,10 @@ internal class ExternalResourceImplByFileWithMd5(
|
||||
}
|
||||
}
|
||||
|
||||
override var origin: Any? = null
|
||||
internal set
|
||||
|
||||
|
||||
override val holder: ResourceHolder = ResourceHolder(file)
|
||||
|
||||
override val sha1: ByteArray by lazy { inputStream().sha1() }
|
||||
@ -97,8 +101,11 @@ internal interface ExternalResourceInternal : ExternalResource {
|
||||
internal class ExternalResourceImplByFile(
|
||||
private val file: RandomAccessFile,
|
||||
formatName: String?,
|
||||
closeOriginalFileOnClose: Boolean = true
|
||||
closeOriginalFileOnClose: Boolean = true,
|
||||
) : ExternalResourceInternal {
|
||||
override var origin: Any? = null
|
||||
internal set
|
||||
|
||||
internal class ResourceHolder(
|
||||
@JvmField internal val closeOriginalFileOnClose: Boolean,
|
||||
@JvmField internal val file: RandomAccessFile,
|
||||
@ -146,6 +153,8 @@ internal class ExternalResourceImplByByteArray(
|
||||
?: ExternalResource.DEFAULT_FORMAT_NAME
|
||||
}
|
||||
override val closed: CompletableDeferred<Unit> = CompletableDeferred()
|
||||
override val origin: Any
|
||||
get() = data//.clone()
|
||||
|
||||
override fun inputStream(): InputStream = data.inputStream()
|
||||
override fun close() {
|
||||
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.spi
|
||||
|
||||
import net.mamoe.mirai.utils.*
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* 将源音频文件转换为 silk v3 with tencent 格式
|
||||
*
|
||||
* @since 2.8.0
|
||||
*/
|
||||
@MiraiExperimentalApi
|
||||
public interface AudioToSilkService : BaseService {
|
||||
/**
|
||||
* implementation note:
|
||||
*
|
||||
* 如果返回值为转换后的资源文件:
|
||||
*
|
||||
* 如果 [ExternalResource.isAutoClose], 需要关闭 [source],
|
||||
* 返回的 [ExternalResource] 的 [ExternalResource.isAutoClose] 必须为 `true`
|
||||
*
|
||||
* 特别的, 如果该方法体抛出了一个错误, 如果 [ExternalResource.isAutoClose], 需要关闭 [source]
|
||||
*
|
||||
* @see [withAutoClose]
|
||||
* @see [runAutoClose]
|
||||
* @see [useAutoClose]
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
public suspend fun convert(source: ExternalResource): ExternalResource
|
||||
|
||||
@MiraiExperimentalApi
|
||||
public companion object : AudioToSilkService {
|
||||
private val loader = SPIServiceLoader(object : AudioToSilkService {
|
||||
override suspend fun convert(source: ExternalResource): ExternalResource = source
|
||||
}, AudioToSilkService::class.java)
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
@Throws(IOException::class)
|
||||
override suspend fun convert(source: ExternalResource): ExternalResource {
|
||||
return loader.service.convert(source)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public fun setService(service: AudioToSilkService) {
|
||||
loader.service = service
|
||||
}
|
||||
}
|
||||
}
|
47
mirai-core-api/src/commonMain/kotlin/spi/SPIServiceLoader.kt
Normal file
47
mirai-core-api/src/commonMain/kotlin/spi/SPIServiceLoader.kt
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.spi
|
||||
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalApi
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* 基本 SPI 接口
|
||||
* @since 2.8.0
|
||||
*/
|
||||
@MiraiExperimentalApi
|
||||
public interface BaseService {
|
||||
/** 使用优先级, 值越小越先使用 */
|
||||
public val priority: Int get() = 5
|
||||
}
|
||||
|
||||
internal class SPIServiceLoader<T : BaseService>(
|
||||
@JvmField val defaultService: T,
|
||||
@JvmField val serviceType: Class<T>,
|
||||
) {
|
||||
@JvmField
|
||||
var service: T = defaultService
|
||||
|
||||
fun reload() {
|
||||
val loader = ServiceLoader.load(serviceType)
|
||||
service = loader.minByOrNull { it.priority } ?: defaultService
|
||||
}
|
||||
|
||||
init {
|
||||
reload()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val SPI_SERVICE_LOADER_LOGGER by lazy {
|
||||
MiraiLogger.Factory.create(SPIServiceLoader::class.java, "spi-service-loader")
|
||||
}
|
||||
}
|
||||
}
|
@ -133,6 +133,27 @@ public interface ExternalResource : Closeable {
|
||||
return generateImageId(md5, formatName.ifEmpty { DEFAULT_FORMAT_NAME })
|
||||
}
|
||||
|
||||
/**
|
||||
* 该 [ExternalResource] 的数据来源, 可能有以下的返回
|
||||
*
|
||||
* - [File] 本地文件
|
||||
* - [java.nio.file.Path] 某个具体文件路径
|
||||
* - [java.nio.ByteBuffer] RAM
|
||||
* - [java.net.URI] uri
|
||||
* - [ByteArray] RAM
|
||||
* - Or more...
|
||||
*
|
||||
* implementation note:
|
||||
*
|
||||
* - 对于无法二次读取的数据来源 (如 [InputStream]), 返回 `null`
|
||||
* - 对于一个来自网络的资源, 请返回 [java.net.URI] (not URL, 或者其他库的 URI/URL 类型)
|
||||
* - 不要返回 [String], 没有约定 [String] 代表什么
|
||||
* - 数据源外漏会严重影响 [inputStream] 等的执行的可以返回 `null` (如 [RandomAccessFile])
|
||||
*
|
||||
* @since TODO
|
||||
*/
|
||||
public val origin: Any? get() = null
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* 在无法识别文件格式时使用的默认格式名. "mirai".
|
||||
@ -157,7 +178,9 @@ public interface ExternalResource : Closeable {
|
||||
@JvmName("create")
|
||||
public fun File.toExternalResource(formatName: String? = null): ExternalResource =
|
||||
// although RandomAccessFile constructor throws IOException, actual performance influence is minor so not propagating IOException
|
||||
RandomAccessFile(this, "r").toExternalResource(formatName)
|
||||
RandomAccessFile(this, "r").toExternalResource(formatName).also {
|
||||
it.cast<ExternalResourceImplByFile>().origin = this@toExternalResource
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 [ExternalResource].
|
||||
|
@ -36,6 +36,7 @@ import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.OfflineAudio
|
||||
import net.mamoe.mirai.spi.AudioToSilkService
|
||||
import net.mamoe.mirai.utils.*
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
@ -83,15 +84,17 @@ internal class FriendImpl(
|
||||
|
||||
override fun toString(): String = "Friend($id)"
|
||||
|
||||
override suspend fun uploadAudio(resource: ExternalResource): OfflineAudio = resource.withAutoClose {
|
||||
override suspend fun uploadAudio(resource: ExternalResource): OfflineAudio = AudioToSilkService.convert(
|
||||
resource
|
||||
).useAutoClose { res ->
|
||||
var audio: OfflineAudioImpl? = null
|
||||
kotlin.runCatching {
|
||||
val resp = Highway.uploadResourceBdh(
|
||||
bot = bot,
|
||||
resource = resource,
|
||||
resource = res,
|
||||
kind = ResourceKind.PRIVATE_AUDIO,
|
||||
commandId = 26,
|
||||
extendInfo = PttStore.C2C.createC2CPttStoreBDHExt(bot, this@FriendImpl.uin, resource)
|
||||
extendInfo = PttStore.C2C.createC2CPttStoreBDHExt(bot, this@FriendImpl.uin, res)
|
||||
.toByteArray(Cmd0x346.ReqBody.serializer())
|
||||
)
|
||||
// resp._miraiContentToString("UV resp")
|
||||
@ -100,44 +103,44 @@ internal class FriendImpl(
|
||||
error("Upload failed")
|
||||
}
|
||||
audio = OfflineAudioImpl(
|
||||
filename = "${resource.md5.toUHexString("")}.amr",
|
||||
fileMd5 = resource.md5,
|
||||
fileSize = resource.size,
|
||||
codec = resource.audioCodec,
|
||||
filename = "${res.md5.toUHexString("")}.amr",
|
||||
fileMd5 = res.md5,
|
||||
fileSize = res.size,
|
||||
codec = res.audioCodec,
|
||||
originalPtt = ImMsgBody.Ptt(
|
||||
fileType = 4,
|
||||
srcUin = bot.uin,
|
||||
fileUuid = c346resp.msgApplyUploadRsp.uuid,
|
||||
fileMd5 = resource.md5,
|
||||
fileName = resource.md5 + ".amr".toByteArray(),
|
||||
fileSize = resource.size.toInt(),
|
||||
fileMd5 = res.md5,
|
||||
fileName = res.md5 + ".amr".toByteArray(),
|
||||
fileSize = res.size.toInt(),
|
||||
boolValid = true,
|
||||
)
|
||||
)
|
||||
}.recoverCatchingSuppressed {
|
||||
when (val resp = PttStore.GroupPttUp(bot.client, bot.id, id, resource).sendAndExpect(bot)) {
|
||||
when (val resp = PttStore.GroupPttUp(bot.client, bot.id, id, res).sendAndExpect(bot)) {
|
||||
is PttStore.GroupPttUp.Response.RequireUpload -> {
|
||||
tryServersUpload(
|
||||
bot,
|
||||
resp.uploadIpList.zip(resp.uploadPortList),
|
||||
resource.size,
|
||||
res.size,
|
||||
ResourceKind.GROUP_AUDIO,
|
||||
ChannelKind.HTTP
|
||||
) { ip, port ->
|
||||
Mirai.Http.postPtt(ip, port, resource, resp.uKey, resp.fileKey)
|
||||
Mirai.Http.postPtt(ip, port, res, resp.uKey, resp.fileKey)
|
||||
}
|
||||
audio = OfflineAudioImpl(
|
||||
filename = "${resource.md5.toUHexString("")}.amr",
|
||||
fileMd5 = resource.md5,
|
||||
fileSize = resource.size,
|
||||
codec = resource.audioCodec,
|
||||
filename = "${res.md5.toUHexString("")}.amr",
|
||||
fileMd5 = res.md5,
|
||||
fileSize = res.size,
|
||||
codec = res.audioCodec,
|
||||
originalPtt = ImMsgBody.Ptt(
|
||||
fileType = 4,
|
||||
srcUin = bot.uin,
|
||||
fileUuid = resp.fileId.toByteArray(),
|
||||
fileMd5 = resource.md5,
|
||||
fileName = resource.md5 + ".amr".toByteArray(),
|
||||
fileSize = resource.size.toInt(),
|
||||
fileMd5 = res.md5,
|
||||
fileName = res.md5 + ".amr".toByteArray(),
|
||||
fileSize = res.size.toInt(),
|
||||
boolValid = true,
|
||||
)
|
||||
)
|
||||
|
@ -47,6 +47,7 @@ import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
|
||||
import net.mamoe.mirai.internal.utils.subLogger
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.spi.AudioToSilkService
|
||||
import net.mamoe.mirai.utils.*
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import kotlin.contracts.contract
|
||||
@ -239,19 +240,21 @@ internal class GroupImpl constructor(
|
||||
}
|
||||
|
||||
@Suppress("OverridingDeprecatedMember", "DEPRECATION")
|
||||
override suspend fun uploadVoice(resource: ExternalResource): Voice = resource.withAutoClose {
|
||||
override suspend fun uploadVoice(resource: ExternalResource): Voice = AudioToSilkService.convert(
|
||||
resource
|
||||
).useAutoClose { res ->
|
||||
return bot.network.run {
|
||||
uploadAudioResource(resource)
|
||||
uploadAudioResource(res)
|
||||
|
||||
// val body = resp?.loadAs(Cmd0x388.RspBody.serializer())
|
||||
// ?.msgTryupPttRsp
|
||||
// ?.singleOrNull()?.fileKey ?: error("Group voice highway transfer succeed but failed to find fileKey")
|
||||
|
||||
Voice(
|
||||
"${resource.md5.toUHexString("")}.amr",
|
||||
resource.md5,
|
||||
resource.size,
|
||||
resource.voiceCodec,
|
||||
"${res.md5.toUHexString("")}.amr",
|
||||
res.md5,
|
||||
res.size,
|
||||
res.voiceCodec,
|
||||
""
|
||||
)
|
||||
}
|
||||
@ -284,19 +287,21 @@ internal class GroupImpl constructor(
|
||||
}.getOrThrow()
|
||||
}
|
||||
|
||||
override suspend fun uploadAudio(resource: ExternalResource): OfflineAudio = resource.withAutoClose {
|
||||
override suspend fun uploadAudio(resource: ExternalResource): OfflineAudio = AudioToSilkService.convert(
|
||||
resource
|
||||
).useAutoClose { res ->
|
||||
return bot.network.run {
|
||||
uploadAudioResource(resource)
|
||||
uploadAudioResource(res)
|
||||
|
||||
// val body = resp?.loadAs(Cmd0x388.RspBody.serializer())
|
||||
// ?.msgTryupPttRsp
|
||||
// ?.singleOrNull()?.fileKey ?: error("Group voice highway transfer succeed but failed to find fileKey")
|
||||
|
||||
OfflineAudioImpl(
|
||||
filename = "${resource.md5.toUHexString("")}.amr",
|
||||
fileMd5 = resource.md5,
|
||||
fileSize = resource.size,
|
||||
codec = resource.audioCodec,
|
||||
filename = "${res.md5.toUHexString("")}.amr",
|
||||
fileMd5 = res.md5,
|
||||
fileSize = res.size,
|
||||
codec = res.audioCodec,
|
||||
originalPtt = null,
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user