mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
Added a download tracker that displays informative messages
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
- Added voice preview caching system that stores generated previews in the cache folder, mentioned by @jborza in #22
|
- Added voice preview caching system that stores generated previews in the cache folder, mentioned by @jborza in #22
|
||||||
- Added extra metadata support for chaptered M4B files, ensuring better compatibility with audiobook players.
|
- Added extra metadata support for chaptered M4B files, ensuring better compatibility with audiobook players.
|
||||||
- Added new option: `Separate chapters audio format`, allowing to choose between `wav`, `mp4`, `flac` and `opus` formats for chaptered audio files.
|
- Added new option: `Separate chapters audio format`, allowing to choose between `wav`, `mp4`, `flac` and `opus` formats for chaptered audio files.
|
||||||
|
- Added a download tracker that displays informative messages while downloading Kokoro models or voices from HuggingFace.
|
||||||
- Skipping PyTorch CUDA installation if GPU is not NVIDIA in WINDOWS_INSTALL.bat script, preventing unnecessary installation of PyTorch.
|
- Skipping PyTorch CUDA installation if GPU is not NVIDIA in WINDOWS_INSTALL.bat script, preventing unnecessary installation of PyTorch.
|
||||||
- Removed `abogen_` prefix that was adding to converted books in temp directory.
|
- Removed `abogen_` prefix that was adding to converted books in temp directory.
|
||||||
- Fixed voice preview player keeps playing silently at the background after preview ends.
|
- Fixed voice preview player keeps playing silently at the background after preview ends.
|
||||||
|
|||||||
+31
-2
@@ -12,6 +12,7 @@ import soundfile as sf
|
|||||||
from utils import clean_text, create_process
|
from utils import clean_text, create_process
|
||||||
from constants import PROGRAM_NAME, LANGUAGE_DESCRIPTIONS, SAMPLE_VOICE_TEXTS
|
from constants import PROGRAM_NAME, LANGUAGE_DESCRIPTIONS, SAMPLE_VOICE_TEXTS
|
||||||
from voice_formulas import get_new_voice
|
from voice_formulas import get_new_voice
|
||||||
|
import hf_tracker
|
||||||
import static_ffmpeg
|
import static_ffmpeg
|
||||||
import threading # for efficient waiting
|
import threading # for efficient waiting
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -161,6 +162,7 @@ class ConversionThread(QThread):
|
|||||||
f"\nVoice: {self.voice}\nLanguage: {self.lang_code}\nSpeed: {self.speed}\nGPU: {self.use_gpu}\nFile: {self.file_name}\nSubtitle mode: {self.subtitle_mode}\nOutput format: {self.output_format}\nSave option: {self.save_option}\n"
|
f"\nVoice: {self.voice}\nLanguage: {self.lang_code}\nSpeed: {self.speed}\nGPU: {self.use_gpu}\nFile: {self.file_name}\nSubtitle mode: {self.subtitle_mode}\nOutput format: {self.output_format}\nSave option: {self.save_option}\n"
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
|
hf_tracker.set_log_callback(lambda msg: self.log_updated.emit(msg))
|
||||||
# Show configuration
|
# Show configuration
|
||||||
self.log_updated.emit("Configuration:")
|
self.log_updated.emit("Configuration:")
|
||||||
# Use display_path for logs if available, otherwise use the actual file name
|
# Use display_path for logs if available, otherwise use the actual file name
|
||||||
@@ -1003,6 +1005,7 @@ class PlayAudioThread(QThread):
|
|||||||
def __init__(self, wav_path, parent=None):
|
def __init__(self, wav_path, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.wav_path = wav_path
|
self.wav_path = wav_path
|
||||||
|
self.is_canceled = False
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
@@ -1012,11 +1015,37 @@ class PlayAudioThread(QThread):
|
|||||||
pygame.mixer.init()
|
pygame.mixer.init()
|
||||||
pygame.mixer.music.load(self.wav_path)
|
pygame.mixer.music.load(self.wav_path)
|
||||||
pygame.mixer.music.play()
|
pygame.mixer.music.play()
|
||||||
# Wait until playback is finished
|
# Wait until playback is finished or canceled
|
||||||
while pygame.mixer.music.get_busy():
|
while pygame.mixer.music.get_busy() and not self.is_canceled:
|
||||||
_time.sleep(0.2)
|
_time.sleep(0.2)
|
||||||
|
|
||||||
|
# Make sure to clean up regardless of how we exited the loop
|
||||||
|
try:
|
||||||
|
pygame.mixer.music.stop()
|
||||||
pygame.mixer.music.unload()
|
pygame.mixer.music.unload()
|
||||||
pygame.mixer.quit() # Quit the mixer
|
pygame.mixer.quit() # Quit the mixer
|
||||||
|
except Exception:
|
||||||
|
# Ignore any errors during cleanup
|
||||||
|
pass
|
||||||
|
|
||||||
self.finished.emit()
|
self.finished.emit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
# Handle initialization errors separately to give better error messages
|
||||||
|
if "mixer not initialized" in str(e):
|
||||||
|
self.error.emit("Audio playback error: The audio system was not properly initialized")
|
||||||
|
else:
|
||||||
self.error.emit(f"Audio playback error: {str(e)}")
|
self.error.emit(f"Audio playback error: {str(e)}")
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""Safely stop playback"""
|
||||||
|
self.is_canceled = True
|
||||||
|
# Try to stop pygame if it's running, but catch all exceptions
|
||||||
|
try:
|
||||||
|
import pygame
|
||||||
|
if pygame.mixer.get_init():
|
||||||
|
if pygame.mixer.music.get_busy():
|
||||||
|
pygame.mixer.music.stop()
|
||||||
|
pygame.mixer.music.unload()
|
||||||
|
except Exception:
|
||||||
|
# Ignore all errors when stopping since mixer might not be initialized
|
||||||
|
pass
|
||||||
|
|||||||
+21
-8
@@ -4,6 +4,7 @@ import tempfile
|
|||||||
import platform
|
import platform
|
||||||
import base64
|
import base64
|
||||||
import re
|
import re
|
||||||
|
import hf_tracker
|
||||||
import hashlib # Added for cache path generation
|
import hashlib # Added for cache path generation
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QApplication,
|
QApplication,
|
||||||
@@ -80,6 +81,13 @@ if platform.system() == "Windows":
|
|||||||
import ctypes
|
import ctypes
|
||||||
|
|
||||||
|
|
||||||
|
class ShowWarningSignalEmitter(QObject): # New class to handle signal emission
|
||||||
|
show_warning_signal = pyqtSignal(str, str)
|
||||||
|
|
||||||
|
def emit(self, title, message):
|
||||||
|
self.show_warning_signal.emit(title, message)
|
||||||
|
|
||||||
|
|
||||||
class ThreadSafeLogSignal(QObject):
|
class ThreadSafeLogSignal(QObject):
|
||||||
log_signal = pyqtSignal(object)
|
log_signal = pyqtSignal(object)
|
||||||
|
|
||||||
@@ -533,6 +541,11 @@ class abogen(QWidget):
|
|||||||
self.log_signal = ThreadSafeLogSignal()
|
self.log_signal = ThreadSafeLogSignal()
|
||||||
self.log_signal.log_signal.connect(self._update_log_main_thread)
|
self.log_signal.log_signal.connect(self._update_log_main_thread)
|
||||||
|
|
||||||
|
# Create warning signal emitter
|
||||||
|
self.warning_signal_emitter = ShowWarningSignalEmitter()
|
||||||
|
self.warning_signal_emitter.show_warning_signal.connect(self.show_model_download_warning)
|
||||||
|
hf_tracker.set_show_warning_signal_emitter(self.warning_signal_emitter)
|
||||||
|
|
||||||
# Set application icon
|
# Set application icon
|
||||||
icon_path = get_resource_path("abogen.assets", "icon.ico")
|
icon_path = get_resource_path("abogen.assets", "icon.ico")
|
||||||
if icon_path:
|
if icon_path:
|
||||||
@@ -584,6 +597,9 @@ class abogen(QWidget):
|
|||||||
if self.check_updates:
|
if self.check_updates:
|
||||||
QTimer.singleShot(1000, self.check_for_updates_startup)
|
QTimer.singleShot(1000, self.check_for_updates_startup)
|
||||||
|
|
||||||
|
# Set hf_tracker callbacks
|
||||||
|
hf_tracker.set_log_callback(self.update_log)
|
||||||
|
|
||||||
def initUI(self):
|
def initUI(self):
|
||||||
self.setWindowTitle(f"{PROGRAM_NAME} v{VERSION}")
|
self.setWindowTitle(f"{PROGRAM_NAME} v{VERSION}")
|
||||||
screen = QApplication.primaryScreen().geometry()
|
screen = QApplication.primaryScreen().geometry()
|
||||||
@@ -1571,14 +1587,8 @@ class abogen(QWidget):
|
|||||||
if self.preview_playing:
|
if self.preview_playing:
|
||||||
try:
|
try:
|
||||||
if self.play_audio_thread and self.play_audio_thread.isRunning():
|
if self.play_audio_thread and self.play_audio_thread.isRunning():
|
||||||
# Attempt to stop pygame mixer if it's in use by PlayAudioThread
|
# Call the stop method on PlayAudioThread to safely handle stopping
|
||||||
# This is a bit indirect, ideally PlayAudioThread would have a stop method
|
self.play_audio_thread.stop()
|
||||||
import pygame
|
|
||||||
if pygame.mixer.get_init() and pygame.mixer.music.get_busy():
|
|
||||||
pygame.mixer.music.stop()
|
|
||||||
pygame.mixer.music.unload() # Ensure it's unloaded
|
|
||||||
pygame.mixer.quit() # Quit the mixer
|
|
||||||
self.play_audio_thread.quit() # Ask thread to finish
|
|
||||||
self.play_audio_thread.wait(500) # Wait a bit
|
self.play_audio_thread.wait(500) # Wait a bit
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error stopping preview audio: {e}")
|
print(f"Error stopping preview audio: {e}")
|
||||||
@@ -2428,3 +2438,6 @@ Categories=AudioVideo;Audio;Utility;
|
|||||||
self.config["separate_chapters_format"] = fmt
|
self.config["separate_chapters_format"] = fmt
|
||||||
save_config(self.config)
|
save_config(self.config)
|
||||||
|
|
||||||
|
def show_model_download_warning(self, title, message):
|
||||||
|
QMessageBox.information(self, title, message)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
log_callback = None
|
||||||
|
show_warning_signal_emitter = None # Renamed for clarity
|
||||||
|
|
||||||
|
def set_log_callback(cb):
|
||||||
|
global log_callback
|
||||||
|
log_callback = cb
|
||||||
|
|
||||||
|
def set_show_warning_signal_emitter(emitter): # Renamed for clarity
|
||||||
|
global show_warning_signal_emitter
|
||||||
|
show_warning_signal_emitter = emitter
|
||||||
|
|
||||||
|
from huggingface_hub import hf_hub_download
|
||||||
|
|
||||||
|
def tracked_hf_hub_download(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
local_kwargs = dict(kwargs)
|
||||||
|
local_kwargs["local_files_only"] = True
|
||||||
|
hf_hub_download(*args, **local_kwargs)
|
||||||
|
except Exception:
|
||||||
|
repo_id = kwargs.get("repo_id", "<unknown repo>")
|
||||||
|
filename = kwargs.get("filename", "<unknown file>")
|
||||||
|
if filename.endswith(".pth"):
|
||||||
|
msg = f"\nDownloading model '{filename}' from Hugging Face ({repo_id}). This may take a while. Please wait..."
|
||||||
|
if show_warning_signal_emitter: # Check if the emitter is set
|
||||||
|
show_warning_signal_emitter.emit("Downloading Model", f"Downloading model '{filename}' from Hugging Face repository '{repo_id}'. This may take a while, please wait.")
|
||||||
|
else:
|
||||||
|
msg = f"\nDownloading '{filename}' from Hugging Face ({repo_id}). Please wait..."
|
||||||
|
if log_callback:
|
||||||
|
print(msg, flush=True)
|
||||||
|
log_callback(msg)
|
||||||
|
else:
|
||||||
|
print(msg, flush=True)
|
||||||
|
return hf_hub_download(*args, **kwargs)
|
||||||
|
|
||||||
|
import huggingface_hub
|
||||||
|
huggingface_hub.hf_hub_download = tracked_hf_hub_download
|
||||||
Reference in New Issue
Block a user