Skip to content

Commit

Permalink
v0.12
Browse files Browse the repository at this point in the history
  • Loading branch information
FlyingFathead committed Apr 24, 2024
1 parent 03214eb commit 40c07b5
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 48 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ After launching the bot, you can interact with it via Telegram:

## Changes

- v0.12 - async handling & user model change fixes, improved error handling
- v0.11.1 - bot logic + layout changes, model list with `/model` (also in `config.ini`)
- v0.11 - bugfixes & rate limits for `/model` command changes for users
- v0.10 - `/help` & `/about` commands added for further assistance
- `config.ini` now has a list of supported models that can be changed as needed
Expand Down
7 changes: 4 additions & 3 deletions config/config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ keepaudiofiles = False

[WhisperSettings]
model = medium.en
autodetect = True
supportedlanguages = af, am, ar, as, az, ba, be, bg, bn, bo, br, bs, ca, cs, cy, da, de, el, en, es, et, eu, fa, fi, fo, fr, gl, gu, ha, haw, he, hi, hr, ht, hu, hy, id, is, it, ja, jw, ka, kk, km, kn, ko, la, lb, ln, lo, lt, lv, mg, mi, mk, ml, mn, mr, ms, mt, my, ne, nl, nn, no, oc, pa, pl, ps, pt, ro, ru, sa, sd, si, sk, sl, sn, so, sq, sr, su, sv, sw, ta, te, tg, th, tk, tl, tr, tt, uk, ur, uz, vi, yi, yo, yue, zh, Afrikaans, Albanian, Amharic, Arabic, Armenian, Assamese, Azerbaijani, Bashkir, Basque, Belarusian, Bengali, Bosnian, Breton, Bulgarian, Burmese, Cantonese, Castilian, Catalan, Chinese, Croatian, Czech, Danish, Dutch, English, Estonian, Faroese, Finnish, Flemish, French, Galician, Georgian, German, Greek, Gujarati, Haitian, Haitian Creole, Hausa, Hawaiian, Hebrew, Hindi, Hungarian, Icelandic, Indonesian, Italian, Japanese, Javanese, Kannada

[ModelSettings]
validmodels = tiny, base, small, medium, medium.en, large-v3
validmodels = tiny.en, tiny, base.en, base, small.en, small, medium.en, medium, large-v3

[LoggingSettings]
updateintervalseconds = 10

updateintervalseconds = 10
80 changes: 51 additions & 29 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# openai-whisper transcriber-bot for Telegram

# version of this program
version_number = "0.11"
version_number = "0.12"

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# https://github.com/FlyingFathead/whisper-transcriber-telegram-bot/
Expand All @@ -21,7 +21,7 @@
from telegram.ext import CommandHandler

# Adjust import paths based on new structure
from transcription_handler import process_url_message
from transcription_handler import process_url_message, set_user_model, get_whisper_model
from utils.bot_token import get_bot_token
from utils.utils import print_startup_message

Expand All @@ -42,6 +42,9 @@ class TranscriberBot:
# version of this program
version_number = version_number

# Class-level attribute for global locking
processing_lock = asyncio.Lock()

def __init__(self):
self.token = get_bot_token()
self.task_queue = asyncio.Queue() # queue tasks
Expand All @@ -55,11 +58,23 @@ def __init__(self):
self.valid_models = self.config.get('ModelSettings', 'ValidModels', fallback='tiny, base, small, medium, large').split(', ')

self.model_change_limits = {} # Dictionary to track user rate limits
self.model_change_cooldown = 60 # Cooldown period in seconds
self.model_change_cooldown = 20 # Cooldown period in seconds
self.user_models = {} # Use a dictionary to manage models per user.
self.user_models_lock = asyncio.Lock() # Lock for handling user_models dictionary

async def handle_message(self, update: Update, context: CallbackContext) -> None:
logger.info("Received a message.")

user_id = update.effective_user.id # Update the user_id
message_text = update.message.text # Get the text of the message received

# Log the received message along with the user ID
logger.info(f"Received a message from user ID {user_id}: {message_text}")

# Check and log the model before starting transcription
current_model = get_whisper_model(user_id)

logger.debug(f"Current model for user {user_id} before transcription: {current_model}")

if update.message and update.message.text:
urls = re.findall(r'(https?:https://\S+)', update.message.text)

Expand All @@ -78,8 +93,10 @@ async def handle_message(self, update: Update, context: CallbackContext) -> None
async def process_queue(self):
while True:
message_text, bot, update = await self.task_queue.get()
async with self.is_processing: # Ensure one job at a time
await process_url_message(message_text, bot, update)
async with TranscriberBot.processing_lock: # Use the class-level lock
user_id = update.effective_user.id
model = get_whisper_model(user_id)
await process_url_message(message_text, bot, update, model)
self.task_queue.task_done()

async def shutdown(self, signal, loop):
Expand All @@ -95,50 +112,55 @@ async def shutdown(self, signal, loop):

async def help_command(self, update: Update, context: CallbackContext) -> None:
models_list = ', '.join(self.valid_models) # Dynamically generate the list of valid models
help_text = f"""Welcome to the Whisper Transcriber Bot!
help_text = f"""<b>Welcome to the Whisper Transcriber Bot!</b>
Version number: {self.version_number}
<b>Version:</b> {self.version_number}
How to Use:
<b>How to Use:</b>
- Send any supported media URL to have its audio transcribed.
- Use /model to change the transcription model (currently set to '{self.model}').
- Use /model to change the transcription model.
- Use /help or /about to display this help message.
Available Models:
{models_list}
<b>Whisper model currently in use:</b>
<code>{self.model}</code>
Code by FlyingFathead.
<b>Available Whisper models:</b>
{models_list}
Source Code:
https://github.com/FlyingFathead/whisper-transcriber-telegram-bot/
<b>Bot code by FlyingFathead.</b>
Source code on <a href='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/FlyingFathead/whisper-transcriber-telegram-bot/'>GitHub</a>.
Disclaimer:
<b>Disclaimer:</b>
The original author is not responsible for how this bot is utilized. All code and outputs are provided 'AS IS' without warranty of any kind. Users assume full responsibility for the operation and output of the bot. Use at your own risk.
"""
await update.message.reply_text(help_text)
await update.message.reply_text(help_text, parse_mode='HTML')

async def model_command(self, update: Update, context: CallbackContext) -> None:
user_id = update.effective_user.id
current_time = time.time()
models_list = ', '.join(self.valid_models) # Dynamically generate the list of valid models

if not context.args:
await update.message.reply_text(f"The current transcription model is set to: {self.model}")
return

# Cooldown check
if user_id in self.model_change_limits and current_time - self.model_change_limits[user_id] < self.model_change_cooldown:
cooldown_remaining = self.model_change_cooldown - (current_time - self.model_change_limits[user_id])
await update.message.reply_text(f"Please wait {cooldown_remaining:.0f} more seconds before changing the model again. Current model is '{self.model}'.")
current_model = get_whisper_model(user_id)
await update.message.reply_text(
f"<b>Current model:</b>\n<code>{current_model}</code>\n\n"
f"<b>Available models:</b>\n{models_list}\n\n"
"To change the model, use commands like:\n"
"<code>/model medium.en</code>\n"
"<code>/model large-v3</code>",
parse_mode='HTML')
return

new_model = context.args[0]
if new_model in self.valid_models:
self.model = new_model
self.model_change_limits[user_id] = current_time # Record the change time
await update.message.reply_text(f"Model updated to: {new_model}")
set_user_model(user_id, new_model) # Update user-specific model
self.model_change_limits[user_id] = current_time # Update cooldown tracker
await update.message.reply_text(f"Model updated to: <code>{new_model}</code>", parse_mode='HTML')
else:
models_list = ', '.join(self.valid_models)
await update.message.reply_text(f"Invalid model specified.\n\nAvailable models: {models_list}")
await update.message.reply_text(
f"Invalid model specified.\n\n"
f"Available models:\n{models_list}",
parse_mode='HTML')

def run(self):
loop = asyncio.get_event_loop()
Expand Down
53 changes: 37 additions & 16 deletions src/transcription_handler.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# transcription_handler.py
# ~~~
# openai-whisper transcriber-bot for Telegram
# v0.07.7
# v0.12
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# https://github.com/FlyingFathead/whisper-transcriber-telegram-bot/
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand All @@ -10,6 +10,7 @@
import time
import logging
import re
import threading
import asyncio
from asyncio.exceptions import TimeoutError
import json
Expand Down Expand Up @@ -49,6 +50,12 @@
# set the config base dir just once at the top of your script
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Define a dictionary at the module level to store user-specific models
user_models = {}

# lock the user models
user_models_lock = threading.Lock()

# get the general settings
def get_general_settings():
config = configparser.ConfigParser()
Expand All @@ -68,12 +75,31 @@ def get_logging_settings():
return update_interval

# get whisper model
def get_whisper_model():
config = configparser.ConfigParser()
config_path = os.path.join(base_dir, 'config', 'config.ini')
config.read(config_path)
model = config.get('WhisperSettings', 'Model', fallback='base')
return model
def get_whisper_model(user_id=None):
with user_models_lock: # Acquire the lock before accessing user_models
logger.debug(f"Attempting to fetch model for user_id: {user_id}")
global user_models
if user_id is None or user_id not in user_models:
config = configparser.ConfigParser()
config_path = os.path.join(base_dir, 'config', 'config.ini')
config.read(config_path)
default_model = config.get('WhisperSettings', 'Model', fallback='medium.en')
logger.info(f"No custom model for user {user_id}. Using default model: {default_model}")
return default_model
else:
custom_model = user_models[user_id]
logger.info(f"Returning custom model for user {user_id}: {custom_model}")
return custom_model

# Modify the set_user_model function to use the lock
def set_user_model(user_id, model):
with user_models_lock: # Acquire the lock before modifying user_models
global user_models
if user_id and model:
user_models[user_id] = model
logger.info(f"Model set for user {user_id}: {model}")
else:
logger.error(f"Failed to set model for user {user_id}: {model}")

# get transcription settings
def get_transcription_settings():
Expand Down Expand Up @@ -190,9 +216,6 @@ def log_stderr(line):
# transcription logic with header inclusion based on settings
async def transcribe_audio(audio_path, output_dir, youtube_url, video_info_message, include_header, model):

# set the transcription command
model = get_whisper_model()

logger.info(f"Starting transcription with model '{model}' for: {audio_path}")

transcription_command = ["whisper", audio_path, "--model", model, "--output_dir", output_dir]
Expand Down Expand Up @@ -251,12 +274,10 @@ async def transcribe_audio(audio_path, output_dir, youtube_url, video_info_messa
return created_files

# Process the message's URL and keep the user informed
async def process_url_message(message_text, bot, update):
async def process_url_message(message_text, bot, update, model):

try:

model = get_whisper_model() # Ensure the latest model is fetched dynamically

# Get general settings right at the beginning of the function
settings = get_general_settings()

Expand Down Expand Up @@ -330,7 +351,7 @@ async def process_url_message(message_text, bot, update):
continue

# Inform the user that the transcription process has started and do a time estimate
model = get_whisper_model()
model = get_whisper_model(user_id)

# Use the audio duration from the video details
audio_duration = details['audio_duration']
Expand Down Expand Up @@ -367,7 +388,7 @@ async def process_url_message(message_text, bot, update):
await bot.send_message(chat_id=update.effective_chat.id, text=detailed_message)

# Transcribe the audio and handle transcription output
model = get_whisper_model() # Ensure you fetch the current model setting
model = get_whisper_model(user_id) # Ensure you fetch the current model setting
if not model:
logger.error("Failed to retrieve the transcription model.")
return
Expand All @@ -387,7 +408,7 @@ async def process_url_message(message_text, bot, update):
os.remove(audio_path)

# Log the completion message with user ID and video URL
completion_log_message = f"Translation complete for user {user_id}, video: {normalized_url}"
completion_log_message = f"Translation complete for user {user_id}, video: {normalized_url}, model: {model}"
logging.info(completion_log_message)
await bot.send_message(chat_id=update.effective_chat.id, text="Transcription complete. Have a nice day!")

Expand Down
8 changes: 8 additions & 0 deletions src/utils/get_whisper_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import whisper

# List all available Whisper models
available_models = whisper.available_models()

print("Available Whisper models:")
for model in available_models:
print(model)

0 comments on commit 40c07b5

Please sign in to comment.