mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-06-12 13:17:42 +02:00
feat: force voice note audio format
- add custom ffmpeg options
This commit is contained in:
@ -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"
|
||||
|
@ -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")
|
||||
|
@ -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"))
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -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(
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user