Skip to content

Commit

Permalink
Added test classes #2
Browse files Browse the repository at this point in the history
Covered all the publicly documented API of rev.ai #1
  • Loading branch information
shuklaalok7 committed Apr 20, 2020
1 parent 5388707 commit f519b52
Show file tree
Hide file tree
Showing 11 changed files with 326 additions and 25 deletions.
12 changes: 12 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
<artifactId>spring-websocket</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>

<!-- <dependency>-->
<!-- <groupId>org.springframework</groupId>-->
<!-- <artifactId>spring-messaging</artifactId>-->
Expand All @@ -117,12 +118,23 @@
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20190722</version>
</dependency>

<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.13.1</version>
</dependency>

<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.1.1</version>
</dependency>

<!-- <dependency>-->
<!-- <groupId>io.reactivex.rxjava3</groupId>-->
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/RevAi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

package ai.rev.streaming

import ai.rev.streaming.ai.rev.streaming.clients.AsyncClient
import ai.rev.streaming.ai.rev.streaming.clients.AsyncClientImpl
import ai.rev.streaming.clients.AsyncClient
import ai.rev.streaming.clients.AsyncClientImpl
import ai.rev.streaming.clients.StreamingClient
import ai.rev.streaming.clients.StreamingClientImpl
import ai.rev.streaming.models.ClientConfig
Expand All @@ -31,7 +31,7 @@ import ai.rev.streaming.models.ClientConfig
* @author shuklaalok7 (alok@clay.fish)
* @since v0.1.0 2020-03-29 18:20 IST
*/
class RevAi constructor(private val clientConfig: ClientConfig) : AutoCloseable {
class RevAi constructor(clientConfig: ClientConfig) : AutoCloseable {
val asyncClient: AsyncClient = AsyncClientImpl(clientConfig)
val streamingClient: StreamingClient = StreamingClientImpl(clientConfig)

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/SessionHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ internal class SessionHandler(private val config: ClientConfig) : TextWebSocketH
override fun handleTextMessage(session: WebSocketSession, message: TextMessage) {
logger.info("Text message received\n$message")
this.session = session
val data = AppUtils.convertToRevAiResponse(message)
val data = AppUtils.convertToStreamingResponse(message)
if (state.get() == State.READY) config.callback.invoke(data)
else if (data.type == "connected" && state.get() != State.CLOSING && state.get() != State.CLOSED) state.set(State.READY)
}
Expand Down
23 changes: 21 additions & 2 deletions src/main/java/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package ai.rev.streaming

import ai.rev.streaming.models.AudioContentType
import ai.rev.streaming.models.ClientConfig
import ai.rev.streaming.models.RevAiResponse
import ai.rev.streaming.models.StreamingResponse
import com.google.gson.FieldNamingPolicy
import com.google.gson.Gson
import com.google.gson.GsonBuilder
Expand All @@ -30,6 +30,10 @@ import org.springframework.web.socket.WebSocketHttpHeaders
import org.springframework.web.socket.client.WebSocketClient
import org.springframework.web.socket.client.standard.StandardWebSocketClient
import java.net.URI
import java.util.*
import javax.ws.rs.client.Client
import javax.ws.rs.client.Invocation
import javax.ws.rs.core.MediaType


/**
Expand Down Expand Up @@ -91,9 +95,24 @@ internal object AppUtils {

val gson: Gson = GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create()

/**
*
*/
fun createInvocation(client: Client, uri: String, config: ClientConfig, vararg queryParam: Pair<String, String>): Invocation.Builder =
client.target("https://${config.baseUrl}$uri").apply {
queryParam.forEach { this.queryParam(it.first, it.second) }
}.request(MediaType.APPLICATION_JSON_TYPE)
.accept(MediaType.APPLICATION_JSON_TYPE)
.acceptEncoding("gzip", "deflate", "sdch", "br")
.acceptLanguage(Locale.US)
.header("Authorization", "Bearer ${config.accessToken}")
.header("Accept-Language", "en-GB,en;q=0.8,en-US;q=0.6,hi;q=0.4")
.header("Connection", "keep-alive")
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36")

/**
* @param message [TextMessage] received from rev.ai streaming API over the websocket
* @return Response that this library generates
*/
fun convertToRevAiResponse(message: TextMessage): RevAiResponse = gson.fromJson(message.payload, RevAiResponse::class.java)
fun convertToStreamingResponse(message: TextMessage): StreamingResponse = gson.fromJson(message.payload, StreamingResponse::class.java)
}
94 changes: 77 additions & 17 deletions src/main/java/ai/rev/streaming/clients/AsyncClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
package ai.rev.streaming.ai.rev.streaming.clients
package ai.rev.streaming.clients

import ai.rev.streaming.AppUtils
import ai.rev.streaming.models.*
import org.json.JSONObject
import java.util.concurrent.TimeUnit
import javax.ws.rs.client.Client
import javax.ws.rs.client.ClientBuilder
import javax.ws.rs.client.Entity
import javax.ws.rs.core.GenericType
import javax.ws.rs.core.MediaType

/**
* @author shuklaalok7 (alok@clay.fish)
Expand All @@ -27,6 +35,8 @@ interface AsyncClient {
* [Documentation](https://www.rev.ai/docs#tag/Account)
*
* Get the developer's account information.
*
* @return Details about the developer's account
*/
fun getAccount(): Account?

Expand All @@ -44,8 +54,9 @@ interface AsyncClient {
* @param speakerChannel Identifies which channel of the job output to caption. Default is `null` which works
* only for jobs with no `speaker_channels_count` provided during job submission.
* @param format MIME type specifying the caption output format
* @return String in the specified format
*/
fun getCaptions(id: String, speakerChannel: Int? = null, format: CaptionFormat = CaptionFormat.VTT)
fun getCaptions(id: String, speakerChannel: Int? = null, format: CaptionFormat = CaptionFormat.VTT): String?

/**
* [Documentation](https://www.rev.ai/docs#tag/Transcript)
Expand All @@ -60,8 +71,9 @@ interface AsyncClient {
*
* @param id Rev.ai API Job Id
* @param format MIME type specifying the transcription output format
* @return The transcript // todo the transcript has JSON structure, try to put it in POJO
*/
fun getTranscript(id: String, format: TranscriptFormat = TranscriptFormat.JSON)
fun getTranscript(id: String, format: TranscriptFormat = TranscriptFormat.JSON): JSONObject?

/**
* [Documentation](https://www.rev.ai/docs#operation/GetJobById)
Expand Down Expand Up @@ -114,25 +126,54 @@ interface AsyncClient {
* @author shuklaalok7 (alok@clay.fish)
* @since v0.2.0 2020-04-19 08:46 PM IST
*/
internal class AsyncClientImpl(private val clientConfig: ClientConfig) : AsyncClient {
override fun getAccount(): Account? {
TODO("Not yet implemented")
internal class AsyncClientImpl(private val config: ClientConfig) : AsyncClient {

private val client: Client = ClientBuilder.newBuilder().connectTimeout(config.timeout, TimeUnit.SECONDS)
.readTimeout(config.timeout, TimeUnit.SECONDS).build()

override fun getAccount(): Account? = try {
AppUtils.createInvocation(client, "/account", config).get(Account::class.java)
} catch (e: Exception) {
// todo inspect
logger.error("Error occurred in getting account", e)
null
}

override fun getCaptions(id: String, speakerChannel: Int?, format: CaptionFormat) {
TODO("Not yet implemented")
override fun getCaptions(id: String, speakerChannel: Int?, format: CaptionFormat): String? = try {
val invocationBuilder = if (speakerChannel == null) AppUtils.createInvocation(client, "/jobs/$id/captions", config)
else AppUtils.createInvocation(client, "/jobs/$id/captions", config, Pair("speaker_channel", "$speakerChannel"))

invocationBuilder.accept(format.mime).get().entity.toString()
} catch (e: Exception) {
// todo inspect
logger.error("Error occurred in getting captions for job $id", e)
null
}

override fun getTranscript(id: String, format: TranscriptFormat) {
TODO("Not yet implemented")
override fun getTranscript(id: String, format: TranscriptFormat): JSONObject? = try {
JSONObject(AppUtils.createInvocation(client, "/jobs/$id/transcript", config).accept(format.mime)
.get().entity.toString())
} catch (e: Exception) {
// todo inspect
logger.error("Error occurred in getting transcript for job $id", e)
null
}

override fun get(id: String): Job? {
TODO("Not yet implemented")
override fun get(id: String): Job? = try {
AppUtils.createInvocation(client, "/jobs/$id", config).get(Job::class.java)
} catch (e: Exception) {
// todo inspect
logger.error("Error occurred in getting job with $id", e)
null
}

override fun delete(id: String): Boolean {
TODO("Not yet implemented")
override fun delete(id: String): Boolean = try {
val status = AppUtils.createInvocation(client, "/jobs/$id", config).delete().status
status in 200..299
} catch (e: Exception) {
// todo inspect
logger.error("Error occurred in deleting job with $id", e)
false
}

override fun getJobs(limit: Int, startingAfter: String?): List<Job> {
Expand All @@ -141,11 +182,30 @@ internal class AsyncClientImpl(private val clientConfig: ClientConfig) : AsyncCl
if (limit1 == 0) return emptyList()

// todo implement
return emptyList()
return try {
val queryParams = arrayListOf(Pair("limit", "$limit1"))
if (startingAfter != null) queryParams.add(Pair("starting_after", startingAfter))

// May need to use Jackson instead of JaxB: https://stackoverflow.com/questions/9627170/cannot-unmarshal-a-json-array-of-objects-using-jersey-client
AppUtils.createInvocation(client, "/jobs", config, *queryParams.toTypedArray()).get(object : GenericType<List<Job>>() {})
} catch (e: Exception) {
// todo inspect
logger.error("Error occurred in getting jobs", e)
emptyList()
}
}

override fun submitJob(jobRequest: JobRequest): Job? = try {
AppUtils.createInvocation(client, "/jobs", config)
.post(Entity.entity(jobRequest, MediaType.APPLICATION_JSON_TYPE), Job::class.java)
} catch (e: Exception) {
// todo inspect
logger.error("Error occurred in submitting job", e)
null
}

override fun submitJob(jobRequest: JobRequest): Job? {
TODO("Not yet implemented")
companion object {
private val logger = AppUtils.getLogger<AsyncClientImpl>()
}

}
27 changes: 27 additions & 0 deletions src/main/java/ai/rev/streaming/models/JobModels.kt
Original file line number Diff line number Diff line change
Expand Up @@ -148,5 +148,32 @@ data class JobRequest(
*/
val customVocabularies: Array<String>?
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as JobRequest

if (mediaUrl != other.mediaUrl) return false
if (skipDiarization != other.skipDiarization) return false
if (skipPunctuation != other.skipPunctuation) return false
if (removeDisfluencies != other.removeDisfluencies) return false
if (filterProfanity != other.filterProfanity) return false
if (speakerChannelsCount != other.speakerChannelsCount) return false
if (callbackUrl != other.callbackUrl) return false

return true
}

override fun hashCode(): Int {
var result = mediaUrl.hashCode()
result = 31 * result + (skipDiarization?.hashCode() ?: 0)
result = 31 * result + (skipPunctuation?.hashCode() ?: 0)
result = 31 * result + (removeDisfluencies?.hashCode() ?: 0)
result = 31 * result + (filterProfanity?.hashCode() ?: 0)
result = 31 * result + (speakerChannelsCount ?: 0)
result = 31 * result + (callbackUrl?.hashCode() ?: 0)
return result
}

}
9 changes: 7 additions & 2 deletions src/main/java/ai/rev/streaming/models/StreamingModels.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,19 @@ data class ClientConfig(
/**
* Handle the response obtained from rev.ai
*/
val callback: (RevAiResponse) -> Unit,
val callback: (StreamingResponse) -> Unit,

/**
* In case the rev.ai API needs to be changed. Do not use `http`, `https`, `ws`, or `wss` in the beginning. The
* protocol scheme will be added while making requests to the API server.
*/
val baseUrl: String = "api.rev.ai/speechtotext/v1",

/**
* Timeout when connecting with rev.ai API in seconds.
*/
val timeout: Long = 60,

/**
* Size of the buffer in bytes to read from the input-stream passed to [RevAi.stream].
*/
Expand Down Expand Up @@ -92,7 +97,7 @@ data class RawParameters(
* @author shuklaalok7 (alok@clay.fish)
* @since v0.1.0 2020-03-29 07:54 PM IST
*/
data class RevAiResponse(
data class StreamingResponse(
val id: String?,
val type: String?,
val elements: List<Element>?,
Expand Down
Empty file removed src/test/java/.gitkeep
Empty file.
77 changes: 77 additions & 0 deletions src/test/java/ai/rev/api/test/clients/AsyncClientTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
Copyright 2020 ClayFish Technologies
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package ai.rev.api.test.clients

import ai.rev.streaming.RevAi
import ai.rev.streaming.clients.AsyncClient
import ai.rev.streaming.models.AudioContentType
import ai.rev.streaming.models.ClientConfig
import ai.rev.streaming.models.JobRequest
import org.junit.Test
import kotlin.test.BeforeTest
import kotlin.test.assertNotNull

/**
* @author shuklaalok7 (alok@clay.fish)
* @since v0.2.0 2020-04-21 04:53 AM IST
*/
class AsyncClientTest {

private lateinit var asyncClient: AsyncClient

@BeforeTest
fun initialize() {
asyncClient = RevAi(ClientConfig("", AudioContentType.FLAC, {})).asyncClient
}

@Test
fun testGetAccount() {

}

@Test
fun testGetCaptions() {

}

@Test
fun testGetTranscript() {

}

@Test
fun testGet() {

}

@Test
fun testDelete() {

}

@Test
fun testGetJobs() {

}

@Test
fun testSubmitJob() {
assertNotNull(asyncClient.submitJob(JobRequest("", speakerChannelsCount = null, callbackUrl = null,
customVocabularies = null)))
}

}
Loading

0 comments on commit f519b52

Please sign in to comment.