Compare commits
No commits in common. "6d7706dd5bf4273b5d40069f5f37ca83a9f8a650" and "0ae216e9bc3f3133d53191c38c6bbe88709aa2d6" have entirely different histories.
6d7706dd5b
...
0ae216e9bc
11
app.js
11
app.js
|
@ -10,19 +10,22 @@ const morgan = require('morgan')
|
||||||
// const fileUpload = require('express-fileupload')
|
// const fileUpload = require('express-fileupload')
|
||||||
|
|
||||||
const rateLimiter = require('express-rate-limit')
|
const rateLimiter = require('express-rate-limit')
|
||||||
|
const helmet = require('helmet')
|
||||||
|
const xss = require('xss-clean')
|
||||||
|
const cors = require('cors')
|
||||||
|
|
||||||
// Swagger
|
// Swagger
|
||||||
const swaggerUI = require('swagger-ui-express')
|
const swaggerUI = require('swagger-ui-express')
|
||||||
const YAML = require('yamljs')
|
const YAML = require('yamljs')
|
||||||
const swaggerDocument = YAML.load('./swagger.yaml')
|
const swaggerDocument = YAML.load('./swagger.yaml')
|
||||||
|
|
||||||
const helmet = require('helmet')
|
// database
|
||||||
const xss = require('xss-clean')
|
const connectDB = require('./db/connect')
|
||||||
const cors = require('cors')
|
|
||||||
|
|
||||||
// routers
|
// routers
|
||||||
const nlRouter = require('./routes/naturalLanguageRoute')
|
const nlRouter = require('./routes/naturalLanguageRoute')
|
||||||
|
|
||||||
|
|
||||||
const notFoundMiddlware = require('./middleware/not-found')
|
const notFoundMiddlware = require('./middleware/not-found')
|
||||||
const errorHandlerMiddleware = require('./middleware/error-handler')
|
const errorHandlerMiddleware = require('./middleware/error-handler')
|
||||||
|
|
||||||
|
@ -41,8 +44,10 @@ app.use(xss())
|
||||||
app.use(morgan('tiny'))
|
app.use(morgan('tiny'))
|
||||||
app.use(express.json())
|
app.use(express.json())
|
||||||
|
|
||||||
|
// app.use(express.static('./public'))
|
||||||
// app.use(fileUpload())
|
// app.use(fileUpload())
|
||||||
|
|
||||||
|
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
res.send('<h1>Sentiment API</h1><a href="/api-docs">Documentation</a>')
|
res.send('<h1>Sentiment API</h1><a href="/api-docs">Documentation</a>')
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,16 +1,8 @@
|
||||||
const { StatusCodes } = require("http-status-codes")
|
const { StatusCodes } = require("http-status-codes")
|
||||||
const { sentiment, convertTextToSpeech, listVoice, convertAudioToLinear16, getAudioDuration } = require("../utils")
|
const { sentiment, convertTextToSpeech, listVoice } = require("../utils")
|
||||||
const language = require('@google-cloud/language').v2
|
const language = require('@google-cloud/language').v2
|
||||||
const CustomError = require('../errors')
|
const CustomError = require('../errors')
|
||||||
const voiceConfigList = require('../mockData/voice.json')
|
const voiceConfigList = require('../mockData/voice.json')
|
||||||
const languageCodes = require('../mockData/languageCodes.json')
|
|
||||||
const path = require('path')
|
|
||||||
const fs = require('fs')
|
|
||||||
const speech = require('@google-cloud/speech')
|
|
||||||
const { speechToText, speechToTextJob } = require('../utils')
|
|
||||||
const client = new speech.SpeechClient()
|
|
||||||
const protobuf = require('protobufjs')
|
|
||||||
|
|
||||||
|
|
||||||
const getSentiment = async (req, res) => {
|
const getSentiment = async (req, res) => {
|
||||||
|
|
||||||
|
@ -56,7 +48,9 @@ const getAudioFromText = async (req, res) => {
|
||||||
|
|
||||||
// Set the Content-Disposition header
|
// Set the Content-Disposition header
|
||||||
// res.set("Content-Disposition", `attachment; filename="${filename}"`);
|
// res.set("Content-Disposition", `attachment; filename="${filename}"`);
|
||||||
res.set("Content-Disposition", `inline; filename="${filename}"`)
|
res.set("Content-Disposition", `inline; filename="${filename}"`);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
res.contentType('audio/mpeg')
|
res.contentType('audio/mpeg')
|
||||||
|
|
||||||
|
@ -64,134 +58,6 @@ const getAudioFromText = async (req, res) => {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTextFromAudio = async (req, res) => {
|
|
||||||
|
|
||||||
const { languageCode } = req.body
|
|
||||||
|
|
||||||
const audio = req.file
|
|
||||||
|
|
||||||
if (!audio)
|
|
||||||
throw new CustomError.BadRequestError(`Missing the audio file`)
|
|
||||||
|
|
||||||
if (languageCode) {
|
|
||||||
const existLanguageCode = languageCodes.find(l => l.languageCode == languageCode)
|
|
||||||
|
|
||||||
if (!existLanguageCode) {
|
|
||||||
fs.unlinkSync(audio.path)
|
|
||||||
throw new CustomError.BadRequestError(`Invalid language code`)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
const inputFile = path.resolve(audio.path)
|
|
||||||
|
|
||||||
const fileName = path.basename(inputFile, path.extname(inputFile))
|
|
||||||
|
|
||||||
const outputFile = path.join(__dirname, '..', 'public', 'uploads', `${fileName}.wav`)
|
|
||||||
|
|
||||||
const filePath = await convertAudioToLinear16(inputFile, outputFile)
|
|
||||||
|
|
||||||
fs.unlinkSync(inputFile)
|
|
||||||
|
|
||||||
const obj = await speechToText(filePath, languageCode)
|
|
||||||
|
|
||||||
fs.unlinkSync(filePath)
|
|
||||||
|
|
||||||
if (obj?.transcription) return res.status(StatusCodes.OK).json({ transcription: obj.transcription })
|
|
||||||
|
|
||||||
res.status(obj.status).json({ msg: obj.msg })
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const uploadAudioToTranscript = async (req, res) => {
|
|
||||||
|
|
||||||
const { languageCode } = req.body
|
|
||||||
|
|
||||||
const audio = req.file
|
|
||||||
|
|
||||||
if (!audio)
|
|
||||||
throw new CustomError.BadRequestError(`Missing the audio file`)
|
|
||||||
|
|
||||||
if (languageCode) {
|
|
||||||
const existLanguageCode = languageCodes.find(l => l.languageCode == languageCode)
|
|
||||||
|
|
||||||
if (!existLanguageCode) {
|
|
||||||
fs.unlinkSync(audio.path)
|
|
||||||
throw new CustomError.BadRequestError(`Invalid language code`)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const inputFile = path.resolve(audio.path)
|
|
||||||
|
|
||||||
const fileName = path.basename(inputFile, path.extname(inputFile))
|
|
||||||
|
|
||||||
const outputFile = path.join(__dirname, '..', 'public', 'uploads', `${fileName}.wav`)
|
|
||||||
|
|
||||||
const filePath = await convertAudioToLinear16(inputFile, outputFile)
|
|
||||||
|
|
||||||
fs.unlinkSync(inputFile)
|
|
||||||
|
|
||||||
const obj = await speechToTextJob(filePath, languageCode)
|
|
||||||
|
|
||||||
fs.unlinkSync(filePath)
|
|
||||||
|
|
||||||
if (obj?.operationName) return res.status(StatusCodes.OK).json({ operationId: obj.operationName })
|
|
||||||
|
|
||||||
res.status(obj.status).json({ msg: obj.msg })
|
|
||||||
}
|
|
||||||
|
|
||||||
const getJobStatus = async (req, res) => {
|
|
||||||
|
|
||||||
const { operationName } = req.query
|
|
||||||
|
|
||||||
if (!operationName)
|
|
||||||
throw new CustomError.BadRequestError(`Missing operationName query parameter`)
|
|
||||||
|
|
||||||
// Get the operation using the operationName
|
|
||||||
const [response] = await client.getOperation({ name: operationName })
|
|
||||||
|
|
||||||
if (!response) {
|
|
||||||
return res.status(404).json({ msg: "Operation not found" })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.done) {
|
|
||||||
|
|
||||||
// Load the protobuf message types
|
|
||||||
const root = new protobuf.Root()
|
|
||||||
root.loadSync(path.join(__dirname, '..', 'node_modules', 'google-proto-files', 'google', 'rpc', 'status.proto'), { keepCase: true })
|
|
||||||
root.loadSync(path.join(__dirname, '..', 'node_modules', 'google-proto-files', 'google', 'protobuf', 'duration.proto'), { keepCase: true })
|
|
||||||
root.loadSync(path.join(__dirname, '..', 'node_modules', 'google-proto-files', 'google', 'cloud', 'speech', 'v1', 'cloud_speech.proto'), { keepCase: true })
|
|
||||||
|
|
||||||
// Get the message type
|
|
||||||
const LongRunningRecognizeResponse = root.lookupType('google.cloud.speech.v1.LongRunningRecognizeResponse')
|
|
||||||
|
|
||||||
if (!response) {
|
|
||||||
return res.status(StatusCodes.NOT_FOUND).json({ msg: "Operation not found" })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode the response value to get transcribed text
|
|
||||||
const longRunningResponse = LongRunningRecognizeResponse.decode(response.response.value)
|
|
||||||
if (longRunningResponse.error) {
|
|
||||||
console.error('Error:', longRunningResponse.error)
|
|
||||||
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ msg: longRunningResponse.error })
|
|
||||||
} else {
|
|
||||||
|
|
||||||
const transcriptions = longRunningResponse.results.map(result => result.alternatives[0].transcript)
|
|
||||||
|
|
||||||
const fullTranscription = transcriptions.join(' ')
|
|
||||||
|
|
||||||
// console.log('Full Transcription:', fullTranscription)
|
|
||||||
|
|
||||||
res.status(StatusCodes.OK).json({ transcription: fullTranscription })
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
res.status(StatusCodes.ACCEPTED).json({ msg: "Transcription in progress" })
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const getVoiceConfig = async (req, res) => {
|
const getVoiceConfig = async (req, res) => {
|
||||||
|
|
||||||
const { languageCode } = req.query
|
const { languageCode } = req.query
|
||||||
|
@ -206,8 +72,5 @@ const getVoiceConfig = async (req, res) => {
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getSentiment,
|
getSentiment,
|
||||||
getAudioFromText,
|
getAudioFromText,
|
||||||
getTextFromAudio,
|
getVoiceConfig
|
||||||
getVoiceConfig,
|
|
||||||
getJobStatus,
|
|
||||||
uploadAudioToTranscript
|
|
||||||
}
|
}
|
|
@ -1,59 +0,0 @@
|
||||||
[
|
|
||||||
{ "languageCode": "af-ZA" },
|
|
||||||
{ "languageCode": "ar-XA" },
|
|
||||||
{ "languageCode": "bg-BG" },
|
|
||||||
{ "languageCode": "bn-IN" },
|
|
||||||
{ "languageCode": "ca-ES" },
|
|
||||||
{ "languageCode": "cmn-CN" },
|
|
||||||
{ "languageCode": "cmn-TW" },
|
|
||||||
{ "languageCode": "cs-CZ" },
|
|
||||||
{ "languageCode": "da-DK" },
|
|
||||||
{ "languageCode": "de-DE" },
|
|
||||||
{ "languageCode": "el-GR" },
|
|
||||||
{ "languageCode": "en-AU" },
|
|
||||||
{ "languageCode": "en-GB" },
|
|
||||||
{ "languageCode": "en-IN" },
|
|
||||||
{ "languageCode": "en-US" },
|
|
||||||
{ "languageCode": "es-ES" },
|
|
||||||
{ "languageCode": "es-US" },
|
|
||||||
{ "languageCode": "eu-ES" },
|
|
||||||
{ "languageCode": "fi-FI" },
|
|
||||||
{ "languageCode": "fil-PH" },
|
|
||||||
{ "languageCode": "fr-CA" },
|
|
||||||
{ "languageCode": "fr-FR" },
|
|
||||||
{ "languageCode": "gl-ES" },
|
|
||||||
{ "languageCode": "gu-IN" },
|
|
||||||
{ "languageCode": "he-IL" },
|
|
||||||
{ "languageCode": "hi-IN" },
|
|
||||||
{ "languageCode": "hu-HU" },
|
|
||||||
{ "languageCode": "id-ID" },
|
|
||||||
{ "languageCode": "is-IS" },
|
|
||||||
{ "languageCode": "it-IT" },
|
|
||||||
{ "languageCode": "ja-JP" },
|
|
||||||
{ "languageCode": "kn-IN" },
|
|
||||||
{ "languageCode": "ko-KR" },
|
|
||||||
{ "languageCode": "lt-LT" },
|
|
||||||
{ "languageCode": "lv-LV" },
|
|
||||||
{ "languageCode": "ml-IN" },
|
|
||||||
{ "languageCode": "mr-IN" },
|
|
||||||
{ "languageCode": "ms-MY" },
|
|
||||||
{ "languageCode": "nb-NO" },
|
|
||||||
{ "languageCode": "nl-BE" },
|
|
||||||
{ "languageCode": "nl-NL" },
|
|
||||||
{ "languageCode": "pa-IN" },
|
|
||||||
{ "languageCode": "pl-PL" },
|
|
||||||
{ "languageCode": "pt-BR" },
|
|
||||||
{ "languageCode": "pt-PT" },
|
|
||||||
{ "languageCode": "ro-RO" },
|
|
||||||
{ "languageCode": "ru-RU" },
|
|
||||||
{ "languageCode": "sk-SK" },
|
|
||||||
{ "languageCode": "sr-RS" },
|
|
||||||
{ "languageCode": "sv-SE" },
|
|
||||||
{ "languageCode": "ta-IN" },
|
|
||||||
{ "languageCode": "te-IN" },
|
|
||||||
{ "languageCode": "th-TH" },
|
|
||||||
{ "languageCode": "tr-TR" },
|
|
||||||
{ "languageCode": "uk-UA" },
|
|
||||||
{ "languageCode": "vi-VN" },
|
|
||||||
{ "languageCode": "yue-HK" }
|
|
||||||
]
|
|
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
|
@ -11,8 +11,6 @@
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google-cloud/language": "^6.1.0",
|
"@google-cloud/language": "^6.1.0",
|
||||||
"@google-cloud/speech": "^6.0.2",
|
|
||||||
"@google-cloud/storage": "^7.4.0",
|
|
||||||
"@google-cloud/text-to-speech": "^5.0.1",
|
"@google-cloud/text-to-speech": "^5.0.1",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"cookie-parser": "^1.4.5",
|
"cookie-parser": "^1.4.5",
|
||||||
|
@ -23,20 +21,14 @@
|
||||||
"express-fileupload": "^1.2.1",
|
"express-fileupload": "^1.2.1",
|
||||||
"express-mongo-sanitize": "^2.1.0",
|
"express-mongo-sanitize": "^2.1.0",
|
||||||
"express-rate-limit": "^5.4.1",
|
"express-rate-limit": "^5.4.1",
|
||||||
"fluent-ffmpeg": "^2.1.2",
|
|
||||||
"google-gax": "^4.0.5",
|
|
||||||
"google-proto-files": "^4.0.0",
|
|
||||||
"google-protobuf": "^3.21.2",
|
|
||||||
"helmet": "^4.6.0",
|
"helmet": "^4.6.0",
|
||||||
"http-status-codes": "^2.1.4",
|
"http-status-codes": "^2.1.4",
|
||||||
"joi": "^17.4.0",
|
"joi": "^17.4.0",
|
||||||
"mongoose": "^7.3.1",
|
"mongoose": "^7.3.1",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"multer": "^1.4.5-lts.1",
|
|
||||||
"protobufjs": "^7.2.5",
|
|
||||||
"swagger-ui-express": "^4.1.6",
|
|
||||||
"validator": "^13.6.0",
|
"validator": "^13.6.0",
|
||||||
"xss-clean": "^0.1.1",
|
"xss-clean": "^0.1.1",
|
||||||
|
"swagger-ui-express": "^4.1.6",
|
||||||
"yamljs": "^0.3.0"
|
"yamljs": "^0.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 127 KiB |
Binary file not shown.
After Width: | Height: | Size: 205 KiB |
|
@ -1,14 +1,11 @@
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
const { authorization, } = require('../middleware/authentication')
|
const { authorization, } = require('../middleware/authentication')
|
||||||
const { audioUpload } = require("../utils")
|
|
||||||
const { getSentiment, getAudioFromText, getTextFromAudio, getVoiceConfig, uploadAudioToTranscript, getJobStatus } = require('../controllers/naturalLanguageController')
|
const { getSentiment, getAudioFromText, getVoiceConfig } = require('../controllers/naturalLanguageController')
|
||||||
|
|
||||||
router.route('/sentiment').post(authorization, getSentiment)
|
router.route('/sentiment').post(authorization, getSentiment)
|
||||||
router.route('/text-to-speech').get(authorization, getAudioFromText)
|
router.route('/text-to-speech').get(getAudioFromText)
|
||||||
router.route('/speech-to-text').post(audioUpload.single('audio'), authorization, getTextFromAudio)
|
|
||||||
router.route('/upload-audio-to-transcript').post(audioUpload.single('audio'), authorization, uploadAudioToTranscript)
|
|
||||||
router.route('/query-job-status').get(getJobStatus)
|
|
||||||
router.route('/voice-config').get(getVoiceConfig)
|
router.route('/voice-config').get(getVoiceConfig)
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|
139
swagger.yaml
139
swagger.yaml
|
@ -1,117 +1,29 @@
|
||||||
openapi: 3.0.0
|
openapi: 3.0.0
|
||||||
info:
|
info:
|
||||||
title: Natural Language API Google
|
title: Natural Language API
|
||||||
description: This API describes the endpoints and parameters to use resources from Google Cloud API.
|
|
||||||
contact: {}
|
contact: {}
|
||||||
version: '1.0'
|
version: '1.0'
|
||||||
servers:
|
servers:
|
||||||
- url: http://localhost:6001/api/v1/nl
|
- url: http://localhost:6001/api/v1/nl/
|
||||||
variables: {}
|
variables: {}
|
||||||
paths:
|
paths:
|
||||||
/upload-audio-to-transcript:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- Speech to text async
|
|
||||||
summary: Speech to text job
|
|
||||||
operationId: Speechtotextjob
|
|
||||||
parameters: []
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
multipart/form-data:
|
|
||||||
encoding: {}
|
|
||||||
schema:
|
|
||||||
required:
|
|
||||||
- audio
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
audio:
|
|
||||||
type: string
|
|
||||||
format: binary
|
|
||||||
languageCode:
|
|
||||||
type: string
|
|
||||||
description: 'If not provided, the default will be: pt-BR'
|
|
||||||
example: pt-BR
|
|
||||||
required: false
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: ''
|
|
||||||
headers: {}
|
|
||||||
deprecated: false
|
|
||||||
security:
|
|
||||||
- bearer: []
|
|
||||||
/query-job-status:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- Speech to text async
|
|
||||||
summary: Speech to text job process
|
|
||||||
operationId: Speechtotextjobprocess
|
|
||||||
parameters:
|
|
||||||
- name: operationName
|
|
||||||
in: query
|
|
||||||
description: 'The job id returned after uploading the audio file that will be transcribed.'
|
|
||||||
required: true
|
|
||||||
style: form
|
|
||||||
explode: true
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
example: 2993135803178989324
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: ''
|
|
||||||
headers: {}
|
|
||||||
deprecated: false
|
|
||||||
security:
|
|
||||||
- bearer: []
|
|
||||||
/speech-to-text:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- Speech to text sync
|
|
||||||
summary: Speech to text
|
|
||||||
operationId: Speechtotext
|
|
||||||
parameters: []
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
multipart/form-data:
|
|
||||||
encoding: {}
|
|
||||||
schema:
|
|
||||||
required:
|
|
||||||
- audio
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
audio:
|
|
||||||
type: string
|
|
||||||
format: binary
|
|
||||||
languageCode:
|
|
||||||
description: 'If not provided, the default will be: pt-BR'
|
|
||||||
type: string
|
|
||||||
example: pt-BR
|
|
||||||
required: false
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: ''
|
|
||||||
headers: {}
|
|
||||||
deprecated: false
|
|
||||||
security:
|
|
||||||
- bearer: []
|
|
||||||
/sentiment:
|
/sentiment:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Sentiment
|
- API ROUTES
|
||||||
summary: Get sentiment
|
summary: Get sentiment
|
||||||
operationId: Getsentiment
|
operationId: Getsentiment
|
||||||
parameters: []
|
parameters: []
|
||||||
requestBody:
|
requestBody:
|
||||||
description: ''
|
description: 'The text that will be analyzed'
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/GetsentimentRequest'
|
|
||||||
- example:
|
- example:
|
||||||
text: Toda vez a mesma coisa ja to cansado de ficar ligando pra resolver esses problemas de conexão!
|
text: Vcs vao fica enrolando ate quando pra entregar isso!
|
||||||
example:
|
example:
|
||||||
text: Toda vez a mesma coisa ja to cansado de ficar ligando pra resolver esses problemas de conexão!
|
text: Vcs vao fica enrolando ate quando pra entregar isso!
|
||||||
required: true
|
required: true
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
|
@ -123,13 +35,13 @@ paths:
|
||||||
/text-to-speech:
|
/text-to-speech:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Text to speech
|
- API ROUTES
|
||||||
summary: Text to speech
|
summary: Text to speech
|
||||||
operationId: Texttospeech
|
operationId: Texttospeech
|
||||||
parameters:
|
parameters:
|
||||||
- name: text
|
- name: text
|
||||||
in: query
|
in: query
|
||||||
description: ''
|
description: 'The text that will be converted to audio'
|
||||||
required: true
|
required: true
|
||||||
style: form
|
style: form
|
||||||
explode: true
|
explode: true
|
||||||
|
@ -138,31 +50,31 @@ paths:
|
||||||
example: Vela branca na enxurrada la vou eu de léo em léo, se o navio é pequeno do tamanho de um chapeu, não importa a volta ao mundo, é viagem de brinquedo em um barquinho de papel.
|
example: Vela branca na enxurrada la vou eu de léo em léo, se o navio é pequeno do tamanho de um chapeu, não importa a volta ao mundo, é viagem de brinquedo em um barquinho de papel.
|
||||||
- name: voice_name
|
- name: voice_name
|
||||||
in: query
|
in: query
|
||||||
description: ''
|
description: 'The name of the voice. Ex: pt-BR-Wavenet-C'
|
||||||
required: false
|
required: false
|
||||||
style: form
|
style: form
|
||||||
explode: true
|
explode: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
example: pt-BR-Wavenet-C
|
example:
|
||||||
- name: voice_gender
|
- name: voice_gender
|
||||||
in: query
|
in: query
|
||||||
description: ''
|
description: 'The name of the voice. Ex: FEMALE'
|
||||||
required: false
|
required: false
|
||||||
style: form
|
style: form
|
||||||
explode: true
|
explode: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
example: FEMALE
|
example:
|
||||||
- name: languageCode
|
- name: languageCode
|
||||||
in: query
|
in: query
|
||||||
description: ''
|
description: 'Ex pt-BR'
|
||||||
required: false
|
required: false
|
||||||
style: form
|
style: form
|
||||||
explode: true
|
explode: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
example: pt-BR
|
example:
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: ''
|
description: ''
|
||||||
|
@ -173,13 +85,13 @@ paths:
|
||||||
/voice-config:
|
/voice-config:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Text to speech
|
- API ROUTES
|
||||||
summary: Get voice config
|
summary: Get voice config
|
||||||
operationId: Getvoiceconfig
|
operationId: Getvoiceconfig
|
||||||
parameters:
|
parameters:
|
||||||
- name: languageCode
|
- name: languageCode
|
||||||
in: query
|
in: query
|
||||||
description: ''
|
description: 'Ex pt-BR'
|
||||||
required: false
|
required: false
|
||||||
style: form
|
style: form
|
||||||
explode: true
|
explode: true
|
||||||
|
@ -191,26 +103,11 @@ paths:
|
||||||
description: ''
|
description: ''
|
||||||
headers: {}
|
headers: {}
|
||||||
deprecated: false
|
deprecated: false
|
||||||
security: []
|
security:
|
||||||
|
- bearer: []
|
||||||
components:
|
components:
|
||||||
schemas:
|
|
||||||
GetsentimentRequest:
|
|
||||||
title: GetsentimentRequest
|
|
||||||
required:
|
|
||||||
- text
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
text:
|
|
||||||
type: string
|
|
||||||
example:
|
|
||||||
text: Toda vez a mesma coisa ja to cansado de ficar ligando pra resolver esses problemas de conexão!
|
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
bearer:
|
bearer:
|
||||||
type: http
|
type: http
|
||||||
scheme: bearer
|
scheme: bearer
|
||||||
security: []
|
security: []
|
||||||
tags:
|
|
||||||
- name: Speech to text async
|
|
||||||
- name: Speech to text sync
|
|
||||||
- name: Sentiment
|
|
||||||
- name: Text to speech
|
|
|
@ -1,216 +0,0 @@
|
||||||
openapi: 3.0.0
|
|
||||||
info:
|
|
||||||
title: Natural Language API Google
|
|
||||||
description: This API describes the endpoints and parameters to use resources from Google Cloud API.
|
|
||||||
contact: {}
|
|
||||||
version: '1.0'
|
|
||||||
servers:
|
|
||||||
- url: https://hit-nl.omnihit.app.br/api/v1/nl
|
|
||||||
variables: {}
|
|
||||||
paths:
|
|
||||||
/upload-audio-to-transcript:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- Speech to text async
|
|
||||||
summary: Speech to text job
|
|
||||||
operationId: Speechtotextjob
|
|
||||||
parameters: []
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
multipart/form-data:
|
|
||||||
encoding: {}
|
|
||||||
schema:
|
|
||||||
required:
|
|
||||||
- audio
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
audio:
|
|
||||||
type: string
|
|
||||||
format: binary
|
|
||||||
languageCode:
|
|
||||||
type: string
|
|
||||||
description: 'If not provided, the default will be: pt-BR'
|
|
||||||
example: pt-BR
|
|
||||||
required: false
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: ''
|
|
||||||
headers: {}
|
|
||||||
deprecated: false
|
|
||||||
security:
|
|
||||||
- bearer: []
|
|
||||||
/query-job-status:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- Speech to text async
|
|
||||||
summary: Speech to text job process
|
|
||||||
operationId: Speechtotextjobprocess
|
|
||||||
parameters:
|
|
||||||
- name: operationName
|
|
||||||
in: query
|
|
||||||
description: 'The job id returned after uploading the audio file that will be transcribed.'
|
|
||||||
required: true
|
|
||||||
style: form
|
|
||||||
explode: true
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
example: 2993135803178989324
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: ''
|
|
||||||
headers: {}
|
|
||||||
deprecated: false
|
|
||||||
security:
|
|
||||||
- bearer: []
|
|
||||||
/speech-to-text:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- Speech to text sync
|
|
||||||
summary: Speech to text
|
|
||||||
operationId: Speechtotext
|
|
||||||
parameters: []
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
multipart/form-data:
|
|
||||||
encoding: {}
|
|
||||||
schema:
|
|
||||||
required:
|
|
||||||
- audio
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
audio:
|
|
||||||
type: string
|
|
||||||
format: binary
|
|
||||||
languageCode:
|
|
||||||
description: 'If not provided, the default will be: pt-BR'
|
|
||||||
type: string
|
|
||||||
example: pt-BR
|
|
||||||
required: false
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: ''
|
|
||||||
headers: {}
|
|
||||||
deprecated: false
|
|
||||||
security:
|
|
||||||
- bearer: []
|
|
||||||
/sentiment:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- Sentiment
|
|
||||||
summary: Get sentiment
|
|
||||||
operationId: Getsentiment
|
|
||||||
parameters: []
|
|
||||||
requestBody:
|
|
||||||
description: ''
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
allOf:
|
|
||||||
- $ref: '#/components/schemas/GetsentimentRequest'
|
|
||||||
- example:
|
|
||||||
text: Toda vez a mesma coisa ja to cansado de ficar ligando pra resolver esses problemas de conexão!
|
|
||||||
example:
|
|
||||||
text: Toda vez a mesma coisa ja to cansado de ficar ligando pra resolver esses problemas de conexão!
|
|
||||||
required: true
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: ''
|
|
||||||
headers: {}
|
|
||||||
deprecated: false
|
|
||||||
security:
|
|
||||||
- bearer: []
|
|
||||||
/text-to-speech:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- Text to speech
|
|
||||||
summary: Text to speech
|
|
||||||
operationId: Texttospeech
|
|
||||||
parameters:
|
|
||||||
- name: text
|
|
||||||
in: query
|
|
||||||
description: ''
|
|
||||||
required: true
|
|
||||||
style: form
|
|
||||||
explode: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: Vela branca na enxurrada la vou eu de léo em léo, se o navio é pequeno do tamanho de um chapeu, não importa a volta ao mundo, é viagem de brinquedo em um barquinho de papel.
|
|
||||||
- name: voice_name
|
|
||||||
in: query
|
|
||||||
description: ''
|
|
||||||
required: false
|
|
||||||
style: form
|
|
||||||
explode: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: pt-BR-Wavenet-C
|
|
||||||
- name: voice_gender
|
|
||||||
in: query
|
|
||||||
description: ''
|
|
||||||
required: false
|
|
||||||
style: form
|
|
||||||
explode: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: FEMALE
|
|
||||||
- name: languageCode
|
|
||||||
in: query
|
|
||||||
description: ''
|
|
||||||
required: false
|
|
||||||
style: form
|
|
||||||
explode: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: pt-BR
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: ''
|
|
||||||
headers: {}
|
|
||||||
deprecated: false
|
|
||||||
security:
|
|
||||||
- bearer: []
|
|
||||||
/voice-config:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- Text to speech
|
|
||||||
summary: Get voice config
|
|
||||||
operationId: Getvoiceconfig
|
|
||||||
parameters:
|
|
||||||
- name: languageCode
|
|
||||||
in: query
|
|
||||||
description: ''
|
|
||||||
required: false
|
|
||||||
style: form
|
|
||||||
explode: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
example: pt-Br
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: ''
|
|
||||||
headers: {}
|
|
||||||
deprecated: false
|
|
||||||
security: []
|
|
||||||
components:
|
|
||||||
schemas:
|
|
||||||
GetsentimentRequest:
|
|
||||||
title: GetsentimentRequest
|
|
||||||
required:
|
|
||||||
- text
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
text:
|
|
||||||
type: string
|
|
||||||
example:
|
|
||||||
text: Toda vez a mesma coisa ja to cansado de ficar ligando pra resolver esses problemas de conexão!
|
|
||||||
securitySchemes:
|
|
||||||
bearer:
|
|
||||||
type: http
|
|
||||||
scheme: bearer
|
|
||||||
security: []
|
|
||||||
tags:
|
|
||||||
- name: Speech to text async
|
|
||||||
- name: Speech to text sync
|
|
||||||
- name: Sentiment
|
|
||||||
- name: Text to speech
|
|
|
@ -1,25 +0,0 @@
|
||||||
const multer = require('multer')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
//Destination to store the file
|
|
||||||
const audioStorage = multer.diskStorage({
|
|
||||||
destination: function (req, file, cb) {
|
|
||||||
cb(null, `public/uploads`)
|
|
||||||
},
|
|
||||||
filename: function (req, file, cb) {
|
|
||||||
cb(null, Date.now() + String(Math.floor(Math.random() * 1000)) + path.extname(file.originalname))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const audioUpload = multer({
|
|
||||||
storage: audioStorage,
|
|
||||||
fileFilter(req, file, cb) {
|
|
||||||
if (!file.originalname.match(/\.(mp3|wav|ogg|flac|aac|wma|m4a|mp4|webm|opus|mpeg)$/i)) {
|
|
||||||
return cb(new Error('Invalid file type. Send only an audio file!'))
|
|
||||||
}
|
|
||||||
cb(undefined, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = audioUpload
|
|
|
@ -1,35 +0,0 @@
|
||||||
// Imports the Google Cloud client library
|
|
||||||
const { Storage } = require('@google-cloud/storage')
|
|
||||||
|
|
||||||
async function audioUploadToBucket(
|
|
||||||
bucketName,
|
|
||||||
filePath,
|
|
||||||
destFileName,
|
|
||||||
) {
|
|
||||||
// [START storage_upload_file]
|
|
||||||
|
|
||||||
// Creates a client
|
|
||||||
const storage = new Storage()
|
|
||||||
|
|
||||||
async function uploadFile() {
|
|
||||||
const options = {
|
|
||||||
destination: destFileName,
|
|
||||||
}
|
|
||||||
|
|
||||||
await storage.bucket(bucketName).upload(filePath, options)
|
|
||||||
console.log(`${filePath} uploaded to ${bucketName}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await uploadFile()
|
|
||||||
return true
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// [END storage_upload_file]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = audioUploadToBucket
|
|
|
@ -1,16 +0,0 @@
|
||||||
const ffmpeg = require('fluent-ffmpeg')
|
|
||||||
|
|
||||||
async function convertToLINEAR16(inputFile, outputFile) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
ffmpeg(inputFile)
|
|
||||||
.audioCodec('pcm_s16le') // Set the audio codec to LINEAR16
|
|
||||||
.audioFrequency(16000) // Set the sample rate to 16,000 Hz
|
|
||||||
.audioChannels(1)
|
|
||||||
.on('end', () => resolve(outputFile))
|
|
||||||
.on('error', (err) => reject(err))
|
|
||||||
.save(outputFile)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = convertToLINEAR16
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
const ffmpeg = require('fluent-ffmpeg')
|
|
||||||
|
|
||||||
async function getAudioDuration(filePath) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
ffmpeg.ffprobe(filePath, (err, metadata) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err)
|
|
||||||
} else {
|
|
||||||
resolve(Math.round(metadata.format.duration))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = getAudioDuration
|
|
||||||
|
|
||||||
|
|
|
@ -5,21 +5,10 @@
|
||||||
const sentiment = require('./sentiment')
|
const sentiment = require('./sentiment')
|
||||||
const convertTextToSpeech = require('./textToSpeech')
|
const convertTextToSpeech = require('./textToSpeech')
|
||||||
const listVoice = require('./listVoice')
|
const listVoice = require('./listVoice')
|
||||||
const convertAudioToLinear16 = require('./convertAudioToLinear16')
|
|
||||||
const getAudioDuration = require('./getAudioDuration')
|
|
||||||
const audioUploadToBucket = require('./audioUploadToBucket')
|
|
||||||
const audioUpload = require('./audioUpload')
|
|
||||||
const speechToText = require('./speechToText')
|
|
||||||
const speechToTextJob = require('./speechToTextJob')
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
sentiment,
|
sentiment,
|
||||||
convertTextToSpeech,
|
convertTextToSpeech,
|
||||||
listVoice,
|
listVoice
|
||||||
convertAudioToLinear16,
|
|
||||||
getAudioDuration,
|
|
||||||
audioUploadToBucket,
|
|
||||||
audioUpload,
|
|
||||||
speechToText,
|
|
||||||
speechToTextJob
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
// Imports the Google Cloud client library
|
|
||||||
const speech = require('@google-cloud/speech')
|
|
||||||
const { StatusCodes } = require("http-status-codes")
|
|
||||||
const path = require('path')
|
|
||||||
const fs = require('fs')
|
|
||||||
const getAudioDuration = require('./getAudioDuration')
|
|
||||||
const audioUploadToBucket = require('./audioUploadToBucket')
|
|
||||||
|
|
||||||
async function speechToText(filename, languageCode = 'pt-Br', bucket = 'speect-to-text-bucket', sampleRateHertz = 16000, encoding = 'LINEAR16') {
|
|
||||||
|
|
||||||
const client = new speech.SpeechClient()
|
|
||||||
|
|
||||||
let audio
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
encoding: encoding,
|
|
||||||
sampleRateHertz: sampleRateHertz,
|
|
||||||
languageCode: languageCode,
|
|
||||||
}
|
|
||||||
|
|
||||||
const seconds = await getAudioDuration(filename)
|
|
||||||
|
|
||||||
if (seconds >= 28800) {
|
|
||||||
return { msg: 'Audio file is higher than 480 minute', status: StatusCodes.BAD_REQUEST }
|
|
||||||
}
|
|
||||||
else if (seconds <= 59) {
|
|
||||||
audio = {
|
|
||||||
content: fs.readFileSync(filename).toString('base64'),
|
|
||||||
}
|
|
||||||
} else if (seconds >= 60) {
|
|
||||||
const uploaded = await audioUploadToBucket(bucket, filename, path.basename(filename))
|
|
||||||
|
|
||||||
if (uploaded) {
|
|
||||||
audio = {
|
|
||||||
uri: `gs://${bucket}/${path.basename(filename)}`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!audio) return { msg: `Error on try upload the file to google cloud bucket(${bucket}) storage`, status: StatusCodes.INTERNAL_SERVER_ERROR }
|
|
||||||
|
|
||||||
const request = {
|
|
||||||
config: config,
|
|
||||||
audio: audio,
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Detects speech in the audio file. This creates a recognition job that you
|
|
||||||
// can wait for now, or get its result later.
|
|
||||||
const [operation] = await client.longRunningRecognize(request)
|
|
||||||
// Get a Promise representation of the final result of the job
|
|
||||||
const [response] = await operation.promise()
|
|
||||||
const transcription = response.results
|
|
||||||
.map(result => result.alternatives[0].transcript)
|
|
||||||
.join('\n')
|
|
||||||
console.log(`Transcription: ${transcription}`)
|
|
||||||
|
|
||||||
return { msg: `Transcript success`, status: StatusCodes.OK, transcription }
|
|
||||||
} catch (error) {
|
|
||||||
console.log('ERROR ON TRY TRANSCRIPT: ', error)
|
|
||||||
return { msg: `Error on try transcript the file`, status: StatusCodes.INTERNAL_SERVER_ERROR }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = speechToText
|
|
||||||
|
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
// Imports the Google Cloud client library
|
|
||||||
const speech = require('@google-cloud/speech')
|
|
||||||
const path = require('path')
|
|
||||||
const fs = require('fs')
|
|
||||||
const getAudioDuration = require('./getAudioDuration')
|
|
||||||
const audioUploadToBucket = require('./audioUploadToBucket')
|
|
||||||
const { StatusCodes } = require("http-status-codes")
|
|
||||||
|
|
||||||
|
|
||||||
async function speechToTextJob(filename, languageCode = 'pt-Br', bucket = 'speect-to-text-bucket', sampleRateHertz = 16000, encoding = 'LINEAR16') {
|
|
||||||
|
|
||||||
const client = new speech.SpeechClient()
|
|
||||||
|
|
||||||
let audio
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
encoding: encoding,
|
|
||||||
sampleRateHertz: sampleRateHertz,
|
|
||||||
languageCode: languageCode,
|
|
||||||
}
|
|
||||||
|
|
||||||
const seconds = await getAudioDuration(filename)
|
|
||||||
|
|
||||||
if (seconds >= 28800) {
|
|
||||||
return { msg: 'Audio file is higher than 480 minute', status: StatusCodes.BAD_REQUEST }
|
|
||||||
}
|
|
||||||
else if (seconds <= 59) {
|
|
||||||
audio = {
|
|
||||||
content: fs.readFileSync(filename).toString('base64'),
|
|
||||||
}
|
|
||||||
} else if (seconds >= 60) {
|
|
||||||
const uploaded = await audioUploadToBucket(bucket, filename, path.basename(filename))
|
|
||||||
|
|
||||||
if (uploaded) {
|
|
||||||
audio = {
|
|
||||||
uri: `gs://${bucket}/${path.basename(filename)}`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!audio) return { msg: `Error on try upload the file to google cloud bucket(${bucket}) storage`, status: StatusCodes.INTERNAL_SERVER_ERROR }
|
|
||||||
|
|
||||||
const request = {
|
|
||||||
config: config,
|
|
||||||
audio: audio,
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Detects speech in the audio file. This creates a recognition job that you
|
|
||||||
// can wait for now, or get its result later.
|
|
||||||
const [operation] = await client.longRunningRecognize(request)
|
|
||||||
|
|
||||||
console.log('===========> operationName: ', operation.name)
|
|
||||||
|
|
||||||
return { msg: `success`, status: StatusCodes.OK, operationName: operation.name }
|
|
||||||
} catch (error) {
|
|
||||||
console.log('ERROR ON TRY TRANSCRIPT: ', error)
|
|
||||||
return { msg: `Error on try transcript the file`, status: StatusCodes.INTERNAL_SERVER_ERROR }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = speechToTextJob
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue