fedirz / faster-whisper-server

https://hub.docker.com/r/fedirz/faster-whisper-server
MIT License
791 stars 112 forks source link

faster-whisper-server suddenly broken (ValueError: max() iterable argument is empty) #70

Open Arche151 opened 2 months ago

Arche151 commented 2 months ago

I've been using faster-whisper-server via Docker for weeks with no issues with my transcription script on Ubuntu, but suddenly the server is just broken.

I get this error, whenever I try to transcribe something:

INFO:     Started server process [1]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
2024-09-03 10:54:59,418:DEBUG:faster_whisper_server.logger:load_model:Loading Systran/faster-whisper-large-v2...
2024-09-03 10:55:02,625:INFO:faster_whisper_server.logger:load_model:Loaded Systran/faster-whisper-large-v2 loaded in 3.21 seconds. auto(default) will be used for inference.
INFO:     172.17.0.1:45646 - "POST /v1/audio/transcriptions HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/usr/local/lib/python3.12/dist-packages/uvicorn/protocols/http/h11_impl.py", line 406, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/dist-packages/uvicorn/middleware/proxy_headers.py", line 70, in __call__
    return await self.app(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/dist-packages/fastapi/applications.py", line 1054, in __call__
    await super().__call__(scope, receive, send)
  File "/usr/local/lib/python3.12/dist-packages/starlette/applications.py", line 123, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/usr/local/lib/python3.12/dist-packages/starlette/middleware/errors.py", line 186, in __call__
    raise exc
  File "/usr/local/lib/python3.12/dist-packages/starlette/middleware/errors.py", line 164, in __call__
    await self.app(scope, receive, _send)
  File "/usr/local/lib/python3.12/dist-packages/starlette/middleware/exceptions.py", line 65, in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
  File "/usr/local/lib/python3.12/dist-packages/starlette/_exception_handler.py", line 64, in wrapped_app
    raise exc
  File "/usr/local/lib/python3.12/dist-packages/starlette/_exception_handler.py", line 53, in wrapped_app
    await app(scope, receive, sender)
  File "/usr/local/lib/python3.12/dist-packages/starlette/routing.py", line 754, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/usr/local/lib/python3.12/dist-packages/starlette/routing.py", line 774, in app
    await route.handle(scope, receive, send)
  File "/usr/local/lib/python3.12/dist-packages/starlette/routing.py", line 295, in handle
    await self.app(scope, receive, send)
  File "/usr/local/lib/python3.12/dist-packages/starlette/routing.py", line 77, in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
  File "/usr/local/lib/python3.12/dist-packages/starlette/_exception_handler.py", line 64, in wrapped_app
    raise exc
  File "/usr/local/lib/python3.12/dist-packages/starlette/_exception_handler.py", line 53, in wrapped_app
    await app(scope, receive, sender)
  File "/usr/local/lib/python3.12/dist-packages/starlette/routing.py", line 74, in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/dist-packages/fastapi/routing.py", line 297, in app
    raw_response = await run_endpoint_function(
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/dist-packages/fastapi/routing.py", line 212, in run_endpoint_function
    return await run_in_threadpool(dependant.call, **values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/dist-packages/starlette/concurrency.py", line 42, in run_in_threadpool
    return await anyio.to_thread.run_sync(func, *args)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/dist-packages/anyio/to_thread.py", line 56, in run_sync
    return await get_async_backend().run_sync_in_worker_thread(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/dist-packages/anyio/_backends/_asyncio.py", line 2177, in run_sync_in_worker_thread
    return await future
           ^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/dist-packages/anyio/_backends/_asyncio.py", line 859, in run
    result = context.run(func, *args)
             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/faster-whisper-server/faster_whisper_server/main.py", line 285, in transcribe_file
    segments, transcription_info = whisper.transcribe(
                                   ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/dist-packages/faster_whisper/transcribe.py", line 419, in transcribe
    language = max(
               ^^^^
ValueError: max() iterable argument is empty

I updated to the latest Docker image, but I get the same error. Would greatly appreciate any help, since I use faster-whisper-server every day and it's crucial for my workflow.

Here is my keyboard-shortcut triggered transcription script that I've been using with faster-whisper-server:

import subprocess
import os
import logging
import time
import requests
import threading
import dbus
import signal

# Configure logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')

audio_file = "/tmp/audio_recording.wav"
recording_state_file = "/tmp/recording_state"
volume_state_file = "/tmp/volume_state"

def get_current_volume():
    result = subprocess.run(["pactl", "get-sink-volume", "@DEFAULT_SINK@"], capture_output=True, text=True)
    for line in result.stdout.split('\n'):
        if 'Volume:' in line:
            return line.split('/')[1].strip().rstrip('%')
    return None

def set_volume(volume):
    subprocess.run(["pactl", "set-sink-volume", "@DEFAULT_SINK@", f"{volume}%"])

def smooth_volume_change(start_volume, end_volume, duration=0.5, steps=10):
    volume_change = end_volume - start_volume
    step_size = volume_change / steps
    step_duration = duration / steps

    current_volume = start_volume
    for _ in range(steps):
        current_volume += step_size
        set_volume(int(current_volume))
        time.sleep(step_duration)

def start_recording():
    logging.debug("Starting recording...")

    current_volume = int(get_current_volume())
    with open(volume_state_file, 'w') as f:
        f.write(str(current_volume))

    smooth_volume_change(current_volume, 20, duration=0.25)

    subprocess.Popen(["ffmpeg", "-f", "pulse", "-i", "default", "-y", audio_file])
    open(recording_state_file, 'w').close()

def stop_recording():
    logging.debug("Stopping recording...")

    # Send SIGTERM to ffmpeg
    ffmpeg_process = subprocess.run(["pgrep", "ffmpeg"], capture_output=True, text=True)
    if ffmpeg_process.stdout:
        pid = int(ffmpeg_process.stdout.strip())
        os.kill(pid, signal.SIGTERM)

        # Wait for ffmpeg to finish gracefully (max 3 seconds)
        for _ in range(30):
            if subprocess.run(["pgrep", "ffmpeg"], capture_output=True).returncode != 0:
                break
            time.sleep(0.1)
        else:
            # If ffmpeg hasn't terminated after 3 seconds, force kill it
            subprocess.call(["pkill", "-9", "ffmpeg"])

    # Restore original volume smoothly
    if os.path.exists(volume_state_file):
        with open(volume_state_file, 'r') as f:
            original_volume = int(f.read().strip())
        current_volume = int(get_current_volume())
        smooth_volume_change(current_volume, original_volume, duration=0.5)
        os.remove(volume_state_file)

    if os.path.exists(recording_state_file):
        os.remove(recording_state_file)

    # Add a small delay before transcription to ensure the file is completely written
    time.sleep(0.05)

    transcribe_audio()
    if os.path.exists(audio_file):
        os.remove(audio_file)

def is_recording():
    return os.path.exists(recording_state_file)

def send_temporary_notification(title, message, duration=5):
    bus = dbus.SessionBus()
    notify_obj = bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications')
    notify_iface = dbus.Interface(notify_obj, 'org.freedesktop.Notifications')
    notification_id = notify_iface.Notify('', 0, '', title, message, [], {}, -1)

    def close_notification():
        time.sleep(duration)
        notify_iface.CloseNotification(notification_id)

    threading.Thread(target=close_notification).start()

def transcribe_audio():
    if os.path.exists(audio_file) and os.path.getsize(audio_file) > 0:
        try:
            logging.debug(f"Audio file found: {audio_file} (size: {os.path.getsize(audio_file)} bytes)")

            url = "http://localhost:8000/v1/audio/transcriptions"
            files = {'file': open(audio_file, 'rb')}
            data = {'model': 'Systran/faster-whisper-large-v2'}

            response = requests.post(url, files=files, data=data)

            if response.status_code == 200:
                transcription = response.json()['text']

                logging.debug("Transcription completed. Copying to clipboard...")

                subprocess.run(["xclip", "-selection", "clipboard"], input=transcription.encode(), check=True)

                logging.debug("Transcription copied to clipboard.")

                send_temporary_notification("Transcription Complete", "The transcription has been copied to the clipboard.", 5)
            else:
                logging.error(f"Error during transcription: {response.text}")
        except Exception as e:
            logging.error(f"Error during transcription: {e}")
    else:
        logging.error("Audio file not found or is empty. Skipping transcription.")

def main():
    if is_recording():
        stop_recording()
    else:
        start_recording()

if __name__ == "__main__":
    main()
Arche151 commented 2 months ago

@luochen1990 suggested setting the audio language as a workaround in this bug report: https://github.com/fedirz/faster-whisper-server/issues/59 but thatÄs not an option for me, unfortunately, since I use faster-whisper-server with multiple languages.,

luochen1990 commented 2 months ago

@Arche151 So maybe we should keep this issue open if it is not solved ?

Arche151 commented 2 months ago

@luochen1990 The mistake was on my side. I had my microphone off ^^ After enabling it again, everything worked fine.

thiswillbeyourgithub commented 2 months ago

I have the same error but I think it's actually working still? Like IIRC I see that often in the logs but still seem to get an answer.