mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
feat: Enhance file upload experience with drag-and-drop support and improved UI for upload dropzone
This commit is contained in:
+55
-19
@@ -8,6 +8,7 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
|
||||||
@@ -29,12 +30,22 @@ warnings.filterwarnings("ignore")
|
|||||||
|
|
||||||
|
|
||||||
def detect_encoding(file_path):
|
def detect_encoding(file_path):
|
||||||
import chardet
|
try:
|
||||||
import charset_normalizer
|
import chardet # type: ignore[import-not-found]
|
||||||
|
except ImportError: # pragma: no cover - optional dependency
|
||||||
|
chardet = None # type: ignore[assignment]
|
||||||
|
|
||||||
|
try:
|
||||||
|
import charset_normalizer # type: ignore[import-not-found]
|
||||||
|
except ImportError: # pragma: no cover - optional dependency
|
||||||
|
charset_normalizer = None # type: ignore[assignment]
|
||||||
|
|
||||||
with open(file_path, "rb") as f:
|
with open(file_path, "rb") as f:
|
||||||
raw_data = f.read()
|
raw_data = f.read()
|
||||||
detected_encoding = None
|
detected_encoding = None
|
||||||
for detectors in (charset_normalizer, chardet):
|
for detectors in (charset_normalizer, chardet):
|
||||||
|
if detectors is None:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
result = detectors.detect(raw_data)["encoding"]
|
result = detectors.detect(raw_data)["encoding"]
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -92,7 +103,10 @@ def get_resource_path(package, resource):
|
|||||||
def get_version():
|
def get_version():
|
||||||
"""Return the current version of the application."""
|
"""Return the current version of the application."""
|
||||||
try:
|
try:
|
||||||
with open(get_resource_path("/", "VERSION"), "r") as f:
|
version_path = get_resource_path("/", "VERSION")
|
||||||
|
if not version_path:
|
||||||
|
raise FileNotFoundError("VERSION resource missing")
|
||||||
|
with open(version_path, "r") as f:
|
||||||
return f.read().strip()
|
return f.read().strip()
|
||||||
except Exception:
|
except Exception:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
@@ -158,7 +172,14 @@ def get_user_cache_root():
|
|||||||
if last_error is not None:
|
if last_error is not None:
|
||||||
raise last_error
|
raise last_error
|
||||||
|
|
||||||
def _configure_cache_env():
|
def _configure_cache_env(root: Optional[str]) -> None:
|
||||||
|
temp_root = None
|
||||||
|
if root:
|
||||||
|
try:
|
||||||
|
temp_root = ensure_directory(root)
|
||||||
|
except OSError:
|
||||||
|
temp_root = None
|
||||||
|
|
||||||
home_dir = os.environ.get("HOME")
|
home_dir = os.environ.get("HOME")
|
||||||
if not home_dir:
|
if not home_dir:
|
||||||
home_dir = ensure_directory(os.path.join("/tmp", "abogen-home"))
|
home_dir = ensure_directory(os.path.join("/tmp", "abogen-home"))
|
||||||
@@ -169,6 +190,9 @@ def get_user_cache_root():
|
|||||||
cache_base = os.environ.get("XDG_CACHE_HOME")
|
cache_base = os.environ.get("XDG_CACHE_HOME")
|
||||||
if cache_base:
|
if cache_base:
|
||||||
cache_base = ensure_directory(cache_base)
|
cache_base = ensure_directory(cache_base)
|
||||||
|
elif temp_root:
|
||||||
|
cache_base = temp_root
|
||||||
|
os.environ["XDG_CACHE_HOME"] = cache_base
|
||||||
else:
|
else:
|
||||||
cache_base = ensure_directory(os.path.join(home_dir, ".cache"))
|
cache_base = ensure_directory(os.path.join(home_dir, ".cache"))
|
||||||
os.environ["XDG_CACHE_HOME"] = cache_base
|
os.environ["XDG_CACHE_HOME"] = cache_base
|
||||||
@@ -176,6 +200,9 @@ def get_user_cache_root():
|
|||||||
hf_cache = os.environ.get("HF_HOME")
|
hf_cache = os.environ.get("HF_HOME")
|
||||||
if hf_cache:
|
if hf_cache:
|
||||||
hf_cache = ensure_directory(hf_cache)
|
hf_cache = ensure_directory(hf_cache)
|
||||||
|
elif temp_root:
|
||||||
|
hf_cache = ensure_directory(os.path.join(temp_root, "huggingface"))
|
||||||
|
os.environ["HF_HOME"] = hf_cache
|
||||||
else:
|
else:
|
||||||
hf_cache = ensure_directory(os.path.join(cache_base, "huggingface"))
|
hf_cache = ensure_directory(os.path.join(cache_base, "huggingface"))
|
||||||
os.environ["HF_HOME"] = hf_cache
|
os.environ["HF_HOME"] = hf_cache
|
||||||
@@ -185,7 +212,7 @@ def get_user_cache_root():
|
|||||||
|
|
||||||
os.environ.setdefault("ABOGEN_INTERNAL_CACHE_ROOT", cache_base)
|
os.environ.setdefault("ABOGEN_INTERNAL_CACHE_ROOT", cache_base)
|
||||||
|
|
||||||
cache_root = None
|
cache_root: Optional[str] = None
|
||||||
|
|
||||||
override = os.environ.get("ABOGEN_TEMP_DIR")
|
override = os.environ.get("ABOGEN_TEMP_DIR")
|
||||||
if override:
|
if override:
|
||||||
@@ -215,7 +242,10 @@ def get_user_cache_root():
|
|||||||
logger.warning("Falling back to temp cache directory %s", tmp_candidate)
|
logger.warning("Falling back to temp cache directory %s", tmp_candidate)
|
||||||
cache_root = ensure_directory(tmp_candidate)
|
cache_root = ensure_directory(tmp_candidate)
|
||||||
|
|
||||||
_configure_cache_env()
|
if cache_root is None:
|
||||||
|
raise RuntimeError("Unable to determine cache directory")
|
||||||
|
|
||||||
|
_configure_cache_env(cache_root)
|
||||||
return cache_root
|
return cache_root
|
||||||
|
|
||||||
|
|
||||||
@@ -260,7 +290,7 @@ def get_user_output_path(folder=None):
|
|||||||
return base
|
return base
|
||||||
|
|
||||||
|
|
||||||
_sleep_procs = {"Darwin": None, "Linux": None} # Store sleep prevention processes
|
_sleep_procs: Dict[str, Optional[subprocess.Popen[str]]] = {"Darwin": None, "Linux": None} # Store sleep prevention processes
|
||||||
|
|
||||||
|
|
||||||
def clean_text(text, *args, **kwargs):
|
def clean_text(text, *args, **kwargs):
|
||||||
@@ -319,11 +349,14 @@ def create_process(cmd, stdin=None, text=True, capture_output=False):
|
|||||||
kwargs["stdin"] = stdin
|
kwargs["stdin"] = stdin
|
||||||
|
|
||||||
if platform.system() == "Windows":
|
if platform.system() == "Windows":
|
||||||
startupinfo = subprocess.STARTUPINFO()
|
startupinfo = subprocess.STARTUPINFO() # type: ignore[attr-defined]
|
||||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW # type: ignore[attr-defined]
|
||||||
startupinfo.wShowWindow = subprocess.SW_HIDE
|
startupinfo.wShowWindow = subprocess.SW_HIDE # type: ignore[attr-defined]
|
||||||
kwargs.update(
|
kwargs.update(
|
||||||
{"startupinfo": startupinfo, "creationflags": subprocess.CREATE_NO_WINDOW}
|
{
|
||||||
|
"startupinfo": startupinfo,
|
||||||
|
"creationflags": subprocess.CREATE_NO_WINDOW, # type: ignore[attr-defined]
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Print the command being executed
|
# Print the command being executed
|
||||||
@@ -398,8 +431,8 @@ def calculate_text_length(text):
|
|||||||
|
|
||||||
def get_gpu_acceleration(enabled):
|
def get_gpu_acceleration(enabled):
|
||||||
try:
|
try:
|
||||||
import torch
|
import torch # type: ignore[import-not-found]
|
||||||
from torch.cuda import is_available as cuda_available
|
from torch.cuda import is_available as cuda_available # type: ignore[import-not-found]
|
||||||
|
|
||||||
if not enabled:
|
if not enabled:
|
||||||
return "GPU available but using CPU.", False
|
return "GPU available but using CPU.", False
|
||||||
@@ -437,7 +470,7 @@ def prevent_sleep_start():
|
|||||||
if system == "Windows":
|
if system == "Windows":
|
||||||
import ctypes
|
import ctypes
|
||||||
|
|
||||||
ctypes.windll.kernel32.SetThreadExecutionState(
|
ctypes.windll.kernel32.SetThreadExecutionState( # type: ignore[attr-defined]
|
||||||
0x80000000 | 0x00000001 | 0x00000040
|
0x80000000 | 0x00000001 | 0x00000040
|
||||||
)
|
)
|
||||||
elif system == "Darwin":
|
elif system == "Darwin":
|
||||||
@@ -471,18 +504,21 @@ def prevent_sleep_end():
|
|||||||
if system == "Windows":
|
if system == "Windows":
|
||||||
import ctypes
|
import ctypes
|
||||||
|
|
||||||
ctypes.windll.kernel32.SetThreadExecutionState(0x80000000) # ES_CONTINUOUS
|
ctypes.windll.kernel32.SetThreadExecutionState(0x80000000) # type: ignore[attr-defined]
|
||||||
elif system in ("Darwin", "Linux") and _sleep_procs[system]:
|
elif system in ("Darwin", "Linux"):
|
||||||
|
proc = _sleep_procs.get(system)
|
||||||
|
if proc:
|
||||||
try:
|
try:
|
||||||
_sleep_procs[system].terminate()
|
proc.terminate()
|
||||||
_sleep_procs[system] = None
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
finally:
|
||||||
|
_sleep_procs[system] = None
|
||||||
|
|
||||||
|
|
||||||
def load_numpy_kpipeline():
|
def load_numpy_kpipeline():
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from kokoro import KPipeline
|
from kokoro import KPipeline # type: ignore[import-not-found]
|
||||||
|
|
||||||
return np, KPipeline
|
return np, KPipeline
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ const initDashboard = () => {
|
|||||||
const readerTitle = readerModal?.querySelector('#reader-modal-title') || null;
|
const readerTitle = readerModal?.querySelector('#reader-modal-title') || null;
|
||||||
const defaultReaderHint = readerHint?.textContent || "";
|
const defaultReaderHint = readerHint?.textContent || "";
|
||||||
const scope = uploadModal || document;
|
const scope = uploadModal || document;
|
||||||
|
const sourceFileInput = scope.querySelector('#source_file');
|
||||||
|
const dropzone = document.querySelector('[data-role="upload-dropzone"]');
|
||||||
|
const dropzoneFilename = document.querySelector('[data-role="upload-dropzone-filename"]');
|
||||||
|
|
||||||
const parseJSONScript = (id) => {
|
const parseJSONScript = (id) => {
|
||||||
const element = document.getElementById(id);
|
const element = document.getElementById(id);
|
||||||
@@ -32,6 +35,71 @@ const initDashboard = () => {
|
|||||||
const previewAudio = scope.querySelector('[data-role="voice-preview-audio"]');
|
const previewAudio = scope.querySelector('[data-role="voice-preview-audio"]');
|
||||||
const sampleVoiceTexts = parseJSONScript('voice-sample-texts') || {};
|
const sampleVoiceTexts = parseJSONScript('voice-sample-texts') || {};
|
||||||
|
|
||||||
|
const setDropzoneStatus = (message, state = "") => {
|
||||||
|
if (!dropzoneFilename) return;
|
||||||
|
if (!message) {
|
||||||
|
dropzoneFilename.hidden = true;
|
||||||
|
dropzoneFilename.textContent = "";
|
||||||
|
dropzoneFilename.removeAttribute("data-state");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dropzoneFilename.hidden = false;
|
||||||
|
dropzoneFilename.textContent = message;
|
||||||
|
if (state) {
|
||||||
|
dropzoneFilename.dataset.state = state;
|
||||||
|
} else {
|
||||||
|
dropzoneFilename.removeAttribute("data-state");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateDropzoneFilename = () => {
|
||||||
|
if (!sourceFileInput) {
|
||||||
|
setDropzoneStatus("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const file = sourceFileInput.files && sourceFileInput.files[0];
|
||||||
|
if (file) {
|
||||||
|
setDropzoneStatus(`Selected: ${file.name}`);
|
||||||
|
} else {
|
||||||
|
setDropzoneStatus("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const assignDroppedFile = (file) => {
|
||||||
|
if (!sourceFileInput || !file) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (typeof DataTransfer === "undefined") {
|
||||||
|
throw new Error("DataTransfer API unavailable");
|
||||||
|
}
|
||||||
|
const transfer = new DataTransfer();
|
||||||
|
transfer.items.add(file);
|
||||||
|
sourceFileInput.files = transfer.files;
|
||||||
|
sourceFileInput.dispatchEvent(new Event("change", { bubbles: true }));
|
||||||
|
try {
|
||||||
|
sourceFileInput.focus({ preventScroll: true });
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore focus errors
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("Unable to assign dropped file to input", error);
|
||||||
|
setDropzoneStatus("Drag & drop isn't supported here. Click to choose a file instead.", "error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setDropzoneActive = (isActive) => {
|
||||||
|
if (!dropzone) return;
|
||||||
|
dropzone.classList.toggle("is-dragging", isActive);
|
||||||
|
if (isActive) {
|
||||||
|
dropzone.dataset.state = "drag";
|
||||||
|
} else {
|
||||||
|
delete dropzone.dataset.state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let lastTrigger = null;
|
let lastTrigger = null;
|
||||||
let readerTrigger = null;
|
let readerTrigger = null;
|
||||||
let previewAbortController = null;
|
let previewAbortController = null;
|
||||||
@@ -151,6 +219,13 @@ const initDashboard = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (sourceFileInput) {
|
||||||
|
sourceFileInput.addEventListener("change", updateDropzoneFilename);
|
||||||
|
updateDropzoneFilename();
|
||||||
|
} else {
|
||||||
|
setDropzoneStatus("");
|
||||||
|
}
|
||||||
|
|
||||||
const resolveSampleText = (language) => {
|
const resolveSampleText = (language) => {
|
||||||
const fallback = typeof sampleVoiceTexts === "object" && sampleVoiceTexts?.a
|
const fallback = typeof sampleVoiceTexts === "object" && sampleVoiceTexts?.a
|
||||||
? sampleVoiceTexts.a
|
? sampleVoiceTexts.a
|
||||||
@@ -352,6 +427,68 @@ const initDashboard = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dropzone) {
|
||||||
|
let dragDepth = 0;
|
||||||
|
|
||||||
|
dropzone.addEventListener("dragenter", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
dragDepth += 1;
|
||||||
|
setDropzoneActive(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
dropzone.addEventListener("dragover", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (event.dataTransfer) {
|
||||||
|
event.dataTransfer.dropEffect = "copy";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleDragLeave = (event) => {
|
||||||
|
if (event && dropzone.contains(event.relatedTarget)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dragDepth = Math.max(0, dragDepth - 1);
|
||||||
|
if (dragDepth === 0) {
|
||||||
|
setDropzoneActive(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
dropzone.addEventListener("dragleave", (event) => {
|
||||||
|
handleDragLeave(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
dropzone.addEventListener("dragend", () => {
|
||||||
|
dragDepth = 0;
|
||||||
|
setDropzoneActive(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
dropzone.addEventListener("drop", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
dragDepth = 0;
|
||||||
|
setDropzoneActive(false);
|
||||||
|
const files = event.dataTransfer && event.dataTransfer.files;
|
||||||
|
if (!files || !files.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openUploadModal(dropzone);
|
||||||
|
assignDroppedFile(files[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
dropzone.addEventListener("click", (event) => {
|
||||||
|
if (event.target.closest('[data-role="open-upload-modal"]')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openUploadModal(dropzone);
|
||||||
|
});
|
||||||
|
|
||||||
|
dropzone.addEventListener("keydown", (event) => {
|
||||||
|
if (event.key === "Enter" || event.key === " ") {
|
||||||
|
event.preventDefault();
|
||||||
|
openUploadModal(dropzone);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[voiceSelect, profileSelect, formulaInput, languageSelect, speedInput].forEach((input) => {
|
[voiceSelect, profileSelect, formulaInput, languageSelect, speedInput].forEach((input) => {
|
||||||
if (!input) return;
|
if (!input) return;
|
||||||
const eventName = input === formulaInput ? "input" : "change";
|
const eventName = input === formulaInput ? "input" : "change";
|
||||||
|
|||||||
@@ -130,12 +130,90 @@ body {
|
|||||||
gap: 1.25rem;
|
gap: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.upload-card__dropzone {
|
||||||
|
border: 1.5px dashed rgba(148, 163, 184, 0.3);
|
||||||
|
border-radius: 24px;
|
||||||
|
background: rgba(15, 23, 42, 0.28);
|
||||||
|
padding: 2.75rem 2rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-card__dropzone:hover,
|
||||||
|
.upload-card__dropzone:focus-visible {
|
||||||
|
border-color: rgba(56, 189, 248, 0.6);
|
||||||
|
box-shadow: 0 0 0 4px rgba(56, 189, 248, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-card__dropzone.is-dragging {
|
||||||
|
border-color: rgba(56, 189, 248, 0.85);
|
||||||
|
background: rgba(15, 23, 42, 0.45);
|
||||||
|
box-shadow: 0 0 0 4px rgba(56, 189, 248, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-card__dropzone-content {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.75rem;
|
||||||
|
justify-items: center;
|
||||||
|
max-width: 420px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-card__icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0.55rem 0.7rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(56, 189, 248, 0.12);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-card__headline {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-card__hint {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-card__filename {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-card__filename[data-state="error"] {
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-card__dropzone .button {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.card__actions {
|
.card__actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card__actions .button {
|
||||||
|
padding: 0.7rem 1.1rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 8px 22px rgba(56, 189, 248, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card__actions .button:hover {
|
||||||
|
box-shadow: 0 12px 26px rgba(14, 165, 233, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
.card--modal {
|
.card--modal {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-radius: 28px;
|
border-radius: 28px;
|
||||||
@@ -213,6 +291,7 @@ body {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal__footer {
|
.modal__footer {
|
||||||
|
|||||||
@@ -4,25 +4,16 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="card card--workflow">
|
<section class="card card--workflow">
|
||||||
<div class="step-indicator" aria-label="Audiobook workflow">
|
|
||||||
<span class="step-indicator__item is-active">
|
|
||||||
<span class="step-indicator__index">1</span>
|
|
||||||
<span class="step-indicator__label">Upload & settings</span>
|
|
||||||
</span>
|
|
||||||
<span class="step-indicator__item">
|
|
||||||
<span class="step-indicator__index">2</span>
|
|
||||||
<span class="step-indicator__label">Chapters</span>
|
|
||||||
</span>
|
|
||||||
<span class="step-indicator__item">
|
|
||||||
<span class="step-indicator__index">3</span>
|
|
||||||
<span class="step-indicator__label">Speakers</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<h1 class="card__title">Create a New Audiobook</h1>
|
<h1 class="card__title">Create a New Audiobook</h1>
|
||||||
<p class="card__subtitle">Kick off a fresh conversion with your manuscript or pasted text. You can fine-tune chapters and speakers in the next steps.</p>
|
<p class="card__subtitle">Kick off a fresh conversion with your manuscript or pasted text. You can fine-tune chapters and speakers in the next steps.</p>
|
||||||
<div class="card__actions">
|
<div class="upload-card__dropzone" data-role="upload-dropzone" tabindex="0" role="button" aria-label="Open upload settings or drop a manuscript file">
|
||||||
|
<div class="upload-card__dropzone-content">
|
||||||
|
<span class="upload-card__icon" aria-hidden="true">↑</span>
|
||||||
|
<p class="upload-card__headline">Drop your manuscript to begin</p>
|
||||||
|
<p class="upload-card__hint">Drag & drop a supported file here, or click to choose one in the next step.</p>
|
||||||
|
<p class="upload-card__filename" data-role="upload-dropzone-filename" hidden></p>
|
||||||
<button type="button" class="button" data-role="open-upload-modal">Open upload & settings</button>
|
<button type="button" class="button" data-role="open-upload-modal">Open upload & settings</button>
|
||||||
<a class="button button--ghost" href="{{ url_for('web.queue_page') }}">View queue</a>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
sys.path.insert(0, str(ROOT))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def clear_utils_cache():
|
||||||
|
import abogen.utils as utils
|
||||||
|
|
||||||
|
getattr(utils.get_user_cache_root, "cache_clear")()
|
||||||
|
yield
|
||||||
|
getattr(utils.get_user_cache_root, "cache_clear")()
|
||||||
|
|
||||||
|
|
||||||
|
def _clear_env(monkeypatch: pytest.MonkeyPatch, keys: Iterable[str]) -> None:
|
||||||
|
for key in keys:
|
||||||
|
monkeypatch.delenv(key, raising=False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_abogen_temp_dir_configures_hf_cache(monkeypatch, tmp_path):
|
||||||
|
import abogen.utils as utils
|
||||||
|
|
||||||
|
cache_root = tmp_path / "cache-root"
|
||||||
|
home_dir = tmp_path / "home"
|
||||||
|
|
||||||
|
monkeypatch.setenv("ABOGEN_TEMP_DIR", str(cache_root))
|
||||||
|
monkeypatch.setenv("HOME", str(home_dir))
|
||||||
|
_clear_env(
|
||||||
|
monkeypatch,
|
||||||
|
(
|
||||||
|
"XDG_CACHE_HOME",
|
||||||
|
"HF_HOME",
|
||||||
|
"HUGGINGFACE_HUB_CACHE",
|
||||||
|
"TRANSFORMERS_CACHE",
|
||||||
|
"ABOGEN_INTERNAL_CACHE_ROOT",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
root = utils.get_user_cache_root()
|
||||||
|
|
||||||
|
expected_root = os.path.abspath(str(cache_root))
|
||||||
|
expected_hf = os.path.join(expected_root, "huggingface")
|
||||||
|
|
||||||
|
assert root == expected_root
|
||||||
|
assert os.environ["XDG_CACHE_HOME"] == expected_root
|
||||||
|
assert os.environ["HF_HOME"] == expected_hf
|
||||||
|
assert os.environ["HUGGINGFACE_HUB_CACHE"] == expected_hf
|
||||||
|
assert os.environ["TRANSFORMERS_CACHE"] == expected_hf
|
||||||
Reference in New Issue
Block a user