feat: add S3 storage support and update configuration

- Enhanced .env.example to include S3 storage configuration options.
- Updated main.go to initialize S3 client and handle audio uploads to S3.
- Modified processAudio function to return S3 URL when storage is enabled.
- Updated README.md with new S3 storage instructions and examples.
This commit is contained in:
Davidson Gomes 2024-12-02 20:14:10 -03:00
parent 30a4990e53
commit 5f3a073d76
5 changed files with 292 additions and 122 deletions

View File

@ -2,7 +2,17 @@ PORT=4040
CORS_ALLOW_ORIGINS=*
API_KEY=429683C4C977415CAAFCCE10F7D57E11
ENABLE_TRANSCRIPTION=true
TRANSCRIPTION_PROVIDER=openai # ou groq
OPENAI_API_KEY=sua_chave_openai_aqui
GROK_API_KEY=sua_chave_groq_aqui
TRANSCRIPTION_LANGUAGE=pt
TRANSCRIPTION_PROVIDER=openai # or groq
OPENAI_API_KEY=your_openai_key_here
GROK_API_KEY=your_groq_key_here
TRANSCRIPTION_LANGUAGE=en # Default transcription language (optional)
# S3 Storage Settings
ENABLE_S3_STORAGE=true
S3_ENDPOINT=play.min.io
S3_ACCESS_KEY=your_access_key_here
S3_SECRET_KEY=your_secret_key_here
S3_BUCKET_NAME=audio-files
S3_REGION=us-east-1
S3_USE_SSL=true
S3_URL_EXPIRATION=24h # Duration format: 1h, 24h, 7d, etc.

167
README.md
View File

@ -1,6 +1,6 @@
# Evolution Audio Converter
This project is a microservice in Go that processes audio files, converts them to **opus** or **mp3** format, and returns both the duration of the audio and the converted file in base64. The service accepts audio files sent as **form-data**, **base64**, or **URL**.
This project is a microservice in Go that processes audio files, converts them to **opus** or **mp3** format, and returns both the duration of the audio and the converted file (as base64 or S3 URL). The service accepts audio files sent as **form-data**, **base64**, or **URL**.
## Requirements
@ -50,16 +50,16 @@ The service depends on **FFmpeg** to convert the audio. Make sure FFmpeg is inst
### Configuration
Create a `.env` file in the project's root directory with the following configuration:
Create a `.env` file in the project's root directory. Here are the available configuration options:
#### Basic Configuration
```env
PORT=4040
API_KEY=your_secret_api_key_here
```
### Transcription Configuration
To enable audio transcription, configure the following variables in the `.env` file:
#### Transcription Configuration
```env
ENABLE_TRANSCRIPTION=true
@ -69,17 +69,44 @@ GROQ_API_KEY=your_groq_key_here
TRANSCRIPTION_LANGUAGE=en # Default transcription language (optional)
```
- `ENABLE_TRANSCRIPTION`: Enables or disables the transcription feature
- `TRANSCRIPTION_PROVIDER`: Chooses the AI provider for transcription (openai or groq)
- `OPENAI_API_KEY`: Your OpenAI API key (required if using openai)
- `GROQ_API_KEY`: Your Groq API key (required if using groq)
- `TRANSCRIPTION_LANGUAGE`: Sets the default transcription language (optional)
#### Storage Configuration
```env
ENABLE_S3_STORAGE=true
S3_ENDPOINT=play.min.io
S3_ACCESS_KEY=your_access_key_here
S3_SECRET_KEY=your_secret_key_here
S3_BUCKET_NAME=audio-files
S3_REGION=us-east-1
S3_USE_SSL=true
S3_URL_EXPIRATION=24h
```
### Storage Options
The service supports two storage modes for the converted audio:
1. **Base64 (default)**: Returns the audio file encoded in base64 format
2. **S3 Compatible Storage**: Uploads to S3-compatible storage (AWS S3, MinIO, etc.) and returns a presigned URL
When S3 storage is enabled, the response will include a `url` instead of the `audio` field:
```json
{
"duration": 120,
"format": "ogg",
"url": "https://your-s3-endpoint/bucket/file.ogg?signature...",
"transcription": "Transcribed text here..." // if transcription was requested
}
```
If S3 upload fails, the service automatically falls back to base64 encoding.
## Running the Project
### Locally
To run the service locally, use the following command:
To run the service locally:
```bash
go run main.go -dev
@ -89,8 +116,6 @@ The server will be available at `http://localhost:4040`.
### Using Docker
If you prefer to run the service in a Docker container, follow the steps below:
1. **Build the Docker image**:
```bash
@ -103,113 +128,97 @@ If you prefer to run the service in a Docker container, follow the steps below:
docker run -p 4040:4040 --env-file=.env audio-service
```
This will start the container on the port specified in the `.env` file.
## How to Use
You can send `POST` requests to the `/process-audio` endpoint with an audio file in the following formats:
- **Form-data** (to upload files)
- **Base64** (to send the audio encoded in base64)
- **URL** (to send the link to the audio file)
## API Usage
### Authentication
All requests must include the `apikey` header with the value of the `API_KEY` configured in the `.env` file.
All requests must include the `apikey` header with your API key.
### Optional Parameters
### Endpoints
- **`format`**: You can specify the format for conversion by passing the `format` parameter in the request. Supported values:
- `mp3`
- `ogg` (default)
#### Process Audio
### Audio Transcription
`POST /process-audio`
You can get the audio transcription in two ways:
Accepts audio files in these formats:
1. Along with audio processing by adding the `transcribe=true` parameter:
- Form-data
- Base64
- URL
Optional parameters:
- `format`: Output format (`mp3` or `ogg`, default: `ogg`)
- `transcribe`: Enable transcription (`true` or `false`)
- `language`: Transcription language code (e.g., "en", "es", "pt")
#### Transcribe Only
`POST /transcribe`
Transcribes audio without format conversion.
Optional parameters:
- `language`: Transcription language code
### Example Requests
#### Form-data Upload
```bash
curl -X POST -F "file=@audio.mp3" \
-F "format=ogg" \
-F "transcribe=true" \
-F "language=en" \
http://localhost:4040/process-audio \
-H "apikey: your_secret_api_key_here"
```
2. Using the specific transcription endpoint:
#### Base64 Upload
```bash
curl -X POST -F "file=@audio.mp3" \
-F "language=en" \
http://localhost:4040/transcribe \
curl -X POST \
-d "base64=$(base64 audio.mp3)" \
-d "format=ogg" \
http://localhost:4040/process-audio \
-H "apikey: your_secret_api_key_here"
```
Optional parameters:
- `language`: Audio language code (e.g., "en", "es", "pt"). If not specified, it will use the value defined in `TRANSCRIPTION_LANGUAGE` in `.env`. If neither is defined, the system will try to automatically detect the language.
#### URL Upload
The response will include the `transcription` field with the transcribed text:
```json
{
"transcription": "Transcribed text here..."
}
```bash
curl -X POST \
-d "url=https://example.com/audio.mp3" \
-d "format=ogg" \
http://localhost:4040/process-audio \
-H "apikey: your_secret_api_key_here"
```
When used with audio processing (`/process-audio`), the response will include both audio data and transcription:
### Response Format
With S3 storage disabled (default):
```json
{
"duration": 120,
"audio": "UklGR... (base64 of the file)",
"format": "ogg",
"transcription": "Transcribed text here..."
"transcription": "Transcribed text here..." // if requested
}
```
### Example Requests Using cURL
#### Sending as Form-data
```bash
curl -X POST -F "file=@path/to/audio.mp3" http://localhost:4040/process-audio \
-F "format=ogg" \
-H "apikey: your_secret_api_key_here"
```
#### Sending as Base64
```bash
curl -X POST -d "base64=$(base64 path/to/audio.mp3)" http://localhost:4040/process-audio \
-d "format=ogg" \
-H "apikey: your_secret_api_key_here"
```
#### Sending as URL
```bash
curl -X POST -d "url=https://example.com/path/to/audio.mp3" http://localhost:4040/process-audio \
-d "format=ogg" \
-H "apikey: your_secret_api_key_here"
```
### Response
The response will be a JSON object containing the audio duration and the converted audio file in base64:
With S3 storage enabled:
```json
{
"duration": 120,
"audio": "UklGR... (base64 of the file)",
"format": "ogg"
"url": "https://your-s3-endpoint/bucket/file.ogg?signature...",
"format": "ogg",
"transcription": "Transcribed text here..." // if requested
}
```
- `duration`: The audio duration in seconds.
- `audio`: The converted audio file encoded in base64.
- `format`: The format of the converted file (`mp3` or `ogg`).
## License
This project is licensed under the [MIT](LICENSE) license.

32
go.mod
View File

@ -1,36 +1,48 @@
module github.com/EvolutionAPI/evolution-audio-converter
go 1.21.1
go 1.22
require github.com/gin-gonic/gin v1.10.0
toolchain go1.22.9
require (
github.com/gin-contrib/cors v1.7.2
github.com/gin-gonic/gin v1.10.0
github.com/joho/godotenv v1.5.1
github.com/minio/minio-go/v7 v7.0.81
)
require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/cors v1.7.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

49
go.sum
View File

@ -6,9 +6,12 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw=
@ -17,6 +20,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@ -25,23 +30,36 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.81 h1:SzhMN0TQ6T/xSBu6Nvw3M5M8voM+Ht8RH3hE8S7zxaA=
github.com/minio/minio-go/v7 v7.0.81/go.mod h1:84gmIilaX4zcvAWWzJ5Z1WI5axN+hAbM5w25xf8xvC0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -51,6 +69,10 @@ github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@ -70,22 +92,23 @@ github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

146
main.go
View File

@ -2,6 +2,7 @@ package main
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
@ -17,9 +18,13 @@ import (
"strings"
"sync"
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
var (
@ -36,6 +41,15 @@ var (
openaiAPIKey string
groqAPIKey string
defaultTranscriptionLanguage string
enableS3Storage bool
s3Endpoint string
s3AccessKey string
s3SecretKey string
s3BucketName string
s3Region string
s3UseSSL bool
s3Client *minio.Client
s3URLExpiration time.Duration
)
func init() {
@ -70,6 +84,57 @@ func init() {
openaiAPIKey = os.Getenv("OPENAI_API_KEY")
groqAPIKey = os.Getenv("GROQ_API_KEY")
defaultTranscriptionLanguage = os.Getenv("TRANSCRIPTION_LANGUAGE")
// Configuração do S3
enableS3Storage = os.Getenv("ENABLE_S3_STORAGE") == "true"
if enableS3Storage {
s3Endpoint = os.Getenv("S3_ENDPOINT")
s3AccessKey = os.Getenv("S3_ACCESS_KEY")
s3SecretKey = os.Getenv("S3_SECRET_KEY")
s3BucketName = os.Getenv("S3_BUCKET_NAME")
s3Region = os.Getenv("S3_REGION")
s3UseSSL = os.Getenv("S3_USE_SSL") == "true"
// Parse URL expiration duration, default to 24 hours
expiration := os.Getenv("S3_URL_EXPIRATION")
if expiration == "" {
expiration = "24h"
}
var err error
s3URLExpiration, err = time.ParseDuration(expiration)
if err != nil {
fmt.Printf("Invalid S3_URL_EXPIRATION format, using default 24h: %v\n", err)
s3URLExpiration = 24 * time.Hour
}
// Initialize MinIO client
minioClient, err := minio.New(s3Endpoint, &minio.Options{
Creds: credentials.NewStaticV4(s3AccessKey, s3SecretKey, ""),
Secure: s3UseSSL,
Region: s3Region,
})
if err != nil {
fmt.Printf("Error initializing S3 client: %v\n", err)
return
}
s3Client = minioClient
// Create bucket if it doesn't exist
exists, err := s3Client.BucketExists(context.Background(), s3BucketName)
if err != nil {
fmt.Printf("Error checking bucket existence: %v\n", err)
return
}
if !exists {
err = s3Client.MakeBucket(context.Background(), s3BucketName, minio.MakeBucketOptions{Region: s3Region})
if err != nil {
fmt.Printf("Error creating bucket: %v\n", err)
return
}
fmt.Printf("Created bucket: %s\n", s3BucketName)
}
}
}
func validateAPIKey(c *gin.Context) bool {
@ -175,6 +240,11 @@ func transcribeAudio(audioData []byte, language string) (string, error) {
return "", errors.New("transcription is not enabled")
}
// Se nenhum idioma foi especificado, use o padrão do .env
if language == "" {
language = defaultTranscriptionLanguage
}
switch transcriptionProvider {
case "openai":
return transcribeWithOpenAI(audioData, language)
@ -339,6 +409,43 @@ func transcribeWithGroq(audioData []byte, language string) (string, error) {
return result.Text, nil
}
func uploadToS3(data []byte, format string) (string, error) {
if !enableS3Storage || s3Client == nil {
return "", errors.New("S3 storage is not enabled or properly configured")
}
// Generate unique filename
filename := fmt.Sprintf("%d.%s", time.Now().UnixNano(), format)
contentType := fmt.Sprintf("audio/%s", format)
// Upload to S3
_, err := s3Client.PutObject(
context.Background(),
s3BucketName,
filename,
bytes.NewReader(data),
int64(len(data)),
minio.PutObjectOptions{ContentType: contentType},
)
if err != nil {
return "", fmt.Errorf("error uploading to S3: %v", err)
}
// Generate presigned URL
url, err := s3Client.PresignedGetObject(
context.Background(),
s3BucketName,
filename,
s3URLExpiration,
nil,
)
if err != nil {
return "", fmt.Errorf("error generating presigned URL: %v", err)
}
return url.String(), nil
}
func processAudio(c *gin.Context) {
if !validateAPIKey(c) {
return
@ -358,26 +465,34 @@ func processAudio(c *gin.Context) {
return
}
var transcription string
if c.DefaultPostForm("transcribe", "false") == "true" {
language := c.DefaultPostForm("language", "")
trans, err := transcribeAudio(convertedData, language)
if err != nil {
fmt.Printf("Erro na transcrição: %v\n", err)
// Continua sem a transcrição
} else {
transcription = trans
}
}
response := gin.H{
"duration": duration,
"audio": base64.StdEncoding.EncodeToString(convertedData),
"format": format,
}
if transcription != "" {
response["transcription"] = transcription
// Handle S3 upload if enabled
if enableS3Storage {
url, err := uploadToS3(convertedData, format)
if err != nil {
fmt.Printf("Error uploading to S3: %v\n", err)
// Fallback to base64 if S3 upload fails
response["audio"] = base64.StdEncoding.EncodeToString(convertedData)
} else {
response["url"] = url
}
} else {
response["audio"] = base64.StdEncoding.EncodeToString(convertedData)
}
// Handle transcription if requested
if c.DefaultPostForm("transcribe", "false") == "true" {
language := c.DefaultPostForm("language", "")
transcription, err := transcribeAudio(convertedData, language)
if err != nil {
fmt.Printf("Error in transcription: %v\n", err)
} else {
response["transcription"] = transcription
}
}
c.JSON(http.StatusOK, response)
@ -456,6 +571,7 @@ func transcribeOnly(c *gin.Context) {
return
}
// Pega o idioma da requisição ou usa vazio para usar o padrão do .env
language := c.DefaultPostForm("language", "")
transcription, err := transcribeAudio(convertedData, language)
if err != nil {