feat: force voice note audio format

- add custom ffmpeg options
This commit is contained in:
rhunk
2023-09-01 15:10:14 +02:00
parent 94d064e0a5
commit 89a1552277
12 changed files with 228 additions and 68 deletions

View File

@ -115,10 +115,44 @@
"name": "Force Image Format",
"description": "Forces images to be saved as a specific format"
},
"force_voice_note_format": {
"name": "Force Voice Note Format",
"description": "Forces voice notes to be saved as a specific format"
},
"chat_download_context_menu": {
"name": "Chat Download Context Menu",
"description": "Allows to download messages from a conversation by long pressing them"
},
"ffmpeg_options": {
"name": "FFmpeg Options",
"description": "Specify additional FFmpeg options",
"properties": {
"threads": {
"name": "Threads",
"description": "The amount of threads to use"
},
"preset": {
"name": "Preset",
"description": "Set the speed of the conversion"
},
"constant_rate_factor": {
"name": "Constant Rate Factor",
"description": "Set the constant rate factor for the video encoder\nFrom 0 to 51 for libx264 (lower to higher quality)"
},
"video_bitrate": {
"name": "Video Bitrate",
"description": "Set the video bitrate (in kbps)"
},
"custom_video_codec": {
"name": "Custom Video Codec",
"description": "Set a custom video codec (e.g. libx264)"
},
"custom_audio_codec": {
"name": "Custom Audio Codec",
"description": "Set a custom audio codec (e.g. aac)"
}
}
},
"logging": {
"name": "Logging",
"description": "Shows toasts when media is downloading"

View File

@ -65,6 +65,7 @@ class PropertyValue<T>(
fun isSet() = value != null
fun getNullable() = value?.takeIf { it != "null" }
fun isEmpty() = value == null || value == "null" || value.toString().isEmpty()
fun get() = getNullable() ?: throw IllegalStateException("Property is not set")
fun set(value: T?) { this.value = value }
@Suppress("UNCHECKED_CAST")

View File

@ -5,6 +5,17 @@ import me.rhunk.snapenhance.core.config.ConfigFlag
import me.rhunk.snapenhance.core.config.FeatureNotice
class DownloaderConfig : ConfigContainer() {
inner class FFMpegOptions : ConfigContainer() {
val threads = integer("threads", 1)
val preset = unique("preset", "ultrafast", "superfast", "veryfast", "faster", "fast", "medium", "slow", "slower", "veryslow") {
addFlags(ConfigFlag.NO_TRANSLATE)
}
val constantRateFactor = integer("constant_rate_factor", 30)
val videoBitrate = integer("video_bitrate", 5000)
val customVideoCodec = string("custom_video_codec") { addFlags(ConfigFlag.NO_TRANSLATE) }
val customAudioCodec = string("custom_audio_codec") { addFlags(ConfigFlag.NO_TRANSLATE) }
}
val saveFolder = string("save_folder") { addFlags(ConfigFlag.FOLDER) }
val autoDownloadSources = multiple("auto_download_sources",
"friend_snaps",
@ -26,7 +37,11 @@ class DownloaderConfig : ConfigContainer() {
val forceImageFormat = unique("force_image_format", "jpg", "png", "webp") {
addFlags(ConfigFlag.NO_TRANSLATE)
}
val forceVoiceNoteFormat = unique("force_voice_note_format", "aac", "mp3", "opus") {
addFlags(ConfigFlag.NO_TRANSLATE)
}
val chatDownloadContextMenu = boolean("chat_download_context_menu")
val ffmpegOptions = container("ffmpeg_options", FFMpegOptions()) { addNotices(FeatureNotice.UNSTABLE) }
val logging = multiple("logging", "started", "success", "progress", "failure").apply {
set(mutableListOf("started", "success"))
}

View File

@ -10,6 +10,7 @@ import me.rhunk.snapenhance.core.download.data.DownloadMetadata
import me.rhunk.snapenhance.core.download.data.DownloadRequest
import me.rhunk.snapenhance.core.download.data.InputMedia
import me.rhunk.snapenhance.core.download.data.MediaEncryptionKeyPair
import me.rhunk.snapenhance.data.ContentType
class DownloadManagerClient (
private val context: ModContext,
@ -45,15 +46,21 @@ class DownloadManagerClient (
)
}
fun downloadSingleMedia(mediaData: String, mediaType: DownloadMediaType, encryption: MediaEncryptionKeyPair? = null) {
fun downloadSingleMedia(
mediaData: String,
mediaType: DownloadMediaType,
encryption: MediaEncryptionKeyPair? = null,
messageContentType: ContentType? = null
) {
enqueueDownloadRequest(
DownloadRequest(
inputMedias = arrayOf(
InputMedia(
content = mediaData,
type = mediaType,
encryption = encryption
)
content = mediaData,
type = mediaType,
encryption = encryption,
messageContentType = messageContentType?.name
)
)
)
)

View File

@ -5,7 +5,8 @@ data class DashOptions(val offsetTime: Long, val duration: Long?)
data class InputMedia(
val content: String,
val type: DownloadMediaType,
val encryption: MediaEncryptionKeyPair? = null
val encryption: MediaEncryptionKeyPair? = null,
val messageContentType: String? = null,
)
class DownloadRequest(

View File

@ -1,5 +1,6 @@
package me.rhunk.snapenhance.data
import me.rhunk.snapenhance.Logger
import java.io.File
import java.io.InputStream
@ -14,6 +15,8 @@ enum class FileType(
PNG("png", "image/png", false, true, false),
MP4("mp4", "video/mp4", true, false, false),
MP3("mp3", "audio/mp3",false, false, true),
OPUS("opus", "audio/opus", false, false, true),
AAC("aac", "audio/aac", false, false, true),
JPG("jpg", "image/jpg",false, true, false),
ZIP("zip", "application/zip", false, false, false),
WEBP("webp", "image/webp", false, true, false),
@ -25,9 +28,12 @@ enum class FileType(
"52494646" to WEBP,
"504b0304" to ZIP,
"89504e47" to PNG,
"00000020" to MP4,
"00000020" to MP4,
"00000018" to MP4,
"0000001c" to MP4,
"494433" to MP3,
"4f676753" to OPUS,
"fff15" to AAC,
"ffd8ff" to JPG,
)
@ -55,7 +61,9 @@ enum class FileType(
val headerBytes = ByteArray(16)
System.arraycopy(array, 0, headerBytes, 0, 16)
val hex = bytesToHex(headerBytes)
return fileSignatures.entries.firstOrNull { hex.startsWith(it.key) }?.value ?: UNKNOWN
return fileSignatures.entries.firstOrNull { hex.startsWith(it.key) }?.value ?: UNKNOWN.also {
Logger.directDebug("unknown file type, header: $hex", "FileType")
}
}
fun fromInputStream(inputStream: InputStream): FileType {

View File

@ -473,9 +473,10 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
mediaAuthor = authorName,
friendInfo = friendInfo
).downloadSingleMedia(
Base64.UrlSafe.encode(urlProto),
DownloadMediaType.PROTO_MEDIA,
encryption = encryptionKeys?.toKeyPair()
mediaData = Base64.UrlSafe.encode(urlProto),
mediaType = DownloadMediaType.PROTO_MEDIA,
encryption = encryptionKeys?.toKeyPair(),
messageContentType = contentType
)
return
}

View File

@ -1,8 +1,5 @@
package me.rhunk.snapenhance.util.snap
import com.arthenica.ffmpegkit.FFmpegKit
import com.arthenica.ffmpegkit.FFmpegSession
import kotlinx.coroutines.suspendCancellableCoroutine
import me.rhunk.snapenhance.Constants
import me.rhunk.snapenhance.core.download.data.SplitMediaAssetType
import me.rhunk.snapenhance.data.ContentType
@ -10,10 +7,8 @@ import me.rhunk.snapenhance.data.FileType
import me.rhunk.snapenhance.util.download.RemoteMediaResolver
import me.rhunk.snapenhance.util.protobuf.ProtoReader
import java.io.ByteArrayInputStream
import java.io.File
import java.io.FileNotFoundException
import java.io.InputStream
import java.util.concurrent.Executors
import java.util.zip.ZipInputStream
@ -69,43 +64,4 @@ object MediaDownloaderHelper {
return mapOf(SplitMediaAssetType.ORIGINAL to content)
}
private suspend fun runFFmpegAsync(vararg args: String) = suspendCancellableCoroutine<FFmpegSession> {
FFmpegKit.executeAsync(args.joinToString(" "), { session ->
it.resumeWith(
if (session.returnCode.isValueSuccess) {
Result.success(session)
} else {
Result.failure(Exception(session.output))
}
)
},
Executors.newSingleThreadExecutor())
}
//TODO: implement setting parameters
suspend fun downloadDashChapterFile(
dashPlaylist: File,
output: File,
startTime: Long,
duration: Long?) {
runFFmpegAsync(
"-y", "-i", dashPlaylist.absolutePath, "-ss", "'${startTime}ms'", *(if (duration != null) arrayOf("-t", "'${duration}ms'") else arrayOf()),
"-c:v", "libx264", "-preset", "ultrafast", "-threads", "6", "-q:v", "13", output.absolutePath
)
}
suspend fun mergeOverlayFile(
media: File,
overlay: File,
output: File
) {
runFFmpegAsync(
"-y", "-i", media.absolutePath, "-i", overlay.absolutePath,
"-filter_complex", "\"[0]scale2ref[img][vid];[img]setsar=1[img];[vid]nullsink;[img][1]overlay=(W-w)/2:(H-h)/2,scale=2*trunc(iw*sar/2):2*trunc(ih/2)\"",
"-c:v", "libx264", "-b:v", "5M", "-c:a", "copy", "-preset", "ultrafast", "-threads", "6", output.absolutePath
)
}
}