Skip to content

Commit

Permalink
Re-instate clip command (#15)
Browse files Browse the repository at this point in the history
* Prepare for re-instating clip command

* Restore clip command for clip length longer than current recording

* Add bytes per second constant for use with clip

* Disambiguate recordingSize(global) with recordingSize(local)

* Delete dummy main used for experimenting clips

* BYTES_PER_SECOND should be Long, and change log to debug for rec size

* Implement clip by copying tape and trimming until correct length
  • Loading branch information
jvtrigueros authored Dec 31, 2017
1 parent 3bd8cea commit b6951aa
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 11 deletions.
4 changes: 2 additions & 2 deletions src/main/java/tech/gdragon/DiscordBot.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import net.dv8tion.jda.core.Permission;
import net.dv8tion.jda.core.exceptions.RateLimitedException;
import tech.gdragon.commands.CommandHandler;
import tech.gdragon.commands.audio.ClipCommand;
import tech.gdragon.commands.audio.Clip;
import tech.gdragon.commands.audio.EchoCommand;
import tech.gdragon.commands.audio.MessageInABottleCommand;
import tech.gdragon.commands.audio.Save;
Expand All @@ -33,7 +33,7 @@ public DiscordBot(String token) {
CommandHandler.commands.put("leave", new Leave());

// Register audio commands
CommandHandler.commands.put("clip", new ClipCommand());
CommandHandler.commands.put("clip", new Clip());
CommandHandler.commands.put("echo", new EchoCommand());
CommandHandler.commands.put("miab", new MessageInABottleCommand());
CommandHandler.commands.put("save", new Save());
Expand Down
57 changes: 57 additions & 0 deletions src/main/kotlin/tech/gdragon/commands/audio/Clip.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package tech.gdragon.commands.audio

import net.dv8tion.jda.core.events.message.guild.GuildMessageReceivedEvent
import org.jetbrains.exposed.sql.transactions.transaction
import tech.gdragon.BotUtils
import tech.gdragon.commands.Command
import tech.gdragon.db.dao.Guild
import tech.gdragon.listener.CombinedAudioRecorderHandler

class Clip : Command {
override fun action(args: Array<out String>, event: GuildMessageReceivedEvent) {
val prefix = transaction {
val guild = Guild.findById(event.guild.idLong)
guild?.settings?.prefix ?: "!"
}

require(args.size in 1..2) {
BotUtils.sendMessage(event.channel, usage(prefix))
}

val message =
if (event.guild.audioManager.connectedChannel == null) {
"I wasn't recording!"
} else {
val voiceChannel = event.guild.audioManager.connectedChannel.name
val audioReceiveHandler = event.guild.audioManager.receiveHandler as CombinedAudioRecorderHandler

try {
val seconds = args.first().toLong()

if (args.size == 1) {
audioReceiveHandler.saveClip(seconds, voiceChannel, event.channel)
""
} else {
val channelName = if (args[1].startsWith("#")) args[1].substring(1) else args[1]
val channels = event.guild.getTextChannelsByName(channelName, true)

if (channels.isEmpty()) {
"Cannot find $channelName."
} else {
channels.forEach { audioReceiveHandler.saveClip(seconds, voiceChannel, event.channel) }
""
}
}
} catch (e: NumberFormatException) {
usage(prefix)
}
}

if (message.isNotBlank())
BotUtils.sendMessage(event.channel, message)
}

override fun usage(prefix: String): String = "${prefix}clip [seconds] | ${prefix}clip [seconds] [text channel output]"

override fun description(): String = "Saves a clip of the specified length (seconds) and outputs it in the current or specified text channel."
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,21 @@ import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.lang.Thread.sleep
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.concurrent.thread

class CombinedAudioRecorderHandler(val volume: Double, val voiceChannel: VoiceChannel) : AudioReceiveHandler {
companion object {
private const val AFK_LIMIT = (2 * 60 * 1000) / 20 // 2 minutes in ms over 20ms increments
private const val MAX_RECORDING_SIZE = 8 * 1024 * 1024 // 8MB
private const val MAX_RECORDING_SIZE = 8 * 1024 * 1024 // 8MB
private const val BUFFER_TIMEOUT = 200L // 200 milliseconds
private const val BUFFER_MAX_COUNT = 8
private const val BITRATE = 128 // 128 kpbs
private const val BYTES_PER_SECOND = 16_000L // 128 kbps == 16000 bytes per second
}

private val logger = LoggerFactory.getLogger(this.javaClass)
Expand All @@ -43,8 +48,9 @@ class CombinedAudioRecorderHandler(val volume: Double, val voiceChannel: VoiceCh
private var canReceive = true
private var afkCounter = 0

var filename: String? = null
private var filename: String? = null
private var queueFilename: String? = null
private var recordingSize: Int = 0

init {
subscription = createRecording()
Expand Down Expand Up @@ -84,8 +90,7 @@ class CombinedAudioRecorderHandler(val volume: Double, val voiceChannel: VoiceCh
queueFile = QueueFile(File(queueFilename))
canReceive = true

var recordingSize = 0
val encoder = LameEncoder(AudioReceiveHandler.OUTPUT_FORMAT, 128, LameEncoder.CHANNEL_MODE_AUTO, LameEncoder.QUALITY_HIGHEST, false)
val encoder = LameEncoder(AudioReceiveHandler.OUTPUT_FORMAT, BITRATE, LameEncoder.CHANNEL_MODE_AUTO, LameEncoder.QUALITY_HIGHEST, false)


return subject
Expand All @@ -108,7 +113,6 @@ class CombinedAudioRecorderHandler(val volume: Double, val voiceChannel: VoiceCh
recordingSize -= queue.peek()?.size ?: 0
queue.remove()
}

queue.add(bytes)
recordingSize += bytes.size
})
Expand Down Expand Up @@ -137,17 +141,57 @@ class CombinedAudioRecorderHandler(val volume: Double, val voiceChannel: VoiceCh
}
}

val recordingSize = recording.length().toDouble() / 1024 / 1024
val recordingSizeInMB = recording.length().toDouble() / 1024 / 1024

logger.info("Saving audio file '{}' from {} on {} of size {} MB.",
recording.name, voiceChannel?.name, voiceChannel?.guild?.name, recordingSize)
recording.name, voiceChannel?.name, voiceChannel?.guild?.name, recordingSizeInMB)
logger.debug("Recording size in bytes: {}", recordingSize)

uploadRecording(recording, recordingSize, voiceChannel?.name, voiceChannel?.guild?.name, textChannel)
uploadRecording(recording, recordingSizeInMB, voiceChannel?.name, voiceChannel?.guild?.name, textChannel)

// Resume recording
subscription = createRecording()
}

fun saveClip(seconds: Long, voiceChannel: String?, channel: TextChannel?) {
// Stop recording so that we can copy Queue File
canReceive = false

val path = Paths.get(queueFilename)
val clipPath = Paths.get("recordings/clip-${UUID.randomUUID()}.queue")

// Copy the original Queue File so that we can resume receiving audio
Files.copy(path, clipPath, StandardCopyOption.REPLACE_EXISTING)
canReceive = true

val queueFile = QueueFile(clipPath.toFile())
val recording = File(clipPath.toString().replace("queue", "mp3"))
var clipRecordingSize = recordingSize.toLong()

// Reduce the queue size until it's just over the expected clip size
while(clipRecordingSize - queueFile.peek().size > BYTES_PER_SECOND * seconds) {
queueFile.remove()
clipRecordingSize -= queueFile.peek().size
}

FileOutputStream(recording).use {
queueFile.apply {
forEach({ stream, _ ->
stream.transferTo(it)
})

close()
Files.delete(clipPath)
}
}

val recordingSizeInMB = recording.length().toDouble() / 1024 / 1024
logger.info("Saving audio file '{}' from {} on {} of size {} MB.",
recording.name, voiceChannel, channel?.guild?.name, recordingSizeInMB)

uploadRecording(recording, recordingSizeInMB, voiceChannel, channel?.guild?.name, channel)
}

private fun uploadRecording(recording: File, recordingSize: Double, voiceChannelName: String?, guildName: String?, channel: TextChannel?) {
if (recording.length() < MAX_RECORDING_SIZE) {

Expand All @@ -172,7 +216,7 @@ class CombinedAudioRecorderHandler(val volume: Double, val voiceChannel: VoiceCh
logger.info("Deleting file {}...", recording.name)

if (isDeleteSuccess)
logger.info("Successfully deleted file {}. ", recording.name)
logger.info("Successfully deleted file {}.", recording.name)
else
logger.error("Could not delete file {}.", recording.name)
}
Expand Down

0 comments on commit b6951aa

Please sign in to comment.