Changed the temporary directory path to user's cache directory

This commit is contained in:
Deniz Şafak
2025-07-16 15:03:10 +03:00
parent 1f51e3edd3
commit 977b2a736c
5 changed files with 81 additions and 73 deletions
+4
View File
@@ -1,3 +1,7 @@
# 1.1.5 (pre-release)
- Changed the temporary directory path to user's cache directory, which is more appropriate for storing cache files and avoids issues with unintended cleanup.
- Fixed the isssue where extra metadata information was not being saved to M4B files when they have no chapters, ensuring that all metadata is correctly written to the output file.
# 1.1.4 # 1.1.4
- Fixed extra metadata information not being saved to M4B files, ensuring that all metadata is correctly written to the output file. - Fixed extra metadata information not being saved to M4B files, ensuring that all metadata is correctly written to the output file.
- Reformatted the code using Black for better readability and consistency. - Reformatted the code using Black for better readability and consistency.
+5 -5
View File
@@ -149,9 +149,9 @@ Heres Abogen in action: in this demo, it processes 3,000 characters of tex
| **Configure max lines in log window** | Configures the maximum number of lines to display in the log window. | | **Configure max lines in log window** | Configures the maximum number of lines to display in the log window. |
| **Separate chapters audio format** | Configures the audio format for separate chapters as `wav`, `flac`, `mp3`, or `opus`. | | **Separate chapters audio format** | Configures the audio format for separate chapters as `wav`, `flac`, `mp3`, or `opus`. |
| **Create desktop shortcut** | Creates a shortcut on your desktop for easy access. | | **Create desktop shortcut** | Creates a shortcut on your desktop for easy access. |
| **Open config.json directory** | Opens the directory where the configuration file is stored. | | **Open config directory** | Opens the directory where the configuration file is stored. |
| **Open temp directory** | Opens the temporary directory where converted text files are stored. | | **Open cache directory** | Opens the cache directory where converted text files are stored. |
| **Clear temporary files** | Deletes temporary files created during the conversion or preview. | | **Clear cache files** | Deletes cache files created during the conversion or preview. |
| **Check for updates at startup** | Automatically checks for updates when the program starts. | | **Check for updates at startup** | Automatically checks for updates when the program starts. |
| **Disable Kokoro's internet access** | Prevents Kokoro from downloading models or voices from HuggingFace Hub, useful for offline use. | | **Disable Kokoro's internet access** | Prevents Kokoro from downloading models or voices from HuggingFace Hub, useful for offline use. |
| **Reset to default settings** | Resets all settings to their default values. | | **Reset to default settings** | Resets all settings to their default values. |
@@ -174,7 +174,7 @@ Abogen will process each item in the queue automatically, saving outputs as conf
> Special thanks to [@jborza](https://github.com/jborza) for adding queue mode in PR [#35](https://github.com/denizsafak/abogen/pull/35) > Special thanks to [@jborza](https://github.com/jborza) for adding queue mode in PR [#35](https://github.com/denizsafak/abogen/pull/35)
## `About Chapter Markers` ## `About Chapter Markers`
When you process ePUB or PDF files, Abogen converts them into text files stored in your temporary directory. When you click "Edit," you're actually modifying these converted text files. In these text files, you'll notice tags that look like this: When you process ePUB or PDF files, Abogen converts them into text files stored in your cache directory. When you click "Edit," you're actually modifying these converted text files. In these text files, you'll notice tags that look like this:
``` ```
<<CHAPTER_MARKER:Chapter Title>> <<CHAPTER_MARKER:Chapter Title>>
@@ -270,7 +270,7 @@ Abogen launches automatically inside the container.
Known issues: Known issues:
- Audio preview is not working inside container (ALSA error). - Audio preview is not working inside container (ALSA error).
- `Open temp directory` and `Open configuration directory` options in settings not working. (Tried pcmanfm, did not work with Abogen). - `Open cache directory` and `Open configuration directory` options in settings not working. (Tried pcmanfm, did not work with Abogen).
(Special thanks to [@geo38](https://www.reddit.com/user/geo38/) from Reddit, who provided the Dockerfile and instructions in [this comment](https://www.reddit.com/r/selfhosted/comments/1k8x1yo/comment/mpe0bz8/).) (Special thanks to [@geo38](https://www.reddit.com/user/geo38/) from Reddit, who provided the Dockerfile and instructions in [this comment](https://www.reddit.com/r/selfhosted/comments/1k8x1yo/comment/mpe0bz8/).)
+2 -6
View File
@@ -1,6 +1,5 @@
import os import os
import re import re
import tempfile
import time import time
import chardet import chardet
import charset_normalizer import charset_normalizer
@@ -9,7 +8,7 @@ from platformdirs import user_desktop_dir
from PyQt5.QtCore import QThread, pyqtSignal, Qt, QTimer from PyQt5.QtCore import QThread, pyqtSignal, Qt, QTimer
from PyQt5.QtWidgets import QCheckBox, QVBoxLayout, QDialog, QLabel, QDialogButtonBox from PyQt5.QtWidgets import QCheckBox, QVBoxLayout, QDialog, QLabel, QDialogButtonBox
import soundfile as sf import soundfile as sf
from abogen.utils import clean_text, create_process from abogen.utils import clean_text, create_process, get_user_cache_path
from abogen.constants import ( from abogen.constants import (
PROGRAM_NAME, PROGRAM_NAME,
LANGUAGE_DESCRIPTIONS, LANGUAGE_DESCRIPTIONS,
@@ -1297,10 +1296,7 @@ class VoicePreviewThread(QThread):
self.use_gpu = use_gpu self.use_gpu = use_gpu
# Cache location for preview audio # Cache location for preview audio
self.cache_dir = os.path.join( self.cache_dir = get_user_cache_path("preview_cache")
tempfile.gettempdir(), PROGRAM_NAME, "preview_cache"
)
os.makedirs(self.cache_dir, exist_ok=True)
# Calculate cache path # Calculate cache path
self.cache_path = self._get_cache_path() self.cache_path = self._get_cache_path()
+58 -59
View File
@@ -62,6 +62,7 @@ from abogen.utils import (
prevent_sleep_end, prevent_sleep_end,
calculate_text_length, calculate_text_length,
get_resource_path, get_resource_path,
get_user_cache_path,
LoadPipelineThread, LoadPipelineThread,
) )
from abogen.conversion import ConversionThread, VoicePreviewThread, PlayAudioThread from abogen.conversion import ConversionThread, VoicePreviewThread, PlayAudioThread
@@ -564,12 +565,12 @@ class TextboxDialog(QDialog):
self.reject() self.reject()
else: else:
# Check if we need to warn about overwriting a non-temporary file # Check if we need to warn about overwriting a non-temporary file
if hasattr(self, "is_non_temp_file") and self.is_non_temp_file: if hasattr(self, "is_non_cache_file") and self.is_non_cache_file:
msg_box = QMessageBox(self) msg_box = QMessageBox(self)
msg_box.setIcon(QMessageBox.Warning) msg_box.setIcon(QMessageBox.Warning)
msg_box.setWindowTitle("File Overwrite Warning") msg_box.setWindowTitle("File Overwrite Warning")
msg_box.setText( msg_box.setText(
f"You are about to overwrite the original file:\n{self.non_temp_file_path}" f"You are about to overwrite the original file:\n{self.non_cache_file_path}"
) )
msg_box.setInformativeText("Do you want to continue?") msg_box.setInformativeText("Do you want to continue?")
msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
@@ -591,11 +592,11 @@ class TextboxDialog(QDialog):
# Get default filename from original file if editing # Get default filename from original file if editing
initial_path = "" initial_path = ""
if hasattr(self, "non_temp_file_path") and self.non_temp_file_path: if hasattr(self, "non_cache_file_path") and self.non_cache_file_path:
initial_path = self.non_temp_file_path initial_path = self.non_cache_file_path
# For EPUB and PDF files, use the displayed_file_path from the main window # For EPUB and PDF files, use the displayed_file_path from the main window
# This gives a better filename instead of the temporary file path # This gives a better filename instead of the cache file path
main_window = self.parent() main_window = self.parent()
if ( if (
hasattr(main_window, "displayed_file_path") hasattr(main_window, "displayed_file_path")
@@ -1195,7 +1196,7 @@ class abogen(QWidget):
if book_path.lower().endswith(".pdf"): if book_path.lower().endswith(".pdf"):
self.pdf_has_bookmarks = getattr(dialog, "has_pdf_bookmarks", False) self.pdf_has_bookmarks = getattr(dialog, "has_pdf_bookmarks", False)
# Use "abogen" prefix for temporary files # Use "abogen" prefix for cache files
# Extract base name without extension # Extract base name without extension
base_name = os.path.splitext(os.path.basename(book_path))[0] base_name = os.path.splitext(os.path.basename(book_path))[0]
@@ -1205,15 +1206,15 @@ class abogen(QWidget):
self, "Select Project Folder", "", QFileDialog.ShowDirsOnly self, "Select Project Folder", "", QFileDialog.ShowDirsOnly
) )
if not project_dir: if not project_dir:
# User cancelled, fallback to temp # User cancelled, fallback to cache
self.save_as_project = False self.save_as_project = False
temp_dir = os.path.join(tempfile.gettempdir(), PROGRAM_NAME) cache_dir = get_user_cache_path()
else: else:
# Create project folder structure # Create project folder structure
project_name = f"{base_name}_project" project_name = f"{base_name}_project"
project_dir = os.path.join(project_dir, project_name) project_dir = os.path.join(project_dir, project_name)
temp_dir = os.path.join(project_dir, "text") cache_dir = os.path.join(project_dir, "text")
os.makedirs(temp_dir, exist_ok=True) os.makedirs(cache_dir, exist_ok=True)
# Save metadata if available # Save metadata if available
meta_dir = os.path.join(project_dir, "metadata") meta_dir = os.path.join(project_dir, "metadata")
@@ -1268,11 +1269,10 @@ class abogen(QWidget):
with open(cover_path, "wb") as f: with open(cover_path, "wb") as f:
f.write(dialog.book_metadata["cover_image"]) f.write(dialog.book_metadata["cover_image"])
else: else:
temp_dir = os.path.join(tempfile.gettempdir(), PROGRAM_NAME) cache_dir = get_user_cache_path()
os.makedirs(temp_dir, exist_ok=True)
fd, tmp = tempfile.mkstemp( fd, tmp = tempfile.mkstemp(
prefix=f"{base_name}_", suffix=".txt", dir=temp_dir prefix=f"{base_name}_", suffix=".txt", dir=cache_dir
) )
os.close(fd) os.close(fd)
with open(tmp, "w", encoding="utf-8") as f: with open(tmp, "w", encoding="utf-8") as f:
@@ -1293,13 +1293,13 @@ class abogen(QWidget):
return return
editing = False editing = False
is_temp_file = False is_cache_file = False
# If path is explicitly provided, use it # If path is explicitly provided, use it
if file_path and os.path.exists(file_path): if file_path and os.path.exists(file_path):
editing = True editing = True
edit_file = file_path edit_file = file_path
# Check if this is a temporary file # Check if this is a cache file
is_temp_file = tempfile.gettempdir() in file_path is_cache_file = get_user_cache_path() in file_path
# Otherwise use selected_file if it's a txt file # Otherwise use selected_file if it's a txt file
elif ( elif (
self.selected_file_type == "txt" self.selected_file_type == "txt"
@@ -1308,8 +1308,8 @@ class abogen(QWidget):
): ):
editing = True editing = True
edit_file = self.selected_file edit_file = self.selected_file
# Check if this is a temporary file # Check if this is a cache file
is_temp_file = tempfile.gettempdir() in self.selected_file is_cache_file = get_user_cache_path() in self.selected_file
dialog = TextboxDialog(self) dialog = TextboxDialog(self)
if editing: if editing:
@@ -1321,10 +1321,10 @@ class abogen(QWidget):
dialog.text_edit.toPlainText() dialog.text_edit.toPlainText()
) # Store original text ) # Store original text
# If editing a non-temporary file, alert the user # If editing a non-cache file, alert the user
if not is_temp_file: if not is_cache_file:
dialog.is_non_temp_file = True dialog.is_non_cache_file = True
dialog.non_temp_file_path = edit_file dialog.non_cache_file_path = edit_file
except Exception: except Exception:
pass pass
if dialog.exec_() == QDialog.Accepted: if dialog.exec_() == QDialog.Accepted:
@@ -1342,10 +1342,9 @@ class abogen(QWidget):
# Hide chapters button since we're using custom text now # Hide chapters button since we're using custom text now
self.input_box.chapters_btn.hide() self.input_box.chapters_btn.hide()
else: else:
temp_dir = os.path.join(tempfile.gettempdir(), PROGRAM_NAME) cache_dir = get_user_cache_path()
os.makedirs(temp_dir, exist_ok=True)
fd, tmp = tempfile.mkstemp( fd, tmp = tempfile.mkstemp(
prefix="abogen_", suffix=".txt", dir=temp_dir prefix="abogen_", suffix=".txt", dir=cache_dir
) )
os.close(fd) os.close(fd)
with open(tmp, "w", encoding="utf-8") as f: with open(tmp, "w", encoding="utf-8") as f:
@@ -2153,7 +2152,7 @@ class abogen(QWidget):
if not lang_to_cache or not voice_to_cache: # Not enough info if not lang_to_cache or not voice_to_cache: # Not enough info
return None return None
cache_dir = os.path.join(tempfile.gettempdir(), PROGRAM_NAME, "preview_cache") cache_dir = get_user_cache_path("preview_cache")
if "*" in voice_to_cache: # Voice formula if "*" in voice_to_cache: # Voice formula
voice_id = ( voice_id = (
@@ -2771,17 +2770,17 @@ class abogen(QWidget):
reveal_config_action.triggered.connect(self.reveal_config_in_explorer) reveal_config_action.triggered.connect(self.reveal_config_in_explorer)
menu.addAction(reveal_config_action) menu.addAction(reveal_config_action)
# Add open temp directory option # Add open cache directory option
open_temp_action = QAction("Open temp directory", self) open_cache_action = QAction("Open cache directory", self)
open_temp_action.triggered.connect(self.open_temp_directory) open_cache_action.triggered.connect(self.open_cache_directory)
menu.addAction(open_temp_action) menu.addAction(open_cache_action)
# Add clear temporary files option # Add clear cache files option
clear_temp_action = QAction("Clear temporary files", self) clear_cache_action = QAction("Clear cache files", self)
clear_temp_action.triggered.connect(self.clear_temp_files) clear_cache_action.triggered.connect(self.clear_cache_files)
menu.addAction(clear_temp_action) menu.addAction(clear_cache_action)
# Add seperator" # Add separator
menu.addSeparator() menu.addSeparator()
# Add "Disable Kokoro's internet access" option # Add "Disable Kokoro's internet access" option
@@ -2900,21 +2899,21 @@ class abogen(QWidget):
self, "Config Error", f"Could not open config location:\n{e}" self, "Config Error", f"Could not open config location:\n{e}"
) )
def open_temp_directory(self): def open_cache_directory(self):
"""Open the temporary directory used by the program.""" """Open the cache directory used by the program."""
try: try:
# Get the temp directory path # Get the cache directory path
temp_dir = os.path.join(tempfile.gettempdir(), PROGRAM_NAME) cache_dir = get_user_cache_path()
# Create the directory if it doesn't exist # Create the directory if it doesn't exist
if not os.path.exists(temp_dir): if not os.path.exists(cache_dir):
os.makedirs(temp_dir) os.makedirs(cache_dir)
# Open the directory in file explorer # Open the directory in file explorer
QDesktopServices.openUrl(QUrl.fromLocalFile(temp_dir)) QDesktopServices.openUrl(QUrl.fromLocalFile(cache_dir))
except Exception as e: except Exception as e:
QMessageBox.critical( QMessageBox.critical(
self, "Temp Directory Error", f"Could not open temp directory:\n{e}" self, "Cache Directory Error", f"Could not open cache directory:\n{e}"
) )
def add_shortcut_to_desktop(self): def add_shortcut_to_desktop(self):
@@ -3254,23 +3253,23 @@ Categories=AudioVideo;Audio;Utility;
) )
pass pass
def clear_temp_files(self): def clear_cache_files(self):
"""Clear temporary files created by the program.""" """Clear cache files created by the program."""
import glob import glob
try: try:
# Get the abogen temp directory # Get the abogen cache directory
temp_dir = os.path.join(tempfile.gettempdir(), PROGRAM_NAME) cache_dir = get_user_cache_path()
# Find all .txt files in the abogen temp directory # Find all .txt files in the abogen cache directory
pattern = os.path.join(temp_dir, "*.txt") pattern = os.path.join(cache_dir, "*.txt")
temp_files = glob.glob(pattern) cache_files = glob.glob(pattern)
# Count the files # Count the files
file_count = len(temp_files) file_count = len(cache_files)
# Check for preview cache files # Check for preview cache files
preview_cache_dir = os.path.join(temp_dir, "preview_cache") preview_cache_dir = os.path.join(cache_dir, "preview_cache")
preview_files = [] preview_files = []
if os.path.exists(preview_cache_dir): if os.path.exists(preview_cache_dir):
preview_pattern = os.path.join(preview_cache_dir, "*.wav") preview_pattern = os.path.join(preview_cache_dir, "*.wav")
@@ -3280,16 +3279,16 @@ Categories=AudioVideo;Audio;Utility;
if file_count == 0 and preview_count == 0: if file_count == 0 and preview_count == 0:
QMessageBox.information( QMessageBox.information(
self, "No Temporary Files", "No temporary files were found." self, "No Cache Files", "No cache files were found."
) )
return return
# Create a custom message box with checkbox # Create a custom message box with checkbox
msg_box = QMessageBox(self) msg_box = QMessageBox(self)
msg_box.setIcon(QMessageBox.Question) msg_box.setIcon(QMessageBox.Question)
msg_box.setWindowTitle("Clear Temporary Files") msg_box.setWindowTitle("Clear Cache Files")
msg_text = f"Found {file_count} temporary file{'s' if file_count != 1 else ''} in the {PROGRAM_NAME} temp folder." msg_text = f"Found {file_count} cache file{'s' if file_count != 1 else ''} in the {PROGRAM_NAME} cache folder."
if preview_count > 0: if preview_count > 0:
msg_text += f"\nAlso found {preview_count} preview cache file{'s' if preview_count != 1 else ''}." msg_text += f"\nAlso found {preview_count} preview cache file{'s' if preview_count != 1 else ''}."
@@ -3313,7 +3312,7 @@ Categories=AudioVideo;Audio;Utility;
# Delete the text files # Delete the text files
deleted_count = 0 deleted_count = 0
for file_path in temp_files: for file_path in cache_files:
try: try:
os.remove(file_path) os.remove(file_path)
deleted_count += 1 deleted_count += 1
@@ -3336,12 +3335,12 @@ Categories=AudioVideo;Audio;Utility;
result_msg += f"\nAlso deleted {deleted_preview_count} preview cache file{'s' if deleted_preview_count != 1 else ''}." result_msg += f"\nAlso deleted {deleted_preview_count} preview cache file{'s' if deleted_preview_count != 1 else ''}."
# Show results # Show results
QMessageBox.information(self, "Temporary Files Cleared", result_msg) QMessageBox.information(self, "Cache Files Cleared", result_msg)
# If currently selected file is in the temp directory, clear the UI # If currently selected file is in the cache directory, clear the UI
if ( if (
self.selected_file self.selected_file
and os.path.dirname(self.selected_file) == temp_dir and os.path.dirname(self.selected_file) == cache_dir
and self.selected_file.endswith(".txt") and self.selected_file.endswith(".txt")
): ):
self.input_box.clear_input() self.input_box.clear_input()
+12 -3
View File
@@ -74,14 +74,23 @@ def get_user_config_path():
if os.path.exists(custom_dir): if os.path.exists(custom_dir):
config_dir = custom_dir config_dir = custom_dir
else: else:
config_dir = user_config_dir("abogen", appauthor=False, roaming=True) config_dir = user_config_dir("abogen", appauthor=False, roaming=True, ensure_exists=True)
else: else:
# Windows and fallback case # Windows and fallback case
config_dir = user_config_dir("abogen", appauthor=False, roaming=True) config_dir = user_config_dir("abogen", appauthor=False, roaming=True, ensure_exists=True)
os.makedirs(config_dir, exist_ok=True)
return os.path.join(config_dir, "config.json") return os.path.join(config_dir, "config.json")
# Define cache path
def get_user_cache_path(folder=None):
from platformdirs import user_cache_dir
cache_dir = user_cache_dir("abogen", appauthor=False, opinion=True, ensure_exists=True)
if folder:
cache_dir = os.path.join(cache_dir, folder)
# Ensure the directory exists
os.makedirs(cache_dir, exist_ok=True)
return cache_dir
_sleep_procs = {"Darwin": None, "Linux": None} # Store sleep prevention processes _sleep_procs = {"Darwin": None, "Linux": None} # Store sleep prevention processes