Skip to content

Commit

Permalink
v0.10 - /model & /help commands
Browse files Browse the repository at this point in the history
  • Loading branch information
FlyingFathead committed Apr 24, 2024
1 parent 9538665 commit 2c7139a
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 50 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ After launching the bot, you can interact with it via Telegram:

## Changes

- v0.10 - `/help` & `/about` commands added for further assistance
- `config.ini` now has a list of supported models that can be changed as needed
- v0.09 - users can now change the model Whisper model with `/model` command
- v0.08 - auto-retry TG connection on start-up connection failure
- can be set in `config.ini` with `RestartOnConnectionFailure`
- v0.07.7 - log output from `whisper` to logging
- v0.07.6 - update interval for logging `yt-dlp` downloads now configurable from `config.ini`
- v0.07.5 - 10-second interval update for `yt-dlp` logging
Expand Down
26 changes: 10 additions & 16 deletions config/config.ini
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
[DEFAULT]
# Set to `True` to prioritize the TELEGRAM_BOT_TOKEN environment variable over bot_token.txt
# Set to `False` to prioritize bot_token.txt over the TELEGRAM_BOT_TOKEN environment variable
PreferEnvForBotToken = False
preferenvforbottoken = False

[GeneralSettings]
# Allow transcribing from all sites (= `True`) or only from YouTube (= `False`)
AllowAllSites = True
restartonconnectionfailure = True
allowallsites = True

[TranscriptionSettings]
# Include video details in the header of the `.txt` version of the transcription
IncludeHeaderInTranscription = True

# Option to keep audio files after processing. Set to False to delete them.
KeepAudioFiles = False
includeheaderintranscription = True
keepaudiofiles = False

[WhisperSettings]
# Default Whisper local model type to use
Model = medium.en
# For accuracy, you can i.e. set it to `medium.en` or such if you know the spoken language
# Model = large-v3
model = large-v3

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

[LoggingSettings]
# Update interval for yt-dlp backend
UpdateIntervalSeconds = 10
updateintervalseconds = 10
126 changes: 93 additions & 33 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,36 @@
# openai-whisper transcriber-bot for Telegram

# version of this program
version_number = "0.07.7"
version_number = "0.10"

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# https://github.com/FlyingFathead/whisper-transcriber-telegram-bot/
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

import time
import re
import signal
import asyncio
import logging
import configparser

from telegram import Update
from telegram.ext import Application, MessageHandler, filters, CallbackContext
from telegram.ext import CommandHandler

# Adjust import paths based on new structure
from transcription_handler import process_url_message
from utils.bot_token import get_bot_token
from utils.language_selection import ask_language
from utils.utils import print_startup_message

# Configure basic logging
logging.basicConfig(format='[%(asctime)s] %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
logger = logging.getLogger(__name__)

# Call the startup message function
print_startup_message(version_number)
# Read configuration for restart behavior
config = configparser.ConfigParser()
config.read('config/config.ini')
restart_on_failure = config.getboolean('GeneralSettings', 'RestartOnConnectionFailure', fallback=True)

# Initialize the lock outside of your function to ensure it's shared across all invocations.
queue_lock = asyncio.Lock()
Expand All @@ -40,9 +44,15 @@ class TranscriberBot:

def __init__(self):
self.token = get_bot_token()
self.task_queue = asyncio.Queue() # queue tasks
self.task_queue = asyncio.Queue() # queue tasks
self.is_processing = asyncio.Lock() # Lock to ensure one transcription at a time

self.restart_on_failure = restart_on_failure # Controls the restart behavior on connection failure

self.config = configparser.ConfigParser()
self.config.read('config/config.ini')
self.model = self.config.get('WhisperSettings', 'Model', fallback='medium.en')
self.valid_models = self.config.get('ModelSettings', 'ValidModels', fallback='tiny, base, small, medium, large').split(', ')

async def handle_message(self, update: Update, context: CallbackContext) -> None:
logger.info("Received a message.")
Expand All @@ -56,21 +66,10 @@ async def handle_message(self, update: Update, context: CallbackContext) -> None
logger.info(f"Task added to the queue. Current queue size: {queue_length}")

# Check if this is the only job and nothing is currently processing.
if queue_length == 1 and not self.is_processing.locked():
await update.message.reply_text(
"Your request is next and is currently being processed."
)
else:
# When there are other jobs or a job is currently being processed.
# Adjust the count to start from 1 if the queue is not empty.
jobs_ahead = queue_length
await update.message.reply_text(
f"Your request has been added to the queue. There are {jobs_ahead} jobs ahead of yours."
)
response_text = "Your request is next and is currently being processed." if queue_length == 1 else f"Your request has been added to the queue. There are {queue_length - 1} jobs ahead of yours."
await update.message.reply_text(response_text)
else:
await update.message.reply_text(
"No valid URL detected in your message. Please send a message that includes a valid URL."
)
await update.message.reply_text("No valid URL detected in your message. Please send a message that includes a valid URL. If you need help, type: /help")

async def process_queue(self):
while True:
Expand All @@ -79,32 +78,93 @@ async def process_queue(self):
await process_url_message(message_text, bot, update)
self.task_queue.task_done()

async def shutdown(signal, loop):
async def shutdown(self, signal, loop):
"""Cleanup tasks tied to the service's shutdown."""
print(f"Received exit signal {signal.name}...")
logger.info(f"Received exit signal {signal.name}...")
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]

[task.cancel() for task in tasks]

print(f"Cancelling {len(tasks)} outstanding tasks")
logger.info(f"Cancelling {len(tasks)} outstanding tasks")
await asyncio.gather(*tasks, return_exceptions=True)
loop.stop()

def run(self):

loop = asyncio.get_event_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!
Version number: {self.version_number}
How to Use:
- Send any supported media URL to have its audio transcribed.
- Use /model to change the transcription model (currently set to '{self.model}').
- Use /help or /about to display this help message.
Available Models:
{models_list}
Code by FlyingFathead.
Source Code:
https://github.com/FlyingFathead/whisper-transcriber-telegram-bot/
Disclaimer:
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)

async def model_command(self, update: Update, context: CallbackContext) -> None:
# If no specific model is specified, just report the current model
if not context.args:
await update.message.reply_text(f"The current transcription model is set to: {self.model}")
else:
new_model = context.args[0].strip()
if new_model in self.valid_models:
self.model = new_model
self.config.set('WhisperSettings', 'Model', new_model)
with open('config/config.ini', 'w') as configfile:
self.config.write(configfile)
await update.message.reply_text(f"Model updated to {new_model}.")
else:
models_list = ', '.join(self.valid_models)
await update.message.reply_text(f"Invalid model specified. Available models: {models_list}.")

# Using 'sig' as the variable name to avoid conflict with the 'signal' module
def run(self):
loop = asyncio.get_event_loop()

for sig in [signal.SIGINT, signal.SIGTERM]:
loop.add_signal_handler(sig, lambda s=sig: asyncio.create_task(self.shutdown(s, loop)))

self.application = Application.builder().token(self.token).build()
text_handler = MessageHandler(filters.TEXT & ~filters.COMMAND, self.handle_message)
self.application.add_handler(text_handler)

loop.create_task(self.process_queue())
self.application.run_polling()
connected = False
while not connected:
try:
self.application = Application.builder().token(self.token).build()

# Adding handlers
text_handler = MessageHandler(filters.TEXT & ~filters.COMMAND, self.handle_message)
self.application.add_handler(text_handler)

# Here's where you add the command handler for /help
help_handler = CommandHandler(['help', 'about'], self.help_command)
self.application.add_handler(help_handler)

# Adding model command handler
model_handler = CommandHandler('model', self.model_command)
self.application.add_handler(model_handler)

loop.create_task(self.process_queue())
self.application.run_polling()
connected = True
except Exception as e:
logger.error(f"Failed to start polling due to an error: {e}")
if self.restart_on_failure:
logger.info("Attempting to reconnect in 10 seconds...")
time.sleep(10)
else:
logger.error("Restart on failure is disabled. Exiting...")
break

if __name__ == '__main__':
print_startup_message(version_number) # Print startup message
bot = TranscriberBot()
bot.run()
bot.run()
4 changes: 3 additions & 1 deletion src/transcription_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ def log_stderr(line):
logger.error(f"Whisper stderr: {line.strip()}")

# transcription logic with header inclusion based on settings
async def transcribe_audio(audio_path, output_dir, youtube_url, video_info_message, include_header):
async def transcribe_audio(audio_path, output_dir, youtube_url, video_info_message, include_header, model):

# set the transcription command
model = get_whisper_model()
Expand Down Expand Up @@ -254,6 +254,8 @@ async def transcribe_audio(audio_path, output_dir, youtube_url, video_info_messa
async def process_url_message(message_text, bot, update):

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

0 comments on commit 2c7139a

Please sign in to comment.