Spaces:
Configuration error
Configuration error
Upload 4 files
Browse filesadded files related to my project
- Dockerfile +26 -0
- app.py +64 -0
- async_ai_voice.py +60 -0
- async_tgvoicebot.py +44 -0
Dockerfile
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# Use the official Python 3.9 image as the base for the container, providing the Python runtime environment.
|
| 3 |
+
FROM python:3.9
|
| 4 |
+
|
| 5 |
+
# Set the working directory inside the container to /code where all subsequent commands will be executed.
|
| 6 |
+
WORKDIR /code
|
| 7 |
+
|
| 8 |
+
# Copy the requirements.txt file from the host to the /code directory in the container. This file lists all the Python dependencies needed for the application.
|
| 9 |
+
COPY ./requirements.txt /code/requirements.txt
|
| 10 |
+
|
| 11 |
+
# Install the Python dependencies specified in requirements.txt using pip.
|
| 12 |
+
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
| 13 |
+
# --no-cache-dir prevents pip from caching packages, reducing the image size.
|
| 14 |
+
# --upgrade ensures that the latest compatible versions of the dependencies are installed.
|
| 15 |
+
# Example: If requirements.txt contains "fastapi==0.70.0", this command will install FastAPI version 0.70.0.
|
| 16 |
+
|
| 17 |
+
# Copy all files from the current directory on the host machine into the /code directory inside the container.
|
| 18 |
+
COPY . /code
|
| 19 |
+
# This includes the application code, configuration files, and any other necessary resources.
|
| 20 |
+
|
| 21 |
+
# Define the default command to run when the container starts.
|
| 22 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
| 23 |
+
# This command launches the Uvicorn server to serve the FastAPI application defined in app.py.
|
| 24 |
+
# --host "0.0.0.0" makes the server accessible externally.
|
| 25 |
+
# --port "7860" sets the port on which the application will listen.
|
| 26 |
+
# Example: Running this command starts the web server, allowing users to access the Telegram Voice Transcription Bot via port 7860.
|
app.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, Request, Response # Import FastAPI classes for creating the web app and handling requests/responses
|
| 2 |
+
from async_tgvoicebot import handle_message # Import the asynchronous function to handle incoming Telegram messages
|
| 3 |
+
import aiohttp # Import aiohttp for making asynchronous HTTP requests
|
| 4 |
+
import os # Import os module to access environment variables
|
| 5 |
+
|
| 6 |
+
app = FastAPI() # Initialize a FastAPI application instance
|
| 7 |
+
|
| 8 |
+
# Retrieve the Telegram bot token from environment variables
|
| 9 |
+
TELEGRAM_BOT_TOKEN = os.environ.get('TELEGRAM_BOT_TOKEN')
|
| 10 |
+
# Retrieve the secret token for webhook security from environment variables
|
| 11 |
+
SECRET_TOKEN = os.environ.get('SECRET_TOKEN')
|
| 12 |
+
|
| 13 |
+
@app.post("/webhook")
|
| 14 |
+
async def webhook(request: Request):
|
| 15 |
+
"""
|
| 16 |
+
Endpoint to receive webhook updates from Telegram.
|
| 17 |
+
Only processes the update if the secret token matches (if provided).
|
| 18 |
+
"""
|
| 19 |
+
# Check if a secret token is set for additional security
|
| 20 |
+
if SECRET_TOKEN:
|
| 21 |
+
# Compare the secret token in the request headers with the expected SECRET_TOKEN
|
| 22 |
+
if request.headers.get('X-Telegram-Bot-Api-Secret-Token') != SECRET_TOKEN:
|
| 23 |
+
# If tokens do not match, respond with 403 Forbidden status
|
| 24 |
+
return Response(status_code=403)
|
| 25 |
+
|
| 26 |
+
# Parse the incoming JSON payload from the request
|
| 27 |
+
update = await request.json()
|
| 28 |
+
# Create an asynchronous HTTP session
|
| 29 |
+
async with aiohttp.ClientSession() as session:
|
| 30 |
+
# Call the handle_message function with the current session and the message part of the update
|
| 31 |
+
await handle_message(session, update.get('message', {}))
|
| 32 |
+
# Respond to Telegram that the update was received and processed successfully
|
| 33 |
+
return {"status": "ok"}
|
| 34 |
+
|
| 35 |
+
@app.get("/")
|
| 36 |
+
async def root():
|
| 37 |
+
"""
|
| 38 |
+
Root endpoint to confirm that the Telegram Bot is running.
|
| 39 |
+
"""
|
| 40 |
+
# Return a simple JSON message indicating the bot's status
|
| 41 |
+
return {"message": "Telegram Bot is running"}
|
| 42 |
+
|
| 43 |
+
# Endpoint to set webhook
|
| 44 |
+
@app.get("/set_webhook")
|
| 45 |
+
async def set_webhook():
|
| 46 |
+
"""
|
| 47 |
+
Endpoint to set the Telegram webhook to the specified URL.
|
| 48 |
+
This should be called after deploying the bot to ensure Telegram sends updates to the correct endpoint.
|
| 49 |
+
"""
|
| 50 |
+
# Construct the webhook URL using the SPACE_ID environment variable
|
| 51 |
+
webhook_url = f"https://{os.environ.get('SPACE_ID')}.hf.space/webhook"
|
| 52 |
+
# Telegram API URL to set the webhook
|
| 53 |
+
set_webhook_url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/setWebhook"
|
| 54 |
+
# Payload containing the webhook URL and the secret token for security
|
| 55 |
+
payload = {
|
| 56 |
+
"url": webhook_url,
|
| 57 |
+
"secret_token": SECRET_TOKEN
|
| 58 |
+
}
|
| 59 |
+
# Create an asynchronous HTTP session
|
| 60 |
+
async with aiohttp.ClientSession() as session:
|
| 61 |
+
# Send a POST request to Telegram's setWebhook API with the payload
|
| 62 |
+
async with session.post(set_webhook_url, json=payload) as response:
|
| 63 |
+
# Await and return the JSON response from Telegram confirming the webhook setup
|
| 64 |
+
return await response.json()
|
async_ai_voice.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import aiohttp # Import aiohttp for making asynchronous HTTP requests
|
| 2 |
+
import os # Import os to access environment variables
|
| 3 |
+
import json # Import json to handle JSON data
|
| 4 |
+
from typing import Dict, Any # Import type hints for better code clarity
|
| 5 |
+
|
| 6 |
+
# Define the API endpoint URL for the Hugging Face Whisper model
|
| 7 |
+
API_URL = "https://api-inference.huggingface.co/models/distil-whisper/distil-large-v3"
|
| 8 |
+
|
| 9 |
+
# Set up the headers with the authorization token retrieved from environment variables
|
| 10 |
+
headers = {"Authorization": f"Bearer {os.getenv('HUGGINGFACE_API_KEY')}"}
|
| 11 |
+
|
| 12 |
+
async def send_to_voice_AI(audio_data: bytes) -> Dict[str, Any]:
|
| 13 |
+
"""
|
| 14 |
+
Asynchronously sends audio data to the Hugging Face Whisper model for transcription.
|
| 15 |
+
|
| 16 |
+
Args:
|
| 17 |
+
audio_data (bytes): The raw audio data to be transcribed.
|
| 18 |
+
|
| 19 |
+
Returns:
|
| 20 |
+
Dict[str, Any]: A dictionary containing the transcription result or error message.
|
| 21 |
+
"""
|
| 22 |
+
print("Sending to voice AI") # Log the action for debugging purposes
|
| 23 |
+
|
| 24 |
+
# Create an asynchronous HTTP session using aiohttp
|
| 25 |
+
async with aiohttp.ClientSession() as session:
|
| 26 |
+
try:
|
| 27 |
+
# Asynchronously send a POST request to the API_URL with headers and audio data
|
| 28 |
+
async with session.post(API_URL, headers=headers, data=audio_data) as response:
|
| 29 |
+
# Await the response to ensure the request completes before proceeding
|
| 30 |
+
if response.status == 200:
|
| 31 |
+
# If the response is successful, await and return the JSON content
|
| 32 |
+
return await response.json()
|
| 33 |
+
else:
|
| 34 |
+
# If the response status is not 200, await the text of the response
|
| 35 |
+
error_text = await response.text()
|
| 36 |
+
# Parse the error text into a JSON object
|
| 37 |
+
error_json = json.loads(error_text)
|
| 38 |
+
# Return a dictionary with an error message and estimated loading time
|
| 39 |
+
return {
|
| 40 |
+
"text": f"error: {error_json.get('error', 'Unknown error')}\n"
|
| 41 |
+
f"Estimated time to load: {error_json.get('estimated_time', 'unknown')} seconds"
|
| 42 |
+
}
|
| 43 |
+
except aiohttp.ClientError as e:
|
| 44 |
+
# Handle any client-related errors that occur during the request
|
| 45 |
+
return {"text": f"Network error: {str(e)}"}
|
| 46 |
+
except json.JSONDecodeError:
|
| 47 |
+
# Handle errors that occur if the response cannot be parsed as JSON
|
| 48 |
+
return {"text": "Error: Unable to parse API response"}
|
| 49 |
+
|
| 50 |
+
# Example usage:
|
| 51 |
+
# The following example demonstrates how to use the send_to_voice_AI function asynchronously.
|
| 52 |
+
# It reads an audio file, sends it to the voice AI, and prints the output.
|
| 53 |
+
|
| 54 |
+
# import asyncio # Import asyncio to run the asynchronous function
|
| 55 |
+
# async def example():
|
| 56 |
+
# with open("sample1.flac", "rb") as f: # Open the audio file in binary read mode
|
| 57 |
+
# audio_data = f.read() # Read the audio data from the file
|
| 58 |
+
# output = await send_to_voice_AI(audio_data) # Await the asynchronous transcription function
|
| 59 |
+
# print(output) # Print the transcription result
|
| 60 |
+
# asyncio.run(example()) # Run the asynchronous example function
|
async_tgvoicebot.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio # Imports the asyncio library for writing single-threaded concurrent code using coroutines
|
| 2 |
+
import aiohttp # Imports aiohttp for making asynchronous HTTP requests
|
| 3 |
+
import os # Imports the os module to interact with the operating system
|
| 4 |
+
from dotenv import load_dotenv # Imports load_dotenv to load environment variables from a .env file
|
| 5 |
+
load_dotenv() # Loads environment variables from the .env file into the program's environment
|
| 6 |
+
from async_ai_voice import send_to_voice_AI # Imports the send_to_voice_AI function from the async_ai_voice module
|
| 7 |
+
|
| 8 |
+
# Telegram Bot API setup
|
| 9 |
+
TELEGRAM_BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN') # Retrieves the Telegram bot token from environment variables
|
| 10 |
+
TELEGRAM_API_URL = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}" # Constructs the base Telegram API URL using the bot token
|
| 11 |
+
|
| 12 |
+
async def get_voice_file(session, file_id):
|
| 13 |
+
"""
|
| 14 |
+
Asynchronous function to retrieve the voice file from Telegram using the file ID.
|
| 15 |
+
"""
|
| 16 |
+
# Get file path
|
| 17 |
+
file_path_url = f"{TELEGRAM_API_URL}/getFile?file_id={file_id}" # Constructs the URL to get file information from Telegram
|
| 18 |
+
async with session.get(file_path_url) as response: # Asynchronously sends a GET request to the file_path_url
|
| 19 |
+
file_info = await response.json() # Awaits and parses the JSON response containing file information
|
| 20 |
+
file_path = file_info['result']['file_path'] # Extracts the file path from the response
|
| 21 |
+
|
| 22 |
+
# Download file
|
| 23 |
+
file_url = f"https://api.telegram.org/file/bot{TELEGRAM_BOT_TOKEN}/{file_path}" # Constructs the URL to download the actual voice file
|
| 24 |
+
async with session.get(file_url) as response: # Asynchronously sends a GET request to download the file
|
| 25 |
+
return await response.read() # Awaits and returns the raw bytes of the downloaded file
|
| 26 |
+
|
| 27 |
+
async def handle_message(session, message):
|
| 28 |
+
"""
|
| 29 |
+
Asynchronous function to handle incoming Telegram messages.
|
| 30 |
+
"""
|
| 31 |
+
if 'voice' in message: # Checks if the incoming message contains a voice message
|
| 32 |
+
file_id = message['voice']['file_id'] # Retrieves the file ID of the voice message
|
| 33 |
+
audio_data = await get_voice_file(session, file_id) # Awaits the retrieval of the audio data using the get_voice_file function
|
| 34 |
+
transcription = await send_to_voice_AI(audio_data) # Awaits the transcription of the audio data using the AI service
|
| 35 |
+
|
| 36 |
+
# Send transcription back to user
|
| 37 |
+
chat_id = message['chat']['id'] # Retrieves the chat ID to identify where to send the response
|
| 38 |
+
text = transcription.get('text', 'Sorry, could not transcribe the audio due to AI error.') # Gets the transcribed text or a default error message
|
| 39 |
+
send_message_url = f"{TELEGRAM_API_URL}/sendMessage" # Constructs the URL to send a message via the Telegram API
|
| 40 |
+
payload = {
|
| 41 |
+
'chat_id': chat_id, # Specifies the chat ID where the message should be sent
|
| 42 |
+
'text': text # The transcribed text to send back to the user
|
| 43 |
+
}
|
| 44 |
+
await session.post(send_message_url, json=payload) # Asynchronously sends a POST request to send the transcription back to the user
|