Compare commits
21 Commits
testing_up
...
main
| Author | SHA1 | Date |
|---|---|---|
|
|
d255cb1314 | |
|
|
4371980b85 | |
|
|
e5a6544a2d | |
|
|
09c5cf0960 | |
|
|
095e206170 | |
|
|
cf1760daf3 | |
|
|
eb18460da2 | |
|
|
2e3fe33c29 | |
|
|
6bbdefa72c | |
|
|
cb1911c91c | |
|
|
048f2e25b8 | |
|
|
abdcfbbbf4 | |
|
|
0d0ef01e6e | |
|
|
5bc7997589 | |
|
|
21d9cc3905 | |
|
|
ac2427bf34 | |
|
|
f3a1f73282 | |
|
|
0df9e4717f | |
|
|
1c56b0aa5b | |
|
|
70eca6f51e | |
|
|
e07adfac3b |
210
README.md
210
README.md
|
|
@ -231,106 +231,160 @@ Whisper sometimes generates repeated text or phrases that weren't spoken. Whispe
|
|||
|
||||
## Installation
|
||||
|
||||
### Windows Installer (Easiest)
|
||||
### Windows (Recommended)
|
||||
|
||||
Download and run: **WhisperJAV-1.7.4-Windows-x86_64.exe**
|
||||
*Best for: Most users, beginners, and those who want a GUI.*
|
||||
|
||||
This installs everything you need including Python and dependencies.
|
||||
1. **Download the Installer:**
|
||||
**[Download WhisperJAV-1.7.5-Windows-x86_64.exe](https://github.com/meizhong986/WhisperJAV/releases/latest)**
|
||||
2. **Run the File:** Double-click the downloaded `.exe`.
|
||||
3. **Follow the Prompts:** The installer handles all dependencies (Python, FFmpeg, Git) automatically.
|
||||
4. **Launch:** Open "WhisperJAV" from your Desktop shortcut.
|
||||
|
||||
### Upgrading from Previous Installer Versions
|
||||
> **Note:** The first launch may take a few minutes as it initializes the engine. GPU is auto-detected; CPU-only mode is used if no compatible GPU is found.
|
||||
|
||||
If you installed v1.5.x or v1.6.x via the Windows installer:
|
||||
**Upgrading?** Just run the new installer. Your AI models (~3GB), settings, and cached downloads will be preserved.
|
||||
|
||||
1. Download [upgrade_whisperjav.bat](https://github.com/meizhong986/whisperjav/raw/main/installer/upgrade_whisperjav.bat)
|
||||
2. Double-click to run
|
||||
3. Wait 1-2 minutes
|
||||
---
|
||||
|
||||
This updates WhisperJAV without re-downloading PyTorch (~2.5GB) or your AI models (~3GB).
|
||||
### macOS (Apple Silicon & Intel)
|
||||
|
||||
### Install from Source
|
||||
*Best for: M1/M2/M3/M4 users and Intel Mac users.*
|
||||
|
||||
Requires Python 3.9-3.12, FFmpeg, and Git.
|
||||
The install script auto-detects your Mac architecture and handles PyTorch dependencies automatically.
|
||||
|
||||
**Recommended: Use the install scripts** (handles dependency conflicts automatically, auto-detects GPU):
|
||||
**1. Install Prerequisites**
|
||||
|
||||
```bash
|
||||
# Install Xcode Command Line Tools (required for GUI)
|
||||
xcode-select --install
|
||||
|
||||
# Install Homebrew (if not installed)
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
|
||||
# Install system tools
|
||||
brew install python@3.11 ffmpeg git
|
||||
```
|
||||
|
||||
> **GUI Requirement:** The Xcode Command Line Tools are required to compile `pyobjc`, which enables the GUI. Without it, only CLI mode will work.
|
||||
|
||||
**2. Install WhisperJAV**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/meizhong986/whisperjav.git
|
||||
cd whisperjav
|
||||
chmod +x installer/install_linux.sh
|
||||
|
||||
# Run the installer (auto-detects Mac architecture)
|
||||
./installer/install_linux.sh
|
||||
```
|
||||
|
||||
> **Intel Macs:** The script automatically uses CPU-only mode. Expect slower processing (5-10x) compared to Apple Silicon with MPS acceleration.
|
||||
|
||||
---
|
||||
|
||||
### Linux (Ubuntu/Debian/Fedora)
|
||||
|
||||
*Best for: Servers, desktops with NVIDIA GPUs.*
|
||||
|
||||
The install script auto-detects NVIDIA GPUs and installs the matching CUDA version.
|
||||
|
||||
**1. Install System Dependencies**
|
||||
|
||||
```bash
|
||||
# Debian / Ubuntu
|
||||
sudo apt-get update && sudo apt-get install -y python3-dev python3-pip build-essential ffmpeg libsndfile1 git
|
||||
|
||||
# Fedora / RHEL
|
||||
sudo dnf install python3-devel gcc ffmpeg libsndfile git
|
||||
```
|
||||
|
||||
**2. Install WhisperJAV**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/meizhong986/whisperjav.git
|
||||
cd whisperjav
|
||||
chmod +x installer/install_linux.sh
|
||||
|
||||
# Standard Install (auto-detects GPU)
|
||||
./installer/install_linux.sh
|
||||
|
||||
# Or force CPU-only (for servers without GPU)
|
||||
./installer/install_linux.sh --cpu-only
|
||||
```
|
||||
|
||||
> **Performance:** A 2-hour video takes ~5-10 minutes on GPU vs ~30-60 minutes on CPU.
|
||||
|
||||
---
|
||||
|
||||
### Advanced / Developer
|
||||
|
||||
*Best for: Contributors and Python experts.*
|
||||
|
||||
<details>
|
||||
<summary><b>Windows</b></summary>
|
||||
<summary><b>Manual pip install</b></summary>
|
||||
|
||||
> **Warning:** Manual `pip install` is risky due to dependency conflicts (NumPy 2.x vs SciPy). We strongly recommend using the scripts above.
|
||||
|
||||
**1. Create Environment**
|
||||
|
||||
```bash
|
||||
python -m venv whisperjav-env
|
||||
source whisperjav-env/bin/activate # Linux/Mac
|
||||
# whisperjav-env\Scripts\activate # Windows
|
||||
```
|
||||
|
||||
**2. Install PyTorch First (Critical)**
|
||||
|
||||
You must install PyTorch *before* the main package to ensure hardware acceleration works.
|
||||
|
||||
- **NVIDIA GPU:** `pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu124`
|
||||
- **Apple Silicon:** `pip install torch torchaudio`
|
||||
- **CPU only:** `pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu`
|
||||
|
||||
**3. Install WhisperJAV**
|
||||
|
||||
```bash
|
||||
pip install git+https://github.com/meizhong986/whisperjav.git@main
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Editable / Dev install</b></summary>
|
||||
|
||||
Use this if you plan to modify the code.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/meizhong986/whisperjav.git
|
||||
cd whisperjav
|
||||
|
||||
# Windows
|
||||
installer\install_windows.bat --dev
|
||||
|
||||
# Mac/Linux
|
||||
./installer/install_linux.sh --dev
|
||||
|
||||
# Or manual
|
||||
pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Windows source install</b></summary>
|
||||
|
||||
```batch
|
||||
git clone https://github.com/meizhong986/whisperjav.git
|
||||
cd whisperjav
|
||||
installer\install_windows.bat # Auto-detects GPU and CUDA version
|
||||
installer\install_windows.bat # Auto-detects GPU
|
||||
installer\install_windows.bat --cpu-only # Force CPU only
|
||||
installer\install_windows.bat --cuda118 # Force CUDA 11.8
|
||||
installer\install_windows.bat --cuda124 # Force CUDA 12.4
|
||||
installer\install_windows.bat --minimal # Minimal install (no speech enhancement)
|
||||
installer\install_windows.bat --dev # Development/editable install
|
||||
```
|
||||
|
||||
The script automatically:
|
||||
- Detects your NVIDIA GPU and selects optimal CUDA version
|
||||
- Falls back to CPU-only if no GPU found
|
||||
- Checks for WebView2 runtime (required for GUI)
|
||||
- Logs installation to `install_log_windows.txt`
|
||||
- Retries failed downloads up to 3 times
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Linux / macOS</b></summary>
|
||||
|
||||
```bash
|
||||
# Install system dependencies first (Linux only)
|
||||
# Debian/Ubuntu:
|
||||
sudo apt-get install -y python3-dev build-essential ffmpeg libsndfile1
|
||||
|
||||
# Fedora/RHEL:
|
||||
sudo dnf install python3-devel gcc ffmpeg libsndfile
|
||||
|
||||
git clone https://github.com/meizhong986/whisperjav.git
|
||||
cd whisperjav
|
||||
chmod +x installer/install_linux.sh
|
||||
./installer/install_linux.sh # Auto-detects GPU
|
||||
./installer/install_linux.sh --cpu-only # Force CPU only
|
||||
./installer/install_linux.sh --minimal # Minimal install
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Cross-Platform Python Script</b></summary>
|
||||
|
||||
```bash
|
||||
git clone https://github.com/meizhong986/whisperjav.git
|
||||
cd whisperjav
|
||||
python install.py # Auto-detects GPU, defaults to CUDA 12.1
|
||||
python install.py --cpu-only # CPU only
|
||||
python install.py --cuda118 # CUDA 11.8
|
||||
python install.py --cuda121 # CUDA 12.1
|
||||
python install.py --cuda124 # CUDA 12.4
|
||||
python install.py --minimal # Minimal install (no speech enhancement)
|
||||
python install.py --dev # Development/editable install
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
**Alternative: Manual pip install** (may encounter dependency conflicts):
|
||||
|
||||
```bash
|
||||
# Install PyTorch with GPU support first (NVIDIA example)
|
||||
pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu124
|
||||
|
||||
# Then install WhisperJAV
|
||||
pip install git+https://github.com/meizhong986/whisperjav.git@main
|
||||
```
|
||||
|
||||
**Platform Notes:**
|
||||
- **Apple Silicon (M1/M2/M3/M4)**: Just `pip install torch torchaudio` - MPS acceleration works automatically
|
||||
- **AMD GPU (ROCm)**: Experimental. Use `--mode balanced` for best compatibility
|
||||
- **CPU only**: Works but slow. Use `--accept-cpu-mode` to skip the GPU warning
|
||||
- **Linux server (no GPU)**: The install scripts auto-detect and switch to CPU-only
|
||||
- **Linux (Debian/Ubuntu)**: Install system dependencies first: `sudo apt-get install -y python3-dev build-essential ffmpeg libsndfile1`
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Python 3.9-3.12** (3.13+ not compatible with openai-whisper)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,281 @@
|
|||
# WhisperJAV v1.7.5 Release Notes
|
||||
|
||||
**Release Date:** January 2026
|
||||
|
||||
---
|
||||
|
||||
## New Features
|
||||
|
||||
### In-App Upgrade System (Experimental)
|
||||
|
||||
A complete upgrade system that checks for new versions and allows one-click updates.
|
||||
|
||||
| Interface | Command |
|
||||
|-----------|---------|
|
||||
| **GUI** | Notification appears on startup when updates are available |
|
||||
| **CLI** | `whisperjav-upgrade --check` |
|
||||
| **CLI** | `whisperjav-upgrade --yes` (auto-confirm) |
|
||||
| **CLI** | `whisperjav-upgrade --wheel-only` (skip dependencies) |
|
||||
|
||||
**Update Notification Intervals:**
|
||||
- Critical/security updates: Always shown
|
||||
- Major updates: Shown every launch
|
||||
- Minor updates: Shown monthly
|
||||
- Patch updates: Shown weekly (dismissable)
|
||||
|
||||
> **Note:** This feature is experimental. Backup important files before upgrading.
|
||||
|
||||
---
|
||||
|
||||
### Resumable Processing
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| **Translation Resume** | Interrupted translations continue from where they stopped |
|
||||
| **Batch Skip** | `--skip-existing` flag skips files that already have subtitles |
|
||||
|
||||
---
|
||||
|
||||
### GPU Auto-Detection
|
||||
|
||||
Installer and runtime now automatically detect GPU availability and fall back to CPU-only mode when no compatible GPU is found.
|
||||
|
||||
---
|
||||
|
||||
### Installation Scripts
|
||||
|
||||
New standalone scripts for source builds with automatic CUDA detection:
|
||||
|
||||
| Script | Platform | CUDA Support |
|
||||
|--------|----------|--------------|
|
||||
| `install_windows.bat` | Windows | Auto-detect (11.8, 12.1, 12.4, 12.6, 12.8) |
|
||||
| `install_linux.sh` | Linux | Auto-detect (11.8, 12.1, 12.4, 12.6, 12.8) |
|
||||
|
||||
---
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
| Issue | Description |
|
||||
|-------|-------------|
|
||||
| [#97](https://github.com/meizhong986/WhisperJAV/issues/97) | Fixed FFmpeg path not found in installer |
|
||||
| [#96](https://github.com/meizhong986/WhisperJAV/issues/96) | Added GUI documentation for tab relationships and settings persistence |
|
||||
| [#95](https://github.com/meizhong986/WhisperJAV/issues/95) | Fixed CUDA driver compatibility - falls back to CPU if incompatible |
|
||||
| [#94](https://github.com/meizhong986/WhisperJAV/issues/94) | Fixed Semantic options in Advanced tab |
|
||||
| [#92](https://github.com/meizhong986/WhisperJAV/issues/92) | Fixed version number display in GUI |
|
||||
| - | Fixed installer shortcut not launching application |
|
||||
|
||||
---
|
||||
|
||||
## Installer Improvements
|
||||
|
||||
- Improved Windows installer robustness and completeness
|
||||
- Added `fsspec>=2025.3.0` constraint to prevent unnecessary downgrades
|
||||
- Added progress indication during pip install phase
|
||||
- NSIS now handles all shortcut creation (proper separation of concerns)
|
||||
- Added support for CUDA 12.8 (driver 570+)
|
||||
- Fixed desktop shortcut pointing to correct executable (`WhisperJAV-GUI.exe`)
|
||||
|
||||
---
|
||||
|
||||
## Documentation Updates
|
||||
|
||||
- GUI documentation for tab relationships and settings persistence
|
||||
- Speech enhancement CLI documentation
|
||||
- Updated installation section with new scripts and features
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### Upgrading from v1.7.4?
|
||||
|
||||
If you are upgrading from version 1.7.4, simply run the new installer.
|
||||
|
||||
- **Do not uninstall manually.** The installer will detect your previous version.
|
||||
- **Data Safety:** Your AI models (~3GB), settings, and cached downloads will be **preserved**.
|
||||
- **Clean Install:** The installer will automatically clean up old dependency conflicts.
|
||||
|
||||
---
|
||||
|
||||
### Windows (Recommended)
|
||||
|
||||
*Best for: Most users, beginners, and those who want a GUI.*
|
||||
|
||||
1. **Download the Installer:**
|
||||
**[Download WhisperJAV-1.7.5-Windows-x86_64.exe](https://github.com/meizhong986/WhisperJAV/releases/tag/v1.7.5)**
|
||||
2. **Run the File:** Double-click the downloaded `.exe`.
|
||||
3. **Follow the Prompts:** The installer handles all dependencies (Python, FFmpeg, Git) automatically.
|
||||
4. **Launch:** Open "WhisperJAV" from your Desktop shortcut.
|
||||
|
||||
> **Note:** The first launch may take a few minutes as it initializes the engine. GPU is auto-detected; CPU-only mode is used if no compatible GPU is found.
|
||||
|
||||
---
|
||||
|
||||
### macOS (Apple Silicon & Intel)
|
||||
|
||||
*Best for: M1/M2/M3/M4 users and Intel Mac users.*
|
||||
|
||||
The install script auto-detects your Mac architecture and handles PyTorch dependencies automatically.
|
||||
|
||||
**1. Install Prerequisites**
|
||||
|
||||
```bash
|
||||
# Install Xcode Command Line Tools (required for GUI)
|
||||
xcode-select --install
|
||||
|
||||
# Install Homebrew (if not installed)
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
|
||||
# Install system tools
|
||||
brew install python@3.11 ffmpeg git
|
||||
```
|
||||
|
||||
> **GUI Requirement:** The Xcode Command Line Tools are required to compile `pyobjc`, which enables the GUI. Without it, only CLI mode will work.
|
||||
|
||||
**2. Install WhisperJAV**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/meizhong986/whisperjav.git
|
||||
cd whisperjav
|
||||
chmod +x installer/install_linux.sh
|
||||
|
||||
# Run the installer (auto-detects Mac architecture)
|
||||
./installer/install_linux.sh
|
||||
```
|
||||
|
||||
> **Intel Macs:** The script automatically uses CPU-only mode. Expect slower processing (5-10x) compared to Apple Silicon with MPS acceleration.
|
||||
|
||||
---
|
||||
|
||||
### Linux (Ubuntu/Debian/Fedora)
|
||||
|
||||
*Best for: Servers, desktops with NVIDIA GPUs.*
|
||||
|
||||
The install script auto-detects NVIDIA GPUs and installs the matching CUDA version.
|
||||
|
||||
**1. Install System Dependencies**
|
||||
|
||||
```bash
|
||||
# Debian / Ubuntu
|
||||
sudo apt-get update && sudo apt-get install -y python3-dev python3-pip build-essential ffmpeg libsndfile1 git
|
||||
|
||||
# Fedora / RHEL
|
||||
sudo dnf install python3-devel gcc ffmpeg libsndfile git
|
||||
```
|
||||
|
||||
**2. Install WhisperJAV**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/meizhong986/whisperjav.git
|
||||
cd whisperjav
|
||||
chmod +x installer/install_linux.sh
|
||||
|
||||
# Standard Install (auto-detects GPU)
|
||||
./installer/install_linux.sh
|
||||
|
||||
# Or force CPU-only (for servers without GPU)
|
||||
./installer/install_linux.sh --cpu-only
|
||||
```
|
||||
|
||||
> **Performance:** A 2-hour video takes ~5-10 minutes on GPU vs ~30-60 minutes on CPU.
|
||||
|
||||
---
|
||||
|
||||
### Advanced / Developer
|
||||
|
||||
*Best for: Contributors and Python experts.*
|
||||
|
||||
<details>
|
||||
<summary><b>Manual pip install</b></summary>
|
||||
|
||||
> **Warning:** Manual `pip install` is risky due to dependency conflicts (NumPy 2.x vs SciPy). We strongly recommend using the scripts above.
|
||||
|
||||
**1. Create Environment**
|
||||
|
||||
```bash
|
||||
python -m venv whisperjav-env
|
||||
source whisperjav-env/bin/activate # Linux/Mac
|
||||
# whisperjav-env\Scripts\activate # Windows
|
||||
```
|
||||
|
||||
**2. Install PyTorch First (Critical)**
|
||||
|
||||
You must install PyTorch *before* the main package to ensure hardware acceleration works.
|
||||
|
||||
- **NVIDIA GPU:** `pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu124`
|
||||
- **Apple Silicon:** `pip install torch torchaudio`
|
||||
- **CPU only:** `pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu`
|
||||
|
||||
**3. Install WhisperJAV**
|
||||
|
||||
```bash
|
||||
pip install git+https://github.com/meizhong986/whisperjav.git@main
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Editable / Dev install</b></summary>
|
||||
|
||||
Use this if you plan to modify the code.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/meizhong986/whisperjav.git
|
||||
cd whisperjav
|
||||
|
||||
# Windows
|
||||
installer\install_windows.bat --dev
|
||||
|
||||
# Mac/Linux
|
||||
./installer/install_linux.sh --dev
|
||||
|
||||
# Or manual
|
||||
pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Windows source install</b></summary>
|
||||
|
||||
```batch
|
||||
git clone https://github.com/meizhong986/whisperjav.git
|
||||
cd whisperjav
|
||||
installer\install_windows.bat # Auto-detects GPU
|
||||
installer\install_windows.bat --cpu-only # Force CPU only
|
||||
installer\install_windows.bat --cuda118 # Force CUDA 11.8
|
||||
installer\install_windows.bat --cuda124 # Force CUDA 12.4
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Technical Details
|
||||
|
||||
### New Files
|
||||
- `whisperjav/version_checker.py` - Version checking and update notifications
|
||||
- `whisperjav/upgrade.py` - Self-upgrade CLI tool
|
||||
- `tests/test_upgrade_version_checker.py` - 44 tests for version checker
|
||||
- `tests/test_upgrade_cli.py` - 36 tests for upgrade CLI
|
||||
|
||||
### Test Coverage
|
||||
- 80 automated tests for upgrade system
|
||||
|
||||
---
|
||||
|
||||
## Known Issues
|
||||
|
||||
- Self-upgrade on Windows may require Administrator privileges if installed in Program Files
|
||||
- Update notifications require GUI restart after upgrade
|
||||
|
||||
---
|
||||
|
||||
## Contributors
|
||||
|
||||
- MeiZhong - Development and testing
|
||||
- Claude (Anthropic) - Code assistance and documentation
|
||||
|
||||
---
|
||||
|
||||
**Questions or Issues?** [Open a GitHub issue](https://github.com/meizhong986/WhisperJAV/issues)
|
||||
|
|
@ -0,0 +1,409 @@
|
|||
===============================================================================
|
||||
WhisperJAV v1.7.5
|
||||
Japanese AV Subtitle Generator with AI
|
||||
===============================================================================
|
||||
|
||||
Thank you for installing WhisperJAV!
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
QUICK START
|
||||
-------------------------------------------------------------------------------
|
||||
1. Double-click the "WhisperJAV v1.7.5" desktop icon
|
||||
2. Select your video/audio files using "Add File(s)" or "Add Folder"
|
||||
3. Choose processing mode (Balanced recommended for best quality)
|
||||
4. Click "Start" to begin processing
|
||||
5. Subtitles will be saved next to your media files in an output folder
|
||||
|
||||
FIRST RUN NOTE: On your first transcription, AI models will download (~3GB).
|
||||
This is a one-time download that takes 5-10 minutes depending
|
||||
on your internet speed. Progress is shown in the GUI.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
WHAT'S NEW IN v1.7.5 (HOTFIX RELEASE)
|
||||
-------------------------------------------------------------------------------
|
||||
Version 1.7.5 is a hotfix release addressing critical issues:
|
||||
|
||||
1. CRITICAL BUG FIXES
|
||||
- Fixed missing FFmpeg issue in conda-constructor installer
|
||||
- FFmpeg now correctly bundled and accessible in PATH
|
||||
|
||||
2. NEW EXPERIMENTAL FEATURES
|
||||
- Self-upgrade system (EXPERIMENTAL)
|
||||
* Check for updates via GUI or CLI: whisperjav-upgrade --check
|
||||
* Automatic upgrade: whisperjav-upgrade --yes
|
||||
* Note: This feature is in evaluation phase - use with caution
|
||||
- Update notifications in GUI when new versions are available
|
||||
|
||||
IMPORTANT: The upgrade feature is EXPERIMENTAL. While we've tested it
|
||||
extensively, we recommend backing up important files before upgrading.
|
||||
Please report any issues at: https://github.com/meizhong986/WhisperJAV/issues
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
PREVIOUS RELEASE: v1.7.4
|
||||
-------------------------------------------------------------------------------
|
||||
Version 1.7.4 brought major improvements across 5 key areas:
|
||||
|
||||
1. MULTI-PLATFORM GPU SUPPORT
|
||||
- Apple Silicon Macs (M1/M2/M3/M4/M5) with native GPU acceleration
|
||||
- NVIDIA Blackwell architecture (RTX 50-series) support
|
||||
- Automatic device detection for best performance
|
||||
|
||||
2. RESUME TRANSLATION FEATURE
|
||||
- Never lose translation progress - auto-save after each batch
|
||||
- Resume interrupted translations without re-translating
|
||||
- Saves API costs by preserving completed work
|
||||
|
||||
3. MULTI-LANGUAGE SUPPORT (EXPERIMENTAL)
|
||||
- Chinese and Korean transcription
|
||||
- Selectable source language in GUI
|
||||
- Optimized for Japanese, basic support for others
|
||||
|
||||
4. GUI IMPROVEMENTS
|
||||
- Clearer language selection options
|
||||
- CPU mode acceptance checkbox (skip GPU warnings)
|
||||
- Fixed UI scrolling performance
|
||||
- Enhanced drag & drop support
|
||||
|
||||
5. BUG FIXES
|
||||
- Windows UTF-8 encoding fix (Japanese filenames work correctly)
|
||||
- CUDA version detection fix (accurate GPU detection)
|
||||
- UI scrolling performance improvements
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
WHAT THIS INSTALLER DOES
|
||||
-------------------------------------------------------------------------------
|
||||
This installer sets up a complete, self-contained WhisperJAV environment:
|
||||
|
||||
1. Base Environment Setup (~1-2 minutes)
|
||||
- Python 3.10.18 with conda package manager
|
||||
- FFmpeg for audio/video processing
|
||||
- Git for GitHub package installs
|
||||
|
||||
2. Post-Installation Downloads (~8-15 minutes, ~2.5 GB download)
|
||||
- PyTorch with CUDA support (for NVIDIA GPU acceleration)
|
||||
OR CPU-only PyTorch (if no compatible GPU detected)
|
||||
- Python dependencies (Whisper, audio processing, translation)
|
||||
- WhisperJAV application from GitHub
|
||||
|
||||
3. Setup and Configuration
|
||||
- WhisperJAV-GUI.exe launcher created in installation folder
|
||||
- Desktop shortcut creation (points to WhisperJAV-GUI.exe)
|
||||
- Environment configuration
|
||||
- WebView2 runtime check (required for GUI)
|
||||
|
||||
TOTAL INSTALLATION TIME: ~10-20 minutes (depending on internet speed)
|
||||
TOTAL DISK SPACE REQUIRED: ~8 GB (install + cache + models)
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
SYSTEM REQUIREMENTS
|
||||
-------------------------------------------------------------------------------
|
||||
MINIMUM:
|
||||
- Windows 10 (64-bit) or Windows 11
|
||||
- 8 GB RAM (16 GB recommended for large models)
|
||||
- 8 GB free disk space (install + models + cache)
|
||||
- Internet connection (for downloads)
|
||||
- Microsoft Edge WebView2 Runtime (installer will prompt if missing)
|
||||
|
||||
RECOMMENDED FOR BEST PERFORMANCE:
|
||||
- NVIDIA GPU with 6+ GB VRAM (RTX 2060 or better)
|
||||
- NVIDIA Driver with CUDA 11.8 or newer (CUDA 12.1+ preferred)
|
||||
- 16 GB RAM
|
||||
- SSD for faster processing
|
||||
|
||||
SUPPORTED PLATFORMS (v1.7.5):
|
||||
- Windows: NVIDIA GPU (RTX 20/30/40/50-series) or CPU-only
|
||||
- macOS: Apple Silicon (M1/M2/M3/M4/M5) with native MPS acceleration
|
||||
- macOS: Intel (CPU-only, slower)
|
||||
- Linux: NVIDIA GPU or CPU-only
|
||||
|
||||
CPU-ONLY MODE:
|
||||
WhisperJAV can run without an NVIDIA GPU using CPU-only mode, but
|
||||
processing will be significantly slower (6-10x slower than GPU mode).
|
||||
Expect 30-60 minutes per hour of video on CPU vs 5-10 minutes on GPU.
|
||||
|
||||
CUDA GPU ACCELERATION:
|
||||
The installer automatically detects your NVIDIA driver's CUDA version and
|
||||
installs the best matching PyTorch build for optimal performance:
|
||||
|
||||
- CUDA 11.8 - 12.0: PyTorch with CUDA 11.8 support
|
||||
- CUDA 12.1 - 12.3: PyTorch with CUDA 12.1 support
|
||||
- CUDA 12.4 - 12.7: PyTorch with CUDA 12.4 support
|
||||
- CUDA 12.8+: PyTorch with CUDA 12.4+ (Blackwell support)
|
||||
|
||||
This ensures you get the best possible GPU acceleration for your hardware
|
||||
while maintaining compatibility with your driver version.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
AFTER INSTALLATION
|
||||
-------------------------------------------------------------------------------
|
||||
LAUNCHING THE APPLICATION:
|
||||
- Desktop shortcut: "WhisperJAV v1.7.5.lnk" (double-click to start)
|
||||
- Manual launch: Double-click WhisperJAV-GUI.exe in installation folder
|
||||
- Alternative: Open the installation folder and run:
|
||||
pythonw.exe -m whisperjav.webview_gui.main
|
||||
|
||||
INSTALLATION LOCATION:
|
||||
- Default: C:\Users\[YourName]\AppData\Local\WhisperJAV
|
||||
- Or custom location chosen during installation
|
||||
|
||||
LOGS AND DIAGNOSTICS:
|
||||
- Installation log: install_log_v1.7.5.txt (in install folder)
|
||||
- Application logs: Shown in GUI console during processing
|
||||
- Failure marker: INSTALLATION_FAILED_v1.7.5.txt (only if install failed)
|
||||
|
||||
OUTPUT FILES:
|
||||
- Subtitles are saved next to your input video/audio files
|
||||
- Format: SRT (SubRip Text) - compatible with most video players
|
||||
- Filename pattern: [original_name]_output/[original_name].srt
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
SELF-UPGRADE FEATURE (EXPERIMENTAL)
|
||||
-------------------------------------------------------------------------------
|
||||
WhisperJAV v1.7.5 introduces an experimental self-upgrade system:
|
||||
|
||||
COMMAND LINE USAGE:
|
||||
# Check for updates
|
||||
whisperjav-upgrade --check
|
||||
|
||||
# Upgrade to latest version (with confirmation)
|
||||
whisperjav-upgrade
|
||||
|
||||
# Upgrade without confirmation
|
||||
whisperjav-upgrade --yes
|
||||
|
||||
# Wheel-only upgrade (skip installer dependencies)
|
||||
whisperjav-upgrade --wheel-only
|
||||
|
||||
GUI NOTIFICATION:
|
||||
- The GUI will notify you when updates are available
|
||||
- Click the notification to open the releases page
|
||||
- Or use the CLI command to upgrade
|
||||
|
||||
IMPORTANT NOTES:
|
||||
- This feature is EXPERIMENTAL - backup important files first
|
||||
- The upgrade preserves your settings and cached models
|
||||
- If something goes wrong, reinstall from the latest installer
|
||||
- Report issues at: https://github.com/meizhong986/WhisperJAV/issues
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
FEATURES
|
||||
-------------------------------------------------------------------------------
|
||||
PROCESSING MODES:
|
||||
- Balanced: Best quality, uses scene detection + voice activity detection
|
||||
- Fast: Good quality, faster than balanced, uses scene detection
|
||||
- Faster: Fastest processing, direct transcription without preprocessing
|
||||
|
||||
SENSITIVITY LEVELS:
|
||||
- Conservative: Fewer false positives, cleaner output
|
||||
- Balanced: Good balance of detail and accuracy (recommended)
|
||||
- Aggressive: Maximum detail capture, may include more background noise
|
||||
|
||||
SOURCE LANGUAGES:
|
||||
- Japanese: Fully optimized, best results
|
||||
- Korean: Experimental, basic support
|
||||
- Chinese: Experimental, basic support
|
||||
- English: Experimental
|
||||
|
||||
OUTPUT LANGUAGES:
|
||||
- Native: Original language transcription (default)
|
||||
- Direct to English: English transcription via Whisper
|
||||
|
||||
ADVANCED FEATURES:
|
||||
- Batch processing: Process multiple files sequentially
|
||||
- Model override: Choose specific Whisper models (large-v3, large-v2, turbo)
|
||||
- Opening credits: Add custom credit lines to subtitles
|
||||
- Async processing: Process multiple files simultaneously (experimental)
|
||||
- Translation: Translate subtitles to other languages (via whisperjav-translate)
|
||||
- Resume Translation: Resume interrupted translations automatically
|
||||
- Self-Upgrade: Update to latest version via CLI (experimental)
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
TROUBLESHOOTING
|
||||
-------------------------------------------------------------------------------
|
||||
INSTALLATION ISSUES:
|
||||
|
||||
1. "NVIDIA driver not found" or "CUDA version too old":
|
||||
- Download latest driver from: https://www.nvidia.com/drivers
|
||||
- Or accept CPU-only installation when prompted (slower processing)
|
||||
|
||||
2. "WebView2 runtime not detected":
|
||||
- The installer will open: https://go.microsoft.com/fwlink/p/?LinkId=2124703
|
||||
- Download and install the "Evergreen Standalone Installer"
|
||||
- Most Windows 10/11 systems have this pre-installed
|
||||
|
||||
3. "Network connection failed":
|
||||
- Check your internet connection
|
||||
- Disable VPN or proxy temporarily
|
||||
- Check firewall settings (allow Python, pip, git)
|
||||
|
||||
4. "Out of disk space":
|
||||
- Free up at least 8 GB on your system drive
|
||||
- Models are downloaded to C:\Users\[Name]\.cache\whisper (~3 GB)
|
||||
|
||||
5. "Installation failed after retries":
|
||||
- Check install_log_v1.7.5.txt for specific error messages
|
||||
- Common causes: antivirus blocking downloads, network timeout
|
||||
- Try running the installer as Administrator (right-click > Run as admin)
|
||||
|
||||
6. "FFmpeg not found" (FIXED in v1.7.5):
|
||||
- This issue has been fixed in v1.7.5
|
||||
- FFmpeg is now correctly bundled in the installer
|
||||
- If you still see this, try reinstalling from the v1.7.5 installer
|
||||
|
||||
RUNTIME ISSUES:
|
||||
|
||||
1. "GUI won't launch" or "Blank window":
|
||||
- Ensure WebView2 is installed (see issue #2 above)
|
||||
- Try running from command line to see error messages:
|
||||
python.exe -m whisperjav.webview_gui.main
|
||||
|
||||
2. "Processing is very slow":
|
||||
- Check if CUDA is enabled: Look for "GPU acceleration: ENABLED" in console
|
||||
- If CPU-only, consider upgrading to an NVIDIA GPU
|
||||
- For large videos, use "faster" mode for quicker results
|
||||
|
||||
3. "Model download stuck":
|
||||
- Large models (3GB) can take 10-20 minutes on slow connections
|
||||
- Check your internet speed: https://fast.com
|
||||
- Models are cached, so this only happens once
|
||||
|
||||
4. "Subtitles have errors or gibberish":
|
||||
- Try "balanced" mode for better quality
|
||||
- Use "conservative" sensitivity to reduce false positives
|
||||
- Ensure correct source language is selected
|
||||
- For non-Japanese audio, try English or other languages
|
||||
|
||||
5. "Application crashes during processing":
|
||||
- Check logs in the GUI console
|
||||
- Ensure you have enough RAM (16 GB recommended)
|
||||
- Try processing one file at a time instead of batch
|
||||
- Report crashes at: https://github.com/meizhong986/WhisperJAV/issues
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
UNINSTALLING
|
||||
-------------------------------------------------------------------------------
|
||||
To completely remove WhisperJAV v1.7.5:
|
||||
|
||||
1. Delete the installation directory:
|
||||
C:\Users\[YourName]\AppData\Local\WhisperJAV (or your custom location)
|
||||
|
||||
2. Delete the desktop shortcut:
|
||||
Desktop\WhisperJAV v1.7.5.lnk
|
||||
|
||||
3. (Optional) Delete cached models to free up ~3 GB:
|
||||
C:\Users\[YourName]\.cache\whisper
|
||||
|
||||
4. (Optional) Delete user configuration:
|
||||
[Install Directory]\whisperjav_config.json
|
||||
|
||||
NOTE: An automated uninstaller (uninstall_v1.7.5.bat) is included in the
|
||||
installation directory for your convenience.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
PERFORMANCE TIPS
|
||||
-------------------------------------------------------------------------------
|
||||
1. GPU Acceleration:
|
||||
- Ensure NVIDIA drivers are up to date
|
||||
- Close other GPU-intensive applications before processing
|
||||
- Verify CUDA is enabled by checking console output
|
||||
|
||||
2. Processing Speed:
|
||||
- "faster" mode: ~5 minutes per hour of video (GPU)
|
||||
- "fast" mode: ~7 minutes per hour of video (GPU)
|
||||
- "balanced" mode: ~10 minutes per hour of video (GPU)
|
||||
|
||||
3. Quality vs Speed:
|
||||
- For best accuracy: Use "balanced" mode with "balanced" sensitivity
|
||||
- For speed: Use "faster" mode with "conservative" sensitivity
|
||||
- For maximum detail: Use "balanced" mode with "aggressive" sensitivity
|
||||
|
||||
4. Batch Processing:
|
||||
- Process multiple files overnight using batch mode
|
||||
- Use "async processing" (experimental) for parallel execution
|
||||
|
||||
5. Translation Tips:
|
||||
- Enable resume feature for long translations
|
||||
- Progress auto-saved after each batch
|
||||
- Re-run same command to resume interrupted translations
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
ADVANCED USAGE
|
||||
-------------------------------------------------------------------------------
|
||||
COMMAND-LINE INTERFACE:
|
||||
WhisperJAV also includes a CLI for automation and scripting:
|
||||
|
||||
# Basic transcription
|
||||
whisperjav video.mp4 --mode balanced --sensitivity aggressive
|
||||
|
||||
# With translation
|
||||
whisperjav video.mp4 --translate --target-lang english
|
||||
|
||||
# Batch processing
|
||||
whisperjav folder/*.mp4 --mode fast --workers 2
|
||||
|
||||
Run "whisperjav --help" for full CLI documentation
|
||||
|
||||
TRANSLATION:
|
||||
Use whisperjav-translate for AI-powered subtitle translation:
|
||||
|
||||
# Translate Japanese to English
|
||||
whisperjav-translate -i subtitles.srt --target english
|
||||
|
||||
# Use custom instructions
|
||||
whisperjav-translate -i subtitles.srt --custom-gist [URL]
|
||||
|
||||
# Resume interrupted translation
|
||||
whisperjav-translate -i subtitles.srt
|
||||
# (Progress auto-saves, just re-run if interrupted)
|
||||
|
||||
See translation documentation for setup and API key configuration
|
||||
|
||||
SELF-UPGRADE (EXPERIMENTAL):
|
||||
# Check for updates
|
||||
whisperjav-upgrade --check
|
||||
|
||||
# Upgrade to latest version
|
||||
whisperjav-upgrade --yes
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
SUPPORT & COMMUNITY
|
||||
-------------------------------------------------------------------------------
|
||||
GitHub Repository: https://github.com/meizhong986/WhisperJAV
|
||||
Issue Tracker: https://github.com/meizhong986/WhisperJAV/issues
|
||||
Documentation: See GitHub README
|
||||
|
||||
For bugs, feature requests, or questions, please open an issue on GitHub
|
||||
with detailed information about your system and the problem.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
VERSION INFORMATION
|
||||
-------------------------------------------------------------------------------
|
||||
WhisperJAV Version: 1.7.5
|
||||
Release Date: January 2026
|
||||
Installer Version: v1.7.5 (conda-constructor)
|
||||
Release Type: Hotfix
|
||||
|
||||
Key Changes in 1.7.5:
|
||||
- Fixed missing FFmpeg issue in installer
|
||||
- Added experimental self-upgrade feature
|
||||
- Added update notification system
|
||||
- Improved installer robustness
|
||||
|
||||
Previous Major Changes (1.7.4):
|
||||
- Multi-platform GPU support (Apple Silicon, Blackwell)
|
||||
- Resume translation feature (auto-save progress)
|
||||
- Multi-language support (Chinese, Korean - experimental)
|
||||
- GUI improvements (language selection, CPU mode checkbox)
|
||||
- Critical bug fixes (UTF-8 encoding, CUDA detection, UI scrolling)
|
||||
|
||||
For full release notes, see RELEASE_NOTES_v1.7.5.md
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
LICENSE
|
||||
-------------------------------------------------------------------------------
|
||||
WhisperJAV is released under the MIT License.
|
||||
See LICENSE.txt in the installation directory for details.
|
||||
|
||||
Copyright (c) 2025-2026 MeiZhong and contributors
|
||||
|
||||
===============================================================================
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
WhisperJAV v1.7.5 GUI Launcher
|
||||
================================
|
||||
|
||||
This launcher script:
|
||||
1. Sets up the Python environment (PATH for ffmpeg, DLLs)
|
||||
2. Checks for first-run status (informational only)
|
||||
3. Launches the PyWebView GUI using pythonw.exe (no console window)
|
||||
4. Detaches the GUI process so the launcher can exit
|
||||
|
||||
The GUI handles all user interaction, including model downloads
|
||||
on first transcription.
|
||||
|
||||
No Tkinter dependency - all messages go to console (if run from terminal)
|
||||
or are handled by the GUI itself.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def log_first_run_info(install_root: Path):
|
||||
"""
|
||||
Check if this is first run and log informational message.
|
||||
|
||||
Note: We don't prompt the user here. The PyWebView GUI will handle
|
||||
model downloads automatically when the user starts their first transcription.
|
||||
"""
|
||||
config_path = install_root / "whisperjav_config.json"
|
||||
|
||||
try:
|
||||
if config_path.exists():
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
|
||||
if config.get("first_run", True):
|
||||
print("=" * 60)
|
||||
print(" WhisperJAV First Run")
|
||||
print("=" * 60)
|
||||
print("AI models (~3GB) will download on first transcription.")
|
||||
print("This is a one-time download that takes 5-10 minutes.")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# Mark first run as complete
|
||||
config["first_run"] = False
|
||||
with open(config_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(config, f, indent=2)
|
||||
|
||||
else:
|
||||
# Create default config for first run
|
||||
config = {
|
||||
"first_run": True,
|
||||
"version": "1.7.5"
|
||||
}
|
||||
with open(config_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(config, f, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
# Non-fatal: if config handling fails, just continue
|
||||
print(f"Note: Could not read/write config file: {e}")
|
||||
print("This won't affect the application.")
|
||||
|
||||
|
||||
def main():
|
||||
"""Launch WhisperJAV GUI using pythonw.exe (windowless)"""
|
||||
try:
|
||||
# Determine installation root directory
|
||||
# When frozen (PyInstaller), use directory of executable
|
||||
# Otherwise, use sys.prefix (conda environment root)
|
||||
if getattr(sys, 'frozen', False):
|
||||
install_root = Path(os.path.dirname(sys.executable))
|
||||
else:
|
||||
install_root = Path(sys.prefix)
|
||||
|
||||
print(f"WhisperJAV v1.7.5 Launcher")
|
||||
print(f"Installation: {install_root}")
|
||||
print()
|
||||
|
||||
# Add Scripts and Library\bin to PATH for this session
|
||||
# This ensures ffmpeg and other DLLs are accessible
|
||||
scripts_dir = install_root / "Scripts"
|
||||
lib_bin_dir = install_root / "Library" / "bin"
|
||||
|
||||
path_additions = [
|
||||
str(scripts_dir),
|
||||
str(lib_bin_dir),
|
||||
os.environ.get("PATH", "")
|
||||
]
|
||||
os.environ["PATH"] = os.pathsep.join(path_additions)
|
||||
|
||||
# Check for first run (informational only)
|
||||
log_first_run_info(install_root)
|
||||
|
||||
# Find pythonw.exe (preferred) or python.exe (fallback)
|
||||
pythonw = install_root / "pythonw.exe"
|
||||
if not pythonw.exists():
|
||||
pythonw = install_root / "python.exe"
|
||||
if not pythonw.exists():
|
||||
print(f"ERROR: Cannot find Python executable in {install_root}")
|
||||
print("Installation may be corrupted.")
|
||||
input("Press Enter to exit...")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Launching GUI...")
|
||||
print(f"Using: {pythonw}")
|
||||
print()
|
||||
|
||||
# Build command to launch PyWebView GUI
|
||||
cmd = [
|
||||
str(pythonw),
|
||||
"-m",
|
||||
"whisperjav.webview_gui.main"
|
||||
]
|
||||
|
||||
# Launch as detached process (DETACHED_PROCESS flag on Windows)
|
||||
# This allows the launcher to exit while the GUI continues running
|
||||
if sys.platform == "win32":
|
||||
creationflags = subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
|
||||
else:
|
||||
creationflags = 0
|
||||
|
||||
subprocess.Popen(
|
||||
cmd,
|
||||
cwd=str(install_root),
|
||||
creationflags=creationflags,
|
||||
close_fds=True
|
||||
)
|
||||
|
||||
print("GUI launched successfully!")
|
||||
print("You can close this window.")
|
||||
|
||||
except FileNotFoundError as e:
|
||||
print()
|
||||
print("=" * 60)
|
||||
print(" ERROR: File Not Found")
|
||||
print("=" * 60)
|
||||
print(f"{e}")
|
||||
print()
|
||||
print("The installation may be incomplete or corrupted.")
|
||||
print("Please try reinstalling WhisperJAV.")
|
||||
print("=" * 60)
|
||||
input("Press Enter to exit...")
|
||||
sys.exit(1)
|
||||
|
||||
except Exception as e:
|
||||
print()
|
||||
print("=" * 60)
|
||||
print(" ERROR: Failed to Start WhisperJAV")
|
||||
print("=" * 60)
|
||||
print(f"{e}")
|
||||
print()
|
||||
print("Please check the installation or report this issue at:")
|
||||
print("https://github.com/meizhong986/WhisperJAV/issues")
|
||||
print("=" * 60)
|
||||
input("Press Enter to exit...")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,289 @@
|
|||
@echo off
|
||||
REM ===============================================================================
|
||||
REM WhisperJAV v1.7.5 Installer Build Script
|
||||
REM ===============================================================================
|
||||
REM
|
||||
REM This script builds the conda-constructor installer for WhisperJAV v1.7.5
|
||||
REM
|
||||
REM Prerequisites:
|
||||
REM - Conda with constructor package installed:
|
||||
REM conda install constructor -c conda-forge
|
||||
REM - All v1.7.5 installer files must be present in installer/ directory
|
||||
REM
|
||||
REM Build Process:
|
||||
REM 1. Check prerequisites (constructor, Python)
|
||||
REM 2. Validate installer configuration (via validate_installer_v1.7.5.py)
|
||||
REM 3. Clean previous v1.7.5 builds
|
||||
REM 4. Run constructor to build installer
|
||||
REM 5. Verify output and generate build report
|
||||
REM
|
||||
REM Output: WhisperJAV-1.7.5-Windows-x86_64.exe
|
||||
REM Logs: build_log_v1.7.5.txt
|
||||
REM
|
||||
|
||||
SETLOCAL EnableDelayedExpansion
|
||||
|
||||
REM ===== Configuration =====
|
||||
set CONFIG_FILE=construct_v1.7.5.yaml
|
||||
set VALIDATOR=validate_installer_v1.7.5.py
|
||||
set BUILD_LOG=build_log_v1.7.5.txt
|
||||
set VERSION=1.7.5
|
||||
|
||||
echo ===============================================================================
|
||||
echo WhisperJAV v1.7.5 Installer Build
|
||||
echo ===============================================================================
|
||||
echo.
|
||||
|
||||
REM Initialize log file
|
||||
echo WhisperJAV v1.7.5 Installer Build Log > "%BUILD_LOG%"
|
||||
echo Build started: %DATE% %TIME% >> "%BUILD_LOG%"
|
||||
echo. >> "%BUILD_LOG%"
|
||||
|
||||
REM ===== Phase 1: Check Prerequisites =====
|
||||
echo [Phase 1/6] Checking prerequisites...
|
||||
echo [Phase 1/6] Checking prerequisites... >> "%BUILD_LOG%"
|
||||
|
||||
REM Check if constructor is installed
|
||||
constructor --version >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo.
|
||||
echo ERROR: Constructor not found or not working!
|
||||
echo ERROR: Constructor not found >> "%BUILD_LOG%"
|
||||
echo.
|
||||
echo Please install constructor:
|
||||
echo conda install constructor -c conda-forge
|
||||
echo.
|
||||
echo If constructor is installed:
|
||||
echo 1. Activate your conda environment: conda activate base
|
||||
echo 2. Ensure conda is in your PATH
|
||||
echo 3. Try running: constructor --version
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
for /f "tokens=*" %%a in ('constructor --version 2^>^&1') do set CONS_VER=%%a
|
||||
echo - Constructor: %CONS_VER%
|
||||
echo - Constructor: %CONS_VER% >> "%BUILD_LOG%"
|
||||
|
||||
REM Check if Python is available
|
||||
python --version >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo.
|
||||
echo ERROR: Python not found!
|
||||
echo ERROR: Python not found >> "%BUILD_LOG%"
|
||||
echo Please ensure Python is in your PATH.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
for /f "tokens=*" %%a in ('python --version 2^>^&1') do set PY_VER=%%a
|
||||
echo - Python: %PY_VER%
|
||||
echo - Python: %PY_VER% >> "%BUILD_LOG%"
|
||||
|
||||
REM Check if config file exists
|
||||
if not exist "%CONFIG_FILE%" (
|
||||
echo.
|
||||
echo ERROR: Configuration file not found: %CONFIG_FILE%
|
||||
echo ERROR: Missing %CONFIG_FILE% >> "%BUILD_LOG%"
|
||||
echo.
|
||||
echo Make sure you're running this script from the installer/ directory.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo - Config file: %CONFIG_FILE% (found)
|
||||
echo - Config file: %CONFIG_FILE% >> "%BUILD_LOG%"
|
||||
echo.
|
||||
|
||||
REM ===== Phase 2: Verify WhisperJAV Wheel =====
|
||||
echo [Phase 2/6] Checking for WhisperJAV wheel...
|
||||
echo [Phase 2/6] Checking wheel... >> "%BUILD_LOG%"
|
||||
|
||||
REM Check if wheel already exists (built by build_release.py)
|
||||
REM Use dir command for reliable wildcard detection
|
||||
dir /b whisperjav-*.whl >nul 2>nul
|
||||
if !errorlevel!==0 (
|
||||
REM Wheel found - get the filename
|
||||
for /f "delims=" %%f in ('dir /b whisperjav-*.whl 2^>nul') do set WHEEL_FILE=%%f
|
||||
echo - Found existing wheel: !WHEEL_FILE!
|
||||
echo - Wheel: !WHEEL_FILE! (pre-built) >> "%BUILD_LOG%"
|
||||
) else (
|
||||
echo - No wheel found, building from source...
|
||||
REM Call parent directory script (uses %~dp0 so works from any location)
|
||||
call ..\build_whisperjav_wheel.bat
|
||||
if errorlevel 1 (
|
||||
echo.
|
||||
echo ERROR: Failed to build WhisperJAV wheel!
|
||||
echo ERROR: Wheel build failed >> "%BUILD_LOG%"
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
REM Verify wheel now exists
|
||||
dir /b whisperjav-*.whl >nul 2>nul
|
||||
if errorlevel 1 (
|
||||
echo.
|
||||
echo ERROR: Wheel still not found after build!
|
||||
echo ERROR: Wheel missing after build >> "%BUILD_LOG%"
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
for /f "delims=" %%f in ('dir /b whisperjav-*.whl 2^>nul') do set WHEEL_FILE=%%f
|
||||
echo - WhisperJAV wheel built: !WHEEL_FILE!
|
||||
echo - Wheel: !WHEEL_FILE! (local build) >> "%BUILD_LOG%"
|
||||
)
|
||||
echo.
|
||||
|
||||
REM ===== Phase 3: Validate Configuration =====
|
||||
REM NOTE: PyInstaller build removed - WhisperJAV-GUI.exe is created by post_install
|
||||
REM from Scripts/whisperjav-gui.exe (pip entry_points)
|
||||
echo [Phase 3/6] Validating installer configuration...
|
||||
echo [Phase 3/6] Validating configuration... >> "%BUILD_LOG%"
|
||||
|
||||
if exist "%VALIDATOR%" (
|
||||
python "%VALIDATOR%"
|
||||
if errorlevel 1 (
|
||||
echo.
|
||||
echo ERROR: Configuration validation failed!
|
||||
echo ERROR: Validation failed >> "%BUILD_LOG%"
|
||||
echo Check the error messages above and fix any issues.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo - All validation checks passed
|
||||
echo - Validation: PASSED >> "%BUILD_LOG%"
|
||||
) else (
|
||||
echo - Validation script not found, skipping validation
|
||||
echo - Validation: SKIPPED (validator not found) >> "%BUILD_LOG%"
|
||||
)
|
||||
echo.
|
||||
|
||||
REM ===== Phase 4: Clean Previous Builds =====
|
||||
echo [Phase 4/6] Cleaning previous v1.7.5 builds...
|
||||
echo [Phase 4/6] Cleaning previous builds... >> "%BUILD_LOG%"
|
||||
|
||||
REM Only clean v1.7.5 builds, preserve other versions
|
||||
if exist build (
|
||||
echo - Removing build/ directory...
|
||||
rmdir /s /q build
|
||||
echo - Cleaned: build/ >> "%BUILD_LOG%"
|
||||
)
|
||||
|
||||
if exist _build (
|
||||
echo - Removing _build/ directory...
|
||||
rmdir /s /q _build
|
||||
echo - Cleaned: _build/ >> "%BUILD_LOG%"
|
||||
)
|
||||
|
||||
if exist WhisperJAV-%VERSION%-Windows-x86_64.exe (
|
||||
echo - Removing old installer: WhisperJAV-%VERSION%-Windows-x86_64.exe
|
||||
del WhisperJAV-%VERSION%-Windows-x86_64.exe
|
||||
echo - Cleaned: WhisperJAV-%VERSION%-Windows-x86_64.exe >> "%BUILD_LOG%"
|
||||
)
|
||||
|
||||
echo - Cleanup complete
|
||||
echo.
|
||||
|
||||
REM ===== Phase 5: Build Installer =====
|
||||
echo [Phase 5/6] Building installer with constructor...
|
||||
echo [Phase 5/6] Building installer... >> "%BUILD_LOG%"
|
||||
echo.
|
||||
echo This will take 2-5 minutes. Please wait...
|
||||
echo.
|
||||
|
||||
REM Build with verbose output
|
||||
constructor . --config "%CONFIG_FILE%" -v
|
||||
|
||||
if errorlevel 1 (
|
||||
echo.
|
||||
echo ===============================================================================
|
||||
echo BUILD FAILED!
|
||||
echo ===============================================================================
|
||||
echo.
|
||||
echo ERROR: Constructor build failed! >> "%BUILD_LOG%"
|
||||
echo Check the error messages above for details.
|
||||
echo.
|
||||
echo Common issues:
|
||||
echo - Missing dependencies in construct_v1.7.5.yaml
|
||||
echo - Network issues downloading packages
|
||||
echo - Insufficient disk space
|
||||
echo - Corrupted conda package cache
|
||||
echo.
|
||||
echo To troubleshoot:
|
||||
echo 1. Check %BUILD_LOG% for details
|
||||
echo 2. Try: conda clean --all
|
||||
echo 3. Verify all files in construct_v1.7.5.yaml extra_files exist
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
|
||||
REM ===== Phase 6: Verify Output and Generate Report =====
|
||||
echo [Phase 6/6] Verifying build output...
|
||||
echo [Phase 6/6] Verifying output... >> "%BUILD_LOG%"
|
||||
|
||||
set INSTALLER_NAME=WhisperJAV-%VERSION%-Windows-x86_64.exe
|
||||
|
||||
if not exist "%INSTALLER_NAME%" (
|
||||
echo.
|
||||
echo ERROR: Installer file was not created!
|
||||
echo ERROR: Output file missing: %INSTALLER_NAME% >> "%BUILD_LOG%"
|
||||
echo.
|
||||
echo Expected: %INSTALLER_NAME%
|
||||
echo.
|
||||
echo This suggests the build failed silently.
|
||||
echo Check constructor output above for errors.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Get file size
|
||||
for %%A in ("%INSTALLER_NAME%") do set FILESIZE=%%~zA
|
||||
set /a FILESIZE_MB=!FILESIZE! / 1048576
|
||||
|
||||
echo - Installer created: %INSTALLER_NAME%
|
||||
echo - File size: !FILESIZE_MB! MB
|
||||
echo.
|
||||
|
||||
REM Log success
|
||||
echo. >> "%BUILD_LOG%"
|
||||
echo =============================================================================== >> "%BUILD_LOG%"
|
||||
echo BUILD SUCCESSFUL >> "%BUILD_LOG%"
|
||||
echo =============================================================================== >> "%BUILD_LOG%"
|
||||
echo. >> "%BUILD_LOG%"
|
||||
echo Output file: %INSTALLER_NAME% >> "%BUILD_LOG%"
|
||||
echo File size: !FILESIZE_MB! MB >> "%BUILD_LOG%"
|
||||
echo Build completed: %DATE% %TIME% >> "%BUILD_LOG%"
|
||||
echo. >> "%BUILD_LOG%"
|
||||
|
||||
REM ===== Build Summary =====
|
||||
echo ===============================================================================
|
||||
echo BUILD COMPLETED SUCCESSFULLY!
|
||||
echo ===============================================================================
|
||||
echo.
|
||||
echo Installer Details:
|
||||
echo - File: %INSTALLER_NAME%
|
||||
echo - Size: !FILESIZE_MB! MB
|
||||
echo - Version: %VERSION%
|
||||
echo - Build log: %BUILD_LOG%
|
||||
echo.
|
||||
echo Next Steps:
|
||||
echo 1. Test the installer on a clean Windows VM
|
||||
echo 2. Verify all features work correctly
|
||||
echo 3. Check installation logs (install_log_v1.7.5.txt)
|
||||
echo 4. Distribute to users
|
||||
echo.
|
||||
echo To test installer:
|
||||
echo - Run %INSTALLER_NAME%
|
||||
echo - Follow installation prompts
|
||||
echo - Launch GUI from desktop shortcut
|
||||
echo - Process a test video
|
||||
echo.
|
||||
echo ===============================================================================
|
||||
|
||||
pause
|
||||
exit /b 0
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['..\\WhisperJAV_Launcher_v1.7.5.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name='WhisperJAV',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=False,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
icon=['whisperjav_icon.ico'],
|
||||
)
|
||||
|
|
@ -0,0 +1,902 @@
|
|||
(['C:\\BIN\\git\\WhisperJav_V1_Minami_Edition\\installer\\generated\\WhisperJAV_Launcher_v1.7.5.py'],
|
||||
['C:\\BIN\\git\\WhisperJav_V1_Minami_Edition\\installer\\generated'],
|
||||
[],
|
||||
[('C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\site-packages\\numpy\\_pyinstaller',
|
||||
0),
|
||||
('C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\site-packages\\webview\\__pyinstaller',
|
||||
0),
|
||||
('C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\site-packages\\_pyinstaller_hooks_contrib\\stdhooks',
|
||||
-1000),
|
||||
('C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\site-packages\\_pyinstaller_hooks_contrib',
|
||||
-1000)],
|
||||
{},
|
||||
[],
|
||||
[],
|
||||
False,
|
||||
{},
|
||||
0,
|
||||
[],
|
||||
[],
|
||||
'3.11.14 | packaged by conda-forge | (main, Oct 22 2025, 22:35:28) [MSC '
|
||||
'v.1944 64 bit (AMD64)]',
|
||||
[('pyi_rth_inspect',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py',
|
||||
'PYSOURCE'),
|
||||
('WhisperJAV_Launcher_v1.7.5',
|
||||
'C:\\BIN\\git\\WhisperJav_V1_Minami_Edition\\installer\\generated\\WhisperJAV_Launcher_v1.7.5.py',
|
||||
'PYSOURCE')],
|
||||
[('zipfile',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\zipfile.py',
|
||||
'PYMODULE'),
|
||||
('argparse',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\argparse.py',
|
||||
'PYMODULE'),
|
||||
('textwrap',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\textwrap.py',
|
||||
'PYMODULE'),
|
||||
('copy', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\copy.py', 'PYMODULE'),
|
||||
('gettext',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\gettext.py',
|
||||
'PYMODULE'),
|
||||
('py_compile',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\py_compile.py',
|
||||
'PYMODULE'),
|
||||
('importlib.machinery',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\machinery.py',
|
||||
'PYMODULE'),
|
||||
('importlib',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('importlib._bootstrap',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\_bootstrap.py',
|
||||
'PYMODULE'),
|
||||
('importlib._bootstrap_external',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\_bootstrap_external.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\metadata\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('typing', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\typing.py', 'PYMODULE'),
|
||||
('importlib.abc',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\abc.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources.abc',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\resources\\abc.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\resources\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources._legacy',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\resources\\_legacy.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources._common',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\resources\\_common.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources._adapters',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\resources\\_adapters.py',
|
||||
'PYMODULE'),
|
||||
('tempfile',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\tempfile.py',
|
||||
'PYMODULE'),
|
||||
('random', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\random.py', 'PYMODULE'),
|
||||
('statistics',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\statistics.py',
|
||||
'PYMODULE'),
|
||||
('decimal',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\decimal.py',
|
||||
'PYMODULE'),
|
||||
('_pydecimal',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\_pydecimal.py',
|
||||
'PYMODULE'),
|
||||
('contextvars',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\contextvars.py',
|
||||
'PYMODULE'),
|
||||
('fractions',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\fractions.py',
|
||||
'PYMODULE'),
|
||||
('numbers',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\numbers.py',
|
||||
'PYMODULE'),
|
||||
('hashlib',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\hashlib.py',
|
||||
'PYMODULE'),
|
||||
('logging',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\logging\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('pickle', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\pickle.py', 'PYMODULE'),
|
||||
('pprint', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\pprint.py', 'PYMODULE'),
|
||||
('dataclasses',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\dataclasses.py',
|
||||
'PYMODULE'),
|
||||
('_compat_pickle',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\_compat_pickle.py',
|
||||
'PYMODULE'),
|
||||
('string', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\string.py', 'PYMODULE'),
|
||||
('bisect', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\bisect.py', 'PYMODULE'),
|
||||
('importlib._abc',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\_abc.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._itertools',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\metadata\\_itertools.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._functools',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\metadata\\_functools.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._collections',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\metadata\\_collections.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._meta',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\metadata\\_meta.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._adapters',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\metadata\\_adapters.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._text',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\metadata\\_text.py',
|
||||
'PYMODULE'),
|
||||
('email.message',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\message.py',
|
||||
'PYMODULE'),
|
||||
('email.policy',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\policy.py',
|
||||
'PYMODULE'),
|
||||
('email.contentmanager',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\contentmanager.py',
|
||||
'PYMODULE'),
|
||||
('email.quoprimime',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\quoprimime.py',
|
||||
'PYMODULE'),
|
||||
('email.headerregistry',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\headerregistry.py',
|
||||
'PYMODULE'),
|
||||
('email._header_value_parser',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\_header_value_parser.py',
|
||||
'PYMODULE'),
|
||||
('urllib',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\urllib\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('email.iterators',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\iterators.py',
|
||||
'PYMODULE'),
|
||||
('email.generator',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\generator.py',
|
||||
'PYMODULE'),
|
||||
('email._encoded_words',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\_encoded_words.py',
|
||||
'PYMODULE'),
|
||||
('base64', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\base64.py', 'PYMODULE'),
|
||||
('getopt', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\getopt.py', 'PYMODULE'),
|
||||
('email.charset',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\charset.py',
|
||||
'PYMODULE'),
|
||||
('email.encoders',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\encoders.py',
|
||||
'PYMODULE'),
|
||||
('email.base64mime',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\base64mime.py',
|
||||
'PYMODULE'),
|
||||
('email._policybase',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\_policybase.py',
|
||||
'PYMODULE'),
|
||||
('email.header',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\header.py',
|
||||
'PYMODULE'),
|
||||
('email.errors',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\errors.py',
|
||||
'PYMODULE'),
|
||||
('email.utils',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\utils.py',
|
||||
'PYMODULE'),
|
||||
('email._parseaddr',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\_parseaddr.py',
|
||||
'PYMODULE'),
|
||||
('calendar',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\calendar.py',
|
||||
'PYMODULE'),
|
||||
('urllib.parse',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\urllib\\parse.py',
|
||||
'PYMODULE'),
|
||||
('ipaddress',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\ipaddress.py',
|
||||
'PYMODULE'),
|
||||
('datetime',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\datetime.py',
|
||||
'PYMODULE'),
|
||||
('_strptime',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\_strptime.py',
|
||||
'PYMODULE'),
|
||||
('socket', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\socket.py', 'PYMODULE'),
|
||||
('selectors',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\selectors.py',
|
||||
'PYMODULE'),
|
||||
('quopri', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\quopri.py', 'PYMODULE'),
|
||||
('email',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('email.parser',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\parser.py',
|
||||
'PYMODULE'),
|
||||
('email.feedparser',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\feedparser.py',
|
||||
'PYMODULE'),
|
||||
('csv', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\csv.py', 'PYMODULE'),
|
||||
('importlib.readers',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\readers.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources.readers',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\resources\\readers.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources._itertools',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\resources\\_itertools.py',
|
||||
'PYMODULE'),
|
||||
('tokenize',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\tokenize.py',
|
||||
'PYMODULE'),
|
||||
('token', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\token.py', 'PYMODULE'),
|
||||
('lzma', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\lzma.py', 'PYMODULE'),
|
||||
('_compression',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\_compression.py',
|
||||
'PYMODULE'),
|
||||
('bz2', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\bz2.py', 'PYMODULE'),
|
||||
('contextlib',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\contextlib.py',
|
||||
'PYMODULE'),
|
||||
('threading',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\threading.py',
|
||||
'PYMODULE'),
|
||||
('_threading_local',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\_threading_local.py',
|
||||
'PYMODULE'),
|
||||
('struct', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\struct.py', 'PYMODULE'),
|
||||
('shutil', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\shutil.py', 'PYMODULE'),
|
||||
('tarfile',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\tarfile.py',
|
||||
'PYMODULE'),
|
||||
('gzip', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\gzip.py', 'PYMODULE'),
|
||||
('fnmatch',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\fnmatch.py',
|
||||
'PYMODULE'),
|
||||
('importlib.util',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\util.py',
|
||||
'PYMODULE'),
|
||||
('inspect',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\inspect.py',
|
||||
'PYMODULE'),
|
||||
('dis', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\dis.py', 'PYMODULE'),
|
||||
('opcode', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\opcode.py', 'PYMODULE'),
|
||||
('ast', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\ast.py', 'PYMODULE'),
|
||||
('_py_abc',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\_py_abc.py',
|
||||
'PYMODULE'),
|
||||
('tracemalloc',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\tracemalloc.py',
|
||||
'PYMODULE'),
|
||||
('stringprep',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\stringprep.py',
|
||||
'PYMODULE'),
|
||||
('pathlib',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\pathlib.py',
|
||||
'PYMODULE'),
|
||||
('subprocess',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\subprocess.py',
|
||||
'PYMODULE'),
|
||||
('signal', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\signal.py', 'PYMODULE'),
|
||||
('json',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\json\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('json.encoder',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\json\\encoder.py',
|
||||
'PYMODULE'),
|
||||
('json.decoder',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\json\\decoder.py',
|
||||
'PYMODULE'),
|
||||
('json.scanner',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\json\\scanner.py',
|
||||
'PYMODULE')],
|
||||
[('python311.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\python311.dll',
|
||||
'BINARY'),
|
||||
('_decimal.pyd',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\DLLs\\_decimal.pyd',
|
||||
'EXTENSION'),
|
||||
('_hashlib.pyd',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\DLLs\\_hashlib.pyd',
|
||||
'EXTENSION'),
|
||||
('unicodedata.pyd',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\DLLs\\unicodedata.pyd',
|
||||
'EXTENSION'),
|
||||
('select.pyd',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\DLLs\\select.pyd',
|
||||
'EXTENSION'),
|
||||
('_socket.pyd',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\DLLs\\_socket.pyd',
|
||||
'EXTENSION'),
|
||||
('_lzma.pyd',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\DLLs\\_lzma.pyd',
|
||||
'EXTENSION'),
|
||||
('_bz2.pyd',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\DLLs\\_bz2.pyd',
|
||||
'EXTENSION'),
|
||||
('api-ms-win-crt-runtime-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-runtime-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-heap-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-heap-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-stdio-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-stdio-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-conio-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-conio-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-math-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-math-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-environment-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-environment-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-time-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-time-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('zlib.dll', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\zlib.dll', 'BINARY'),
|
||||
('api-ms-win-crt-string-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-string-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-process-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-process-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-filesystem-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-filesystem-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-locale-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-locale-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-convert-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-convert-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('VCRUNTIME140.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\VCRUNTIME140.dll',
|
||||
'BINARY'),
|
||||
('libcrypto-3-x64.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Library\\bin\\libcrypto-3-x64.dll',
|
||||
'BINARY'),
|
||||
('liblzma.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Library\\bin\\liblzma.dll',
|
||||
'BINARY'),
|
||||
('libbz2.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Library\\bin\\libbz2.dll',
|
||||
'BINARY'),
|
||||
('ucrtbase.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\ucrtbase.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-utility-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Library\\bin\\api-ms-win-crt-utility-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-sysinfo-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-sysinfo-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-errorhandling-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-errorhandling-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-console-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-console-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-localization-l1-2-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-localization-l1-2-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-memory-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-memory-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-file-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-file-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-profile-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-profile-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-synch-l1-2-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-synch-l1-2-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-util-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-util-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-string-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-string-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-file-l1-2-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-file-l1-2-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-processenvironment-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-processenvironment-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-timezone-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-timezone-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-synch-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-synch-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-handle-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-handle-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-libraryloader-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-libraryloader-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-datetime-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-datetime-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-interlocked-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-interlocked-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-heap-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-heap-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-namedpipe-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-namedpipe-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-fibers-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-fibers-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-processthreads-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-processthreads-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-processthreads-l1-1-1.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-processthreads-l1-1-1.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-debug-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-debug-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-file-l2-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-file-l2-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-rtlsupport-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-rtlsupport-l1-1-0.dll',
|
||||
'BINARY')],
|
||||
[],
|
||||
[],
|
||||
[('base_library.zip',
|
||||
'C:\\BIN\\git\\WhisperJav_V1_Minami_Edition\\installer\\generated\\build_pyinstaller\\WhisperJAV\\base_library.zip',
|
||||
'DATA')],
|
||||
[('ntpath', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\ntpath.py', 'PYMODULE'),
|
||||
('types', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\types.py', 'PYMODULE'),
|
||||
('keyword',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\keyword.py',
|
||||
'PYMODULE'),
|
||||
('copyreg',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\copyreg.py',
|
||||
'PYMODULE'),
|
||||
('sre_constants',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\sre_constants.py',
|
||||
'PYMODULE'),
|
||||
('operator',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\operator.py',
|
||||
'PYMODULE'),
|
||||
('genericpath',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\genericpath.py',
|
||||
'PYMODULE'),
|
||||
('locale', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\locale.py', 'PYMODULE'),
|
||||
('abc', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\abc.py', 'PYMODULE'),
|
||||
('collections.abc',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\collections\\abc.py',
|
||||
'PYMODULE'),
|
||||
('collections',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\collections\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('enum', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\enum.py', 'PYMODULE'),
|
||||
('functools',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\functools.py',
|
||||
'PYMODULE'),
|
||||
('reprlib',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\reprlib.py',
|
||||
'PYMODULE'),
|
||||
('warnings',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\warnings.py',
|
||||
'PYMODULE'),
|
||||
('encodings.zlib_codec',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\zlib_codec.py',
|
||||
'PYMODULE'),
|
||||
('encodings.uu_codec',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\uu_codec.py',
|
||||
'PYMODULE'),
|
||||
('encodings.utf_8_sig',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\utf_8_sig.py',
|
||||
'PYMODULE'),
|
||||
('encodings.utf_8',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\utf_8.py',
|
||||
'PYMODULE'),
|
||||
('encodings.utf_7',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\utf_7.py',
|
||||
'PYMODULE'),
|
||||
('encodings.utf_32_le',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\utf_32_le.py',
|
||||
'PYMODULE'),
|
||||
('encodings.utf_32_be',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\utf_32_be.py',
|
||||
'PYMODULE'),
|
||||
('encodings.utf_32',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\utf_32.py',
|
||||
'PYMODULE'),
|
||||
('encodings.utf_16_le',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\utf_16_le.py',
|
||||
'PYMODULE'),
|
||||
('encodings.utf_16_be',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\utf_16_be.py',
|
||||
'PYMODULE'),
|
||||
('encodings.utf_16',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\utf_16.py',
|
||||
'PYMODULE'),
|
||||
('encodings.unicode_escape',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\unicode_escape.py',
|
||||
'PYMODULE'),
|
||||
('encodings.undefined',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\undefined.py',
|
||||
'PYMODULE'),
|
||||
('encodings.tis_620',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\tis_620.py',
|
||||
'PYMODULE'),
|
||||
('encodings.shift_jisx0213',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\shift_jisx0213.py',
|
||||
'PYMODULE'),
|
||||
('encodings.shift_jis_2004',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\shift_jis_2004.py',
|
||||
'PYMODULE'),
|
||||
('encodings.shift_jis',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\shift_jis.py',
|
||||
'PYMODULE'),
|
||||
('encodings.rot_13',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\rot_13.py',
|
||||
'PYMODULE'),
|
||||
('encodings.raw_unicode_escape',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\raw_unicode_escape.py',
|
||||
'PYMODULE'),
|
||||
('encodings.quopri_codec',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\quopri_codec.py',
|
||||
'PYMODULE'),
|
||||
('encodings.punycode',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\punycode.py',
|
||||
'PYMODULE'),
|
||||
('encodings.ptcp154',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\ptcp154.py',
|
||||
'PYMODULE'),
|
||||
('encodings.palmos',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\palmos.py',
|
||||
'PYMODULE'),
|
||||
('encodings.oem',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\oem.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mbcs',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\mbcs.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mac_turkish',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\mac_turkish.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mac_romanian',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\mac_romanian.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mac_roman',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\mac_roman.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mac_latin2',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\mac_latin2.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mac_iceland',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\mac_iceland.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mac_greek',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\mac_greek.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mac_farsi',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\mac_farsi.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mac_cyrillic',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\mac_cyrillic.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mac_croatian',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\mac_croatian.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mac_arabic',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\mac_arabic.py',
|
||||
'PYMODULE'),
|
||||
('encodings.latin_1',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\latin_1.py',
|
||||
'PYMODULE'),
|
||||
('encodings.kz1048',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\kz1048.py',
|
||||
'PYMODULE'),
|
||||
('encodings.koi8_u',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\koi8_u.py',
|
||||
'PYMODULE'),
|
||||
('encodings.koi8_t',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\koi8_t.py',
|
||||
'PYMODULE'),
|
||||
('encodings.koi8_r',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\koi8_r.py',
|
||||
'PYMODULE'),
|
||||
('encodings.johab',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\johab.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_9',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso8859_9.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_8',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso8859_8.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_7',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso8859_7.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_6',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso8859_6.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_5',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso8859_5.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_4',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso8859_4.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_3',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso8859_3.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_2',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso8859_2.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_16',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso8859_16.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_15',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso8859_15.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_14',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso8859_14.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_13',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso8859_13.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_11',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso8859_11.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_10',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso8859_10.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_1',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso8859_1.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso2022_kr',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso2022_kr.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso2022_jp_ext',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso2022_jp_ext.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso2022_jp_3',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso2022_jp_3.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso2022_jp_2004',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso2022_jp_2004.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso2022_jp_2',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso2022_jp_2.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso2022_jp_1',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso2022_jp_1.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso2022_jp',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\iso2022_jp.py',
|
||||
'PYMODULE'),
|
||||
('encodings.idna',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\idna.py',
|
||||
'PYMODULE'),
|
||||
('encodings.hz',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\hz.py',
|
||||
'PYMODULE'),
|
||||
('encodings.hp_roman8',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\hp_roman8.py',
|
||||
'PYMODULE'),
|
||||
('encodings.hex_codec',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\hex_codec.py',
|
||||
'PYMODULE'),
|
||||
('encodings.gbk',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\gbk.py',
|
||||
'PYMODULE'),
|
||||
('encodings.gb2312',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\gb2312.py',
|
||||
'PYMODULE'),
|
||||
('encodings.gb18030',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\gb18030.py',
|
||||
'PYMODULE'),
|
||||
('encodings.euc_kr',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\euc_kr.py',
|
||||
'PYMODULE'),
|
||||
('encodings.euc_jp',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\euc_jp.py',
|
||||
'PYMODULE'),
|
||||
('encodings.euc_jisx0213',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\euc_jisx0213.py',
|
||||
'PYMODULE'),
|
||||
('encodings.euc_jis_2004',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\euc_jis_2004.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp950',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp950.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp949',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp949.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp932',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp932.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp875',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp875.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp874',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp874.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp869',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp869.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp866',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp866.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp865',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp865.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp864',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp864.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp863',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp863.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp862',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp862.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp861',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp861.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp860',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp860.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp858',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp858.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp857',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp857.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp856',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp856.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp855',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp855.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp852',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp852.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp850',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp850.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp775',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp775.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp737',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp737.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp720',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp720.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp500',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp500.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp437',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp437.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp424',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp424.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp273',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp273.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1258',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp1258.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1257',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp1257.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1256',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp1256.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1255',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp1255.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1254',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp1254.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1253',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp1253.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1252',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp1252.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1251',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp1251.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1250',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp1250.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1140',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp1140.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1125',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp1125.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1026',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp1026.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1006',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp1006.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp037',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\cp037.py',
|
||||
'PYMODULE'),
|
||||
('encodings.charmap',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\charmap.py',
|
||||
'PYMODULE'),
|
||||
('encodings.bz2_codec',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\bz2_codec.py',
|
||||
'PYMODULE'),
|
||||
('encodings.big5hkscs',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\big5hkscs.py',
|
||||
'PYMODULE'),
|
||||
('encodings.big5',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\big5.py',
|
||||
'PYMODULE'),
|
||||
('encodings.base64_codec',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\base64_codec.py',
|
||||
'PYMODULE'),
|
||||
('encodings.ascii',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\ascii.py',
|
||||
'PYMODULE'),
|
||||
('encodings.aliases',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\aliases.py',
|
||||
'PYMODULE'),
|
||||
('encodings',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\encodings\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('posixpath',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\posixpath.py',
|
||||
'PYMODULE'),
|
||||
('traceback',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\traceback.py',
|
||||
'PYMODULE'),
|
||||
('heapq', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\heapq.py', 'PYMODULE'),
|
||||
('re._parser',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\re\\_parser.py',
|
||||
'PYMODULE'),
|
||||
('re._constants',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\re\\_constants.py',
|
||||
'PYMODULE'),
|
||||
('re._compiler',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\re\\_compiler.py',
|
||||
'PYMODULE'),
|
||||
('re._casefix',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\re\\_casefix.py',
|
||||
'PYMODULE'),
|
||||
('re',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\re\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('io', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\io.py', 'PYMODULE'),
|
||||
('_weakrefset',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\_weakrefset.py',
|
||||
'PYMODULE'),
|
||||
('codecs', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\codecs.py', 'PYMODULE'),
|
||||
('linecache',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\linecache.py',
|
||||
'PYMODULE'),
|
||||
('weakref',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\weakref.py',
|
||||
'PYMODULE'),
|
||||
('_collections_abc',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\_collections_abc.py',
|
||||
'PYMODULE'),
|
||||
('stat', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\stat.py', 'PYMODULE'),
|
||||
('sre_compile',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\sre_compile.py',
|
||||
'PYMODULE'),
|
||||
('sre_parse',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\sre_parse.py',
|
||||
'PYMODULE'),
|
||||
('os', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\os.py', 'PYMODULE')])
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
('C:\\BIN\\git\\WhisperJav_V1_Minami_Edition\\installer\\generated\\build_pyinstaller\\WhisperJAV\\WhisperJAV.pkg',
|
||||
{'BINARY': True,
|
||||
'DATA': True,
|
||||
'EXECUTABLE': True,
|
||||
'EXTENSION': True,
|
||||
'PYMODULE': True,
|
||||
'PYSOURCE': True,
|
||||
'PYZ': False,
|
||||
'SPLASH': True,
|
||||
'SYMLINK': False},
|
||||
[('pyi-contents-directory _internal', '', 'OPTION'),
|
||||
('PYZ-00.pyz',
|
||||
'C:\\BIN\\git\\WhisperJav_V1_Minami_Edition\\installer\\generated\\build_pyinstaller\\WhisperJAV\\PYZ-00.pyz',
|
||||
'PYZ'),
|
||||
('struct',
|
||||
'C:\\BIN\\git\\WhisperJav_V1_Minami_Edition\\installer\\generated\\build_pyinstaller\\WhisperJAV\\localpycs\\struct.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod01_archive',
|
||||
'C:\\BIN\\git\\WhisperJav_V1_Minami_Edition\\installer\\generated\\build_pyinstaller\\WhisperJAV\\localpycs\\pyimod01_archive.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod02_importers',
|
||||
'C:\\BIN\\git\\WhisperJav_V1_Minami_Edition\\installer\\generated\\build_pyinstaller\\WhisperJAV\\localpycs\\pyimod02_importers.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod03_ctypes',
|
||||
'C:\\BIN\\git\\WhisperJav_V1_Minami_Edition\\installer\\generated\\build_pyinstaller\\WhisperJAV\\localpycs\\pyimod03_ctypes.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod04_pywin32',
|
||||
'C:\\BIN\\git\\WhisperJav_V1_Minami_Edition\\installer\\generated\\build_pyinstaller\\WhisperJAV\\localpycs\\pyimod04_pywin32.pyc',
|
||||
'PYMODULE'),
|
||||
('pyiboot01_bootstrap',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\site-packages\\PyInstaller\\loader\\pyiboot01_bootstrap.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_inspect',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py',
|
||||
'PYSOURCE'),
|
||||
('WhisperJAV_Launcher_v1.7.5',
|
||||
'C:\\BIN\\git\\WhisperJav_V1_Minami_Edition\\installer\\generated\\WhisperJAV_Launcher_v1.7.5.py',
|
||||
'PYSOURCE'),
|
||||
('python311.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\python311.dll',
|
||||
'BINARY'),
|
||||
('_decimal.pyd',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\DLLs\\_decimal.pyd',
|
||||
'EXTENSION'),
|
||||
('_hashlib.pyd',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\DLLs\\_hashlib.pyd',
|
||||
'EXTENSION'),
|
||||
('unicodedata.pyd',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\DLLs\\unicodedata.pyd',
|
||||
'EXTENSION'),
|
||||
('select.pyd',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\DLLs\\select.pyd',
|
||||
'EXTENSION'),
|
||||
('_socket.pyd',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\DLLs\\_socket.pyd',
|
||||
'EXTENSION'),
|
||||
('_lzma.pyd',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\DLLs\\_lzma.pyd',
|
||||
'EXTENSION'),
|
||||
('_bz2.pyd',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\DLLs\\_bz2.pyd',
|
||||
'EXTENSION'),
|
||||
('api-ms-win-crt-runtime-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-runtime-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-heap-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-heap-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-stdio-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-stdio-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-conio-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-conio-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-math-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-math-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-environment-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-environment-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-time-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-time-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('zlib.dll', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\zlib.dll', 'BINARY'),
|
||||
('api-ms-win-crt-string-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-string-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-process-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-process-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-filesystem-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-filesystem-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-locale-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-locale-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-convert-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-crt-convert-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('VCRUNTIME140.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\VCRUNTIME140.dll',
|
||||
'BINARY'),
|
||||
('libcrypto-3-x64.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Library\\bin\\libcrypto-3-x64.dll',
|
||||
'BINARY'),
|
||||
('liblzma.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Library\\bin\\liblzma.dll',
|
||||
'BINARY'),
|
||||
('libbz2.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Library\\bin\\libbz2.dll',
|
||||
'BINARY'),
|
||||
('ucrtbase.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\ucrtbase.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-crt-utility-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Library\\bin\\api-ms-win-crt-utility-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-sysinfo-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-sysinfo-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-errorhandling-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-errorhandling-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-console-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-console-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-localization-l1-2-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-localization-l1-2-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-memory-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-memory-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-file-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-file-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-profile-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-profile-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-synch-l1-2-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-synch-l1-2-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-util-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-util-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-string-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-string-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-file-l1-2-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-file-l1-2-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-processenvironment-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-processenvironment-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-timezone-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-timezone-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-synch-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-synch-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-handle-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-handle-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-libraryloader-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-libraryloader-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-datetime-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-datetime-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-interlocked-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-interlocked-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-heap-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-heap-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-namedpipe-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-namedpipe-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-fibers-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-fibers-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-processthreads-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-processthreads-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-processthreads-l1-1-1.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-processthreads-l1-1-1.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-debug-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-debug-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-file-l2-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-file-l2-1-0.dll',
|
||||
'BINARY'),
|
||||
('api-ms-win-core-rtlsupport-l1-1-0.dll',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\api-ms-win-core-rtlsupport-l1-1-0.dll',
|
||||
'BINARY'),
|
||||
('base_library.zip',
|
||||
'C:\\BIN\\git\\WhisperJav_V1_Minami_Edition\\installer\\generated\\build_pyinstaller\\WhisperJAV\\base_library.zip',
|
||||
'DATA')],
|
||||
'python311.dll',
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
[],
|
||||
None,
|
||||
None,
|
||||
None)
|
||||
Binary file not shown.
|
|
@ -0,0 +1,268 @@
|
|||
('C:\\BIN\\git\\WhisperJav_V1_Minami_Edition\\installer\\generated\\build_pyinstaller\\WhisperJAV\\PYZ-00.pyz',
|
||||
[('_compat_pickle',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\_compat_pickle.py',
|
||||
'PYMODULE'),
|
||||
('_compression',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\_compression.py',
|
||||
'PYMODULE'),
|
||||
('_py_abc',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\_py_abc.py',
|
||||
'PYMODULE'),
|
||||
('_pydecimal',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\_pydecimal.py',
|
||||
'PYMODULE'),
|
||||
('_strptime',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\_strptime.py',
|
||||
'PYMODULE'),
|
||||
('_threading_local',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\_threading_local.py',
|
||||
'PYMODULE'),
|
||||
('argparse',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\argparse.py',
|
||||
'PYMODULE'),
|
||||
('ast', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\ast.py', 'PYMODULE'),
|
||||
('base64', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\base64.py', 'PYMODULE'),
|
||||
('bisect', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\bisect.py', 'PYMODULE'),
|
||||
('bz2', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\bz2.py', 'PYMODULE'),
|
||||
('calendar',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\calendar.py',
|
||||
'PYMODULE'),
|
||||
('contextlib',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\contextlib.py',
|
||||
'PYMODULE'),
|
||||
('contextvars',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\contextvars.py',
|
||||
'PYMODULE'),
|
||||
('copy', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\copy.py', 'PYMODULE'),
|
||||
('csv', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\csv.py', 'PYMODULE'),
|
||||
('dataclasses',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\dataclasses.py',
|
||||
'PYMODULE'),
|
||||
('datetime',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\datetime.py',
|
||||
'PYMODULE'),
|
||||
('decimal',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\decimal.py',
|
||||
'PYMODULE'),
|
||||
('dis', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\dis.py', 'PYMODULE'),
|
||||
('email',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('email._encoded_words',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\_encoded_words.py',
|
||||
'PYMODULE'),
|
||||
('email._header_value_parser',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\_header_value_parser.py',
|
||||
'PYMODULE'),
|
||||
('email._parseaddr',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\_parseaddr.py',
|
||||
'PYMODULE'),
|
||||
('email._policybase',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\_policybase.py',
|
||||
'PYMODULE'),
|
||||
('email.base64mime',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\base64mime.py',
|
||||
'PYMODULE'),
|
||||
('email.charset',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\charset.py',
|
||||
'PYMODULE'),
|
||||
('email.contentmanager',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\contentmanager.py',
|
||||
'PYMODULE'),
|
||||
('email.encoders',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\encoders.py',
|
||||
'PYMODULE'),
|
||||
('email.errors',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\errors.py',
|
||||
'PYMODULE'),
|
||||
('email.feedparser',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\feedparser.py',
|
||||
'PYMODULE'),
|
||||
('email.generator',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\generator.py',
|
||||
'PYMODULE'),
|
||||
('email.header',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\header.py',
|
||||
'PYMODULE'),
|
||||
('email.headerregistry',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\headerregistry.py',
|
||||
'PYMODULE'),
|
||||
('email.iterators',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\iterators.py',
|
||||
'PYMODULE'),
|
||||
('email.message',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\message.py',
|
||||
'PYMODULE'),
|
||||
('email.parser',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\parser.py',
|
||||
'PYMODULE'),
|
||||
('email.policy',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\policy.py',
|
||||
'PYMODULE'),
|
||||
('email.quoprimime',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\quoprimime.py',
|
||||
'PYMODULE'),
|
||||
('email.utils',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\email\\utils.py',
|
||||
'PYMODULE'),
|
||||
('fnmatch',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\fnmatch.py',
|
||||
'PYMODULE'),
|
||||
('fractions',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\fractions.py',
|
||||
'PYMODULE'),
|
||||
('getopt', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\getopt.py', 'PYMODULE'),
|
||||
('gettext',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\gettext.py',
|
||||
'PYMODULE'),
|
||||
('gzip', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\gzip.py', 'PYMODULE'),
|
||||
('hashlib',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\hashlib.py',
|
||||
'PYMODULE'),
|
||||
('importlib',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('importlib._abc',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\_abc.py',
|
||||
'PYMODULE'),
|
||||
('importlib._bootstrap',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\_bootstrap.py',
|
||||
'PYMODULE'),
|
||||
('importlib._bootstrap_external',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\_bootstrap_external.py',
|
||||
'PYMODULE'),
|
||||
('importlib.abc',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\abc.py',
|
||||
'PYMODULE'),
|
||||
('importlib.machinery',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\machinery.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\metadata\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._adapters',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\metadata\\_adapters.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._collections',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\metadata\\_collections.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._functools',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\metadata\\_functools.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._itertools',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\metadata\\_itertools.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._meta',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\metadata\\_meta.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._text',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\metadata\\_text.py',
|
||||
'PYMODULE'),
|
||||
('importlib.readers',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\readers.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\resources\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources._adapters',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\resources\\_adapters.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources._common',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\resources\\_common.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources._itertools',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\resources\\_itertools.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources._legacy',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\resources\\_legacy.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources.abc',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\resources\\abc.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources.readers',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\resources\\readers.py',
|
||||
'PYMODULE'),
|
||||
('importlib.util',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\importlib\\util.py',
|
||||
'PYMODULE'),
|
||||
('inspect',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\inspect.py',
|
||||
'PYMODULE'),
|
||||
('ipaddress',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\ipaddress.py',
|
||||
'PYMODULE'),
|
||||
('json',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\json\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('json.decoder',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\json\\decoder.py',
|
||||
'PYMODULE'),
|
||||
('json.encoder',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\json\\encoder.py',
|
||||
'PYMODULE'),
|
||||
('json.scanner',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\json\\scanner.py',
|
||||
'PYMODULE'),
|
||||
('logging',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\logging\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('lzma', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\lzma.py', 'PYMODULE'),
|
||||
('numbers',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\numbers.py',
|
||||
'PYMODULE'),
|
||||
('opcode', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\opcode.py', 'PYMODULE'),
|
||||
('pathlib',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\pathlib.py',
|
||||
'PYMODULE'),
|
||||
('pickle', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\pickle.py', 'PYMODULE'),
|
||||
('pprint', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\pprint.py', 'PYMODULE'),
|
||||
('py_compile',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\py_compile.py',
|
||||
'PYMODULE'),
|
||||
('quopri', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\quopri.py', 'PYMODULE'),
|
||||
('random', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\random.py', 'PYMODULE'),
|
||||
('selectors',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\selectors.py',
|
||||
'PYMODULE'),
|
||||
('shutil', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\shutil.py', 'PYMODULE'),
|
||||
('signal', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\signal.py', 'PYMODULE'),
|
||||
('socket', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\socket.py', 'PYMODULE'),
|
||||
('statistics',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\statistics.py',
|
||||
'PYMODULE'),
|
||||
('string', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\string.py', 'PYMODULE'),
|
||||
('stringprep',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\stringprep.py',
|
||||
'PYMODULE'),
|
||||
('subprocess',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\subprocess.py',
|
||||
'PYMODULE'),
|
||||
('tarfile',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\tarfile.py',
|
||||
'PYMODULE'),
|
||||
('tempfile',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\tempfile.py',
|
||||
'PYMODULE'),
|
||||
('textwrap',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\textwrap.py',
|
||||
'PYMODULE'),
|
||||
('threading',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\threading.py',
|
||||
'PYMODULE'),
|
||||
('token', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\token.py', 'PYMODULE'),
|
||||
('tokenize',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\tokenize.py',
|
||||
'PYMODULE'),
|
||||
('tracemalloc',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\tracemalloc.py',
|
||||
'PYMODULE'),
|
||||
('typing', 'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\typing.py', 'PYMODULE'),
|
||||
('urllib',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\urllib\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('urllib.parse',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\urllib\\parse.py',
|
||||
'PYMODULE'),
|
||||
('zipfile',
|
||||
'C:\\Users\\MK\\anaconda3\\envs\\WJ\\Lib\\zipfile.py',
|
||||
'PYMODULE')])
|
||||
Binary file not shown.
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
This file lists modules PyInstaller was not able to find. This does not
|
||||
necessarily mean this module is required for running your program. Python and
|
||||
Python 3rd-party packages include a lot of conditional or optional modules. For
|
||||
example the module 'ntpath' only exists on Windows, whereas the module
|
||||
'posixpath' only exists on Posix systems.
|
||||
|
||||
Types if import:
|
||||
* top-level: imported at the top-level - look at these first
|
||||
* conditional: imported within an if-statement
|
||||
* delayed: imported within a function
|
||||
* optional: imported within a try-except-statement
|
||||
|
||||
IMPORTANT: Do NOT post this list to the issue-tracker. Use it as a basis for
|
||||
tracking down the missing module yourself. Thanks!
|
||||
|
||||
missing module named 'org.python' - imported by copy (optional)
|
||||
missing module named _frozen_importlib_external - imported by importlib._bootstrap (delayed), importlib (optional), importlib.abc (optional)
|
||||
excluded module named _frozen_importlib - imported by importlib (optional), importlib.abc (optional)
|
||||
missing module named org - imported by pickle (optional)
|
||||
missing module named posix - imported by os (conditional, optional), posixpath (optional), shutil (conditional), importlib._bootstrap_external (conditional)
|
||||
missing module named resource - imported by posix (top-level)
|
||||
missing module named pwd - imported by posixpath (delayed, conditional, optional), shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional)
|
||||
missing module named grp - imported by shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional)
|
||||
missing module named _posixsubprocess - imported by subprocess (conditional)
|
||||
missing module named fcntl - imported by subprocess (optional)
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,109 @@
|
|||
# WhisperJAV v1.7.5 Dependency Constraints
|
||||
# ==========================================
|
||||
#
|
||||
# This file pins critical dependency versions to prevent conflicts caused by
|
||||
# speech enhancement packages (modelscope, clearvoice, bs-roformer, zipenhancer).
|
||||
#
|
||||
# These packages have aggressive dependency specifications that can break:
|
||||
# - numpy (modelscope wants <2.0, but we need >=2.0 for librosa 0.11+)
|
||||
# - datasets (modelscope breaks with datasets 4.x)
|
||||
# - huggingface-hub version compatibility
|
||||
# - librosa audio processing
|
||||
#
|
||||
# Usage: pip install -c constraints_v1.7.5.txt -r requirements_v1.7.5.txt
|
||||
#
|
||||
# IMPORTANT: This file is used during installation to prevent pip from
|
||||
# downgrading critical packages when installing speech enhancement backends.
|
||||
|
||||
# =============================================================================
|
||||
# Core Scientific Stack (STRICT - Do NOT allow downgrades)
|
||||
# =============================================================================
|
||||
# numpy 2.x is required for librosa 0.11+ compatibility
|
||||
# modelscope/zipenhancer may try to install numpy<2.0 - BLOCK THIS
|
||||
numpy>=2.0
|
||||
|
||||
# scipy 1.10.1+ required for audio processing compatibility
|
||||
scipy>=1.10.1
|
||||
|
||||
# librosa 0.11+ supports numpy 2.x
|
||||
librosa>=0.11.0
|
||||
|
||||
# =============================================================================
|
||||
# ML Ecosystem Stability (CRITICAL for modelscope compatibility)
|
||||
# =============================================================================
|
||||
# datasets 4.x breaks modelscope - MUST cap at <4.0
|
||||
datasets>=2.14.0,<4.0
|
||||
|
||||
# pydantic 3.x has breaking API changes
|
||||
pydantic>=2.0,<3.0
|
||||
|
||||
# HuggingFace ecosystem version alignment
|
||||
transformers>=4.40.0
|
||||
accelerate>=0.26.0
|
||||
huggingface-hub>=0.25.0
|
||||
|
||||
# fsspec for filesystem operations (prevent unnecessary downgrade)
|
||||
fsspec>=2025.3.0
|
||||
|
||||
# =============================================================================
|
||||
# System Utilities
|
||||
# =============================================================================
|
||||
# psutil for process management (clean subprocess termination)
|
||||
psutil>=5.9.0
|
||||
|
||||
# pywebview for GUI framework
|
||||
pywebview>=5.0.0
|
||||
|
||||
# =============================================================================
|
||||
# Audio/Video Processing
|
||||
# =============================================================================
|
||||
# Faster Whisper for efficient transcription
|
||||
faster-whisper>=1.1.0
|
||||
|
||||
# silero-vad for voice activity detection
|
||||
silero-vad>=6.0
|
||||
|
||||
# PySubtrans for subtitle translation
|
||||
PySubtrans>=0.7.0
|
||||
|
||||
# OpenAI API for translation providers
|
||||
openai>=1.35.0
|
||||
|
||||
# Google Gemini API for translation providers
|
||||
google-genai>=1.39.0
|
||||
|
||||
# scikit-learn for semantic scene detection
|
||||
scikit-learn>=1.3.0
|
||||
|
||||
# ONNX runtime for ZipEnhancer inference
|
||||
onnxruntime>=1.16.0
|
||||
|
||||
# modelscope for ZipEnhancer speech enhancement
|
||||
modelscope>=1.20
|
||||
|
||||
# =============================================================================
|
||||
# Package Conflict Notes
|
||||
# =============================================================================
|
||||
#
|
||||
# Known conflicts and mitigations:
|
||||
#
|
||||
# 1. modelscope + numpy:
|
||||
# - modelscope may request numpy<2.0
|
||||
# - We REQUIRE numpy>=2.0 for librosa 0.11+
|
||||
# - Solution: Install numpy first, then modelscope with --no-deps flag
|
||||
#
|
||||
# 2. modelscope + datasets:
|
||||
# - modelscope breaks with datasets>=4.0
|
||||
# - Solution: Cap datasets<4.0 in constraints
|
||||
#
|
||||
# 3. clearvoice + numpy:
|
||||
# - Original clearvoice requires numpy<2.0
|
||||
# - Solution: Use forked version with numpy 2.x support
|
||||
#
|
||||
# 4. bs-roformer-infer:
|
||||
# - Generally compatible but may pull old torchaudio
|
||||
# - Solution: Install PyTorch/torchaudio first
|
||||
#
|
||||
# 5. zipenhancer (via modelscope):
|
||||
# - Part of modelscope ecosystem
|
||||
# - Solution: Install modelscope with constraints
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
# Conda-Constructor Configuration for WhisperJAV v1.7.5
|
||||
# =========================================================
|
||||
#
|
||||
# This creates a Windows installer that:
|
||||
# - Installs minimal conda environment to %LOCALAPPDATA%\WhisperJAV
|
||||
# - Downloads PyTorch and dependencies during post-install
|
||||
# - Defers AI model downloads to first application run
|
||||
# - Checks for WebView2 and provides download link if missing
|
||||
#
|
||||
# Build command: constructor . --config construct_v1.7.5.yaml
|
||||
# Output: WhisperJAV-1.7.5-Windows-x86_64.exe
|
||||
|
||||
name: WhisperJAV
|
||||
version: 1.7.5
|
||||
|
||||
# Conda channels for package resolution
|
||||
channels:
|
||||
- conda-forge
|
||||
- defaults
|
||||
|
||||
# Minimal base environment (heavy dependencies in post-install)
|
||||
specs:
|
||||
- python=3.10.18 # Pinned for stability (tested version)
|
||||
- pip # Package installer
|
||||
- git # Required for GitHub installs
|
||||
- ffmpeg>=6,<8 # Audio/video processing (v6 or v7 required, v8+ incompatible)
|
||||
- menuinst # Desktop/Start Menu shortcut creation
|
||||
|
||||
# Install in dependency order for reliability
|
||||
install_in_dependency_order: true
|
||||
|
||||
# Default installation path (user can override during install)
|
||||
default_prefix: '%LOCALAPPDATA%\WhisperJAV'
|
||||
|
||||
# Post-installation script (runs after conda env setup)
|
||||
post_install: post_install_v1.7.5.bat
|
||||
|
||||
# Files to include in installer package
|
||||
extra_files:
|
||||
- post_install_v1.7.5.bat
|
||||
- post_install_v1.7.5.py
|
||||
- requirements_v1.7.5.txt
|
||||
- constraints_v1.7.5.txt # Version pinning for problematic packages
|
||||
- WhisperJAV_Launcher_v1.7.5.py # Script launcher (fallback if frozen exe missing)
|
||||
# NOTE: WhisperJAV-GUI.exe is created by post_install from Scripts/whisperjav-gui.exe
|
||||
# No need to include a frozen launcher - pip creates the entry point executable
|
||||
- README_INSTALLER_v1.7.5.txt
|
||||
- LICENSE.txt
|
||||
- whisperjav_icon.ico
|
||||
- whisperjav-1.7.5-py3-none-any.whl
|
||||
|
||||
# Installer metadata
|
||||
license_file: LICENSE.txt
|
||||
welcome_image: whisperjav_icon.ico # Optional: 164x314 PNG or ICO
|
||||
icon_image: whisperjav_icon.ico # Installer executable icon
|
||||
nsis_template: custom_template_v1.7.5.nsi.tmpl # Custom NSIS template with CreateShortcut
|
||||
|
||||
# Installation options
|
||||
# keep_pkgs: false # Delete package cache after install (saves space)
|
||||
# attempt_hardlinks: true # Use hardlinks to save disk space
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
@echo off
|
||||
REM ========================================
|
||||
REM WhisperJAV v1.7.5 Desktop Shortcut Creator
|
||||
REM ========================================
|
||||
REM
|
||||
REM This script creates a Windows desktop shortcut that:
|
||||
REM - Launches pythonw.exe (no console window)
|
||||
REM - Runs whisperjav.webview_gui.main module
|
||||
REM - Uses the WhisperJAV icon
|
||||
REM - Sets working directory to installation root
|
||||
REM
|
||||
REM Uses PowerShell to create .lnk file via WScript.Shell COM object
|
||||
REM
|
||||
|
||||
echo Creating WhisperJAV v1.7.5 desktop shortcut...
|
||||
|
||||
REM Get the directory where this script is located (install root)
|
||||
set SCRIPT_DIR=%~dp0
|
||||
|
||||
REM Shortcut configuration
|
||||
set SHORTCUT_NAME=WhisperJAV v1.7.5.lnk
|
||||
set TARGET_EXE=%SCRIPT_DIR%WhisperJAV-GUI.exe
|
||||
set PYTHONW=%SCRIPT_DIR%pythonw.exe
|
||||
set TARGET_ARGS=-m whisperjav.webview_gui.main
|
||||
set ICON=%SCRIPT_DIR%whisperjav_icon.ico
|
||||
|
||||
REM Verify WhisperJAV-GUI.exe exists (preferred target)
|
||||
if exist "%TARGET_EXE%" (
|
||||
echo Target: WhisperJAV-GUI.exe (standalone launcher)
|
||||
set USE_LAUNCHER_EXE=1
|
||||
) else (
|
||||
echo WARNING: WhisperJAV-GUI.exe not found, using pythonw fallback
|
||||
set USE_LAUNCHER_EXE=0
|
||||
|
||||
REM Verify pythonw.exe exists as fallback
|
||||
if not exist "%PYTHONW%" (
|
||||
echo WARNING: pythonw.exe not found at: %PYTHONW%
|
||||
echo Trying python.exe as fallback...
|
||||
set PYTHONW=%SCRIPT_DIR%python.exe
|
||||
)
|
||||
|
||||
if not exist "%PYTHONW%" (
|
||||
echo ERROR: Neither WhisperJAV-GUI.exe nor python.exe found!
|
||||
echo Cannot create shortcut.
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
REM Verify icon exists
|
||||
if not exist "%ICON%" (
|
||||
echo WARNING: Icon file not found at: %ICON%
|
||||
echo Shortcut will be created without custom icon.
|
||||
set ICON=
|
||||
)
|
||||
|
||||
REM Create shortcut using PowerShell or VBScript fallback
|
||||
echo Icon: %ICON%
|
||||
echo Working Directory: %SCRIPT_DIR%
|
||||
echo.
|
||||
|
||||
REM Try PowerShell first (modern Windows systems)
|
||||
where powershell >nul 2>nul
|
||||
if %ERRORLEVEL% EQU 0 (
|
||||
echo Using PowerShell to create shortcut...
|
||||
if "%USE_LAUNCHER_EXE%"=="1" (
|
||||
REM Preferred: Point to WhisperJAV-GUI.exe
|
||||
echo Shortcut target: %TARGET_EXE%
|
||||
echo.
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -Command "$WshShell = New-Object -ComObject WScript.Shell; $Desktop = [Environment]::GetFolderPath('Desktop'); $ShortcutPath = Join-Path $Desktop '%SHORTCUT_NAME%'; $Shortcut = $WshShell.CreateShortcut($ShortcutPath); $Shortcut.TargetPath = '%TARGET_EXE%'; $Shortcut.WorkingDirectory = '%SCRIPT_DIR%'; if ('%ICON%' -ne '') { $Shortcut.IconLocation = '%ICON%' }; $Shortcut.Description = 'WhisperJAV v1.7.5 Japanese AV Subtitle Generator'; $Shortcut.Save()"
|
||||
) else (
|
||||
REM Fallback: Point to pythonw.exe with module arguments
|
||||
echo Shortcut target: %PYTHONW% %TARGET_ARGS%
|
||||
echo.
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -Command "$WshShell = New-Object -ComObject WScript.Shell; $Desktop = [Environment]::GetFolderPath('Desktop'); $ShortcutPath = Join-Path $Desktop '%SHORTCUT_NAME%'; $Shortcut = $WshShell.CreateShortcut($ShortcutPath); $Shortcut.TargetPath = '%PYTHONW%'; $Shortcut.Arguments = '%TARGET_ARGS%'; $Shortcut.WorkingDirectory = '%SCRIPT_DIR%'; if ('%ICON%' -ne '') { $Shortcut.IconLocation = '%ICON%' }; $Shortcut.Description = 'WhisperJAV v1.7.5 Japanese AV Subtitle Generator'; $Shortcut.Save()"
|
||||
)
|
||||
) else (
|
||||
REM PowerShell not available - use VBScript fallback (works on all Windows)
|
||||
echo PowerShell not found, using VBScript fallback...
|
||||
|
||||
REM Create temporary VBScript
|
||||
set VBS_SCRIPT=%TEMP%\create_whisperjav_shortcut.vbs
|
||||
|
||||
if "%USE_LAUNCHER_EXE%"=="1" (
|
||||
REM Preferred: Point to WhisperJAV-GUI.exe
|
||||
echo Shortcut target: %TARGET_EXE%
|
||||
echo.
|
||||
(
|
||||
echo Set WshShell = CreateObject^("WScript.Shell"^)
|
||||
echo DesktopPath = WshShell.SpecialFolders^("Desktop"^)
|
||||
echo Set Shortcut = WshShell.CreateShortcut^(DesktopPath ^& "\%SHORTCUT_NAME%"^)
|
||||
echo Shortcut.TargetPath = "%TARGET_EXE%"
|
||||
echo Shortcut.WorkingDirectory = "%SCRIPT_DIR%"
|
||||
echo Shortcut.Description = "WhisperJAV v1.7.5 - Japanese AV Subtitle Generator"
|
||||
) > "%VBS_SCRIPT%"
|
||||
REM Add icon line if icon exists
|
||||
if not "%ICON%"=="" (
|
||||
echo Shortcut.IconLocation = "%ICON%" >> "%VBS_SCRIPT%"
|
||||
)
|
||||
REM Complete VBScript
|
||||
echo Shortcut.Save >> "%VBS_SCRIPT%"
|
||||
) else (
|
||||
REM Fallback: Point to pythonw.exe with module arguments
|
||||
echo Shortcut target: %PYTHONW% %TARGET_ARGS%
|
||||
echo.
|
||||
(
|
||||
echo Set WshShell = CreateObject^("WScript.Shell"^)
|
||||
echo DesktopPath = WshShell.SpecialFolders^("Desktop"^)
|
||||
echo Set Shortcut = WshShell.CreateShortcut^(DesktopPath ^& "\%SHORTCUT_NAME%"^)
|
||||
echo Shortcut.TargetPath = "%PYTHONW%"
|
||||
echo Shortcut.Arguments = "%TARGET_ARGS%"
|
||||
echo Shortcut.WorkingDirectory = "%SCRIPT_DIR%"
|
||||
echo Shortcut.Description = "WhisperJAV v1.7.5 - Japanese AV Subtitle Generator"
|
||||
) > "%VBS_SCRIPT%"
|
||||
REM Add icon line if icon exists
|
||||
if not "%ICON%"=="" (
|
||||
echo Shortcut.IconLocation = "%ICON%" >> "%VBS_SCRIPT%"
|
||||
)
|
||||
REM Complete VBScript
|
||||
echo Shortcut.Save >> "%VBS_SCRIPT%"
|
||||
)
|
||||
|
||||
REM Execute VBScript
|
||||
cscript //NoLogo "%VBS_SCRIPT%"
|
||||
|
||||
REM Cleanup
|
||||
del "%VBS_SCRIPT%" >nul 2>&1
|
||||
)
|
||||
|
||||
REM Verify shortcut was created
|
||||
set DESKTOP_PATH=%USERPROFILE%\Desktop\%SHORTCUT_NAME%
|
||||
if exist "%DESKTOP_PATH%" (
|
||||
echo.
|
||||
echo ========================================
|
||||
echo Desktop shortcut created successfully!
|
||||
echo ========================================
|
||||
echo Location: %DESKTOP_PATH%
|
||||
echo.
|
||||
exit /b 0
|
||||
) else (
|
||||
echo.
|
||||
echo ========================================
|
||||
echo WARNING: Failed to create desktop shortcut
|
||||
echo ========================================
|
||||
echo You can manually create a shortcut to:
|
||||
echo %PYTHONW% %TARGET_ARGS%
|
||||
echo.
|
||||
exit /b 1
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,70 @@
|
|||
@echo off
|
||||
REM ========================================
|
||||
REM WhisperJAV v1.7.5 Post-Install Wrapper
|
||||
REM ========================================
|
||||
REM
|
||||
REM This batch file is called by conda-constructor after the base environment
|
||||
REM is created. Constructor places this script in pkgs/ and extra_files in the
|
||||
REM parent directory (installation root).
|
||||
REM
|
||||
REM This script launches the Python post-install script that handles:
|
||||
REM - Preflight checks (disk space, network, WebView2)
|
||||
REM - PyTorch installation (CUDA or CPU)
|
||||
REM - Python dependencies installation
|
||||
REM - WhisperJAV application installation
|
||||
REM - Desktop shortcut creation
|
||||
REM
|
||||
|
||||
ECHO ========================================
|
||||
ECHO WhisperJAV v1.7.5 Post-Install
|
||||
ECHO ========================================
|
||||
ECHO.
|
||||
|
||||
REM Verify Python executable exists in parent directory
|
||||
if not exist "%~dp0..\python.exe" (
|
||||
ECHO ERROR: Cannot find python.exe
|
||||
ECHO Expected location: %~dp0..\python.exe
|
||||
ECHO Installation may be corrupted.
|
||||
ECHO.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Verify Python post-install script exists in parent directory
|
||||
if not exist "%~dp0..\post_install_v1.7.5.py" (
|
||||
ECHO ERROR: Cannot find post_install_v1.7.5.py
|
||||
ECHO Expected location: %~dp0..\post_install_v1.7.5.py
|
||||
ECHO Installation files may be missing.
|
||||
ECHO.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
ECHO Using Python: "%~dp0..\python.exe"
|
||||
ECHO Script location: "%~dp0..\post_install_v1.7.5.py"
|
||||
ECHO.
|
||||
|
||||
REM Run the Python post-install script
|
||||
REM Both python.exe and the .py script are in the parent directory (../)
|
||||
"%~dp0..\python.exe" "%~dp0..\post_install_v1.7.5.py"
|
||||
|
||||
REM Check exit code
|
||||
if %errorlevel% neq 0 (
|
||||
ECHO.
|
||||
ECHO ========================================
|
||||
ECHO ERROR: Post-install script failed!
|
||||
ECHO ========================================
|
||||
ECHO Exit code: %errorlevel%
|
||||
ECHO Check install_log_v1.7.5.txt for details
|
||||
ECHO.
|
||||
pause
|
||||
exit /b %errorlevel%
|
||||
)
|
||||
|
||||
ECHO.
|
||||
ECHO ========================================
|
||||
ECHO Post-Install completed successfully!
|
||||
ECHO ========================================
|
||||
ECHO.
|
||||
|
||||
exit /b 0
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,118 @@
|
|||
# WhisperJAV v1.7.5 Requirements
|
||||
# ================================
|
||||
#
|
||||
# This file lists all Python dependencies for WhisperJAV.
|
||||
# PyTorch is installed separately in post_install_v1.7.5.py
|
||||
# with CUDA version detection.
|
||||
#
|
||||
# IMPORTANT: Use with constraints file to prevent version conflicts:
|
||||
# pip install -c constraints_v1.7.5.txt -r requirements_v1.7.5.txt
|
||||
#
|
||||
# Installation is performed in phases to ensure dependency compatibility.
|
||||
|
||||
# ============================================================================
|
||||
# PHASE 1: Core Scientific Stack (Install FIRST)
|
||||
# ============================================================================
|
||||
# These packages MUST be installed first to establish version constraints
|
||||
# before any packages that might try to downgrade them.
|
||||
|
||||
numpy>=2.0 # NumPy 2.x (required for librosa 0.11+)
|
||||
scipy>=1.10.1 # Signal processing
|
||||
librosa>=0.11.0 # Audio analysis (NumPy 2.x compatible)
|
||||
|
||||
# ============================================================================
|
||||
# PHASE 2: Core Whisper Dependencies
|
||||
# ============================================================================
|
||||
# Git-based installs from main branches
|
||||
|
||||
openai-whisper @ git+https://github.com/openai/whisper@main
|
||||
stable-ts @ git+https://github.com/meizhong986/stable-ts-fix-setup.git@main
|
||||
|
||||
# Whisper variants
|
||||
faster-whisper>=1.1.0
|
||||
|
||||
# ============================================================================
|
||||
# PHASE 3: Audio and Signal Processing
|
||||
# ============================================================================
|
||||
|
||||
ffmpeg-python @ git+https://github.com/kkroening/ffmpeg-python.git # PyPI tarball fails
|
||||
soundfile # Audio file I/O
|
||||
auditok # Voice Activity Detection (VAD)
|
||||
silero-vad>=6.0 # Silero VAD for scene detection
|
||||
ten-vad # TEN Framework VAD for speech segmentation
|
||||
pyloudnorm # Audio normalization
|
||||
pydub # Audio manipulation
|
||||
|
||||
# ============================================================================
|
||||
# PHASE 4: Speech Enhancement Backends (Required Functionality)
|
||||
# ============================================================================
|
||||
# CRITICAL: These packages have aggressive dependencies that can conflict.
|
||||
# Install with constraints.txt to prevent numpy/datasets downgrades.
|
||||
|
||||
# ModelScope ecosystem (ZipEnhancer - lightweight SOTA)
|
||||
modelscope>=1.20
|
||||
addict # ModelScope dependency (dict with attribute access)
|
||||
datasets>=2.14.0,<4.0 # CRITICAL: 4.x breaks modelscope
|
||||
simplejson # ModelScope dependency (JSON parsing)
|
||||
sortedcontainers # ModelScope dependency (sorted collections)
|
||||
packaging # ModelScope dependency (version parsing)
|
||||
|
||||
# ClearVoice denoising (48kHz) - NumPy 2.x compatible fork
|
||||
clearvoice @ git+https://github.com/meizhong986/ClearerVoice-Studio.git#subdirectory=clearvoice
|
||||
|
||||
# BS-RoFormer vocal isolation (44.1kHz)
|
||||
bs-roformer-infer
|
||||
|
||||
# ONNX runtime for ZipEnhancer ONNX mode
|
||||
onnxruntime>=1.16.0
|
||||
|
||||
# ============================================================================
|
||||
# PHASE 5: GUI Framework (PyWebView)
|
||||
# ============================================================================
|
||||
|
||||
pywebview>=5.0.0 # Modern web-based GUI framework
|
||||
pythonnet>=3.0 # Required for WebView2 backend on Windows
|
||||
pywin32>=305; platform_system=="Windows" # Windows COM support for shortcuts
|
||||
|
||||
# ============================================================================
|
||||
# PHASE 6: HuggingFace Transformers Pipeline
|
||||
# ============================================================================
|
||||
|
||||
huggingface-hub>=0.25.0 # Hugging Face cache management (faster-whisper models)
|
||||
transformers>=4.40.0 # HuggingFace Transformers for ASR pipeline
|
||||
accelerate>=0.26.0 # Efficient model loading for Transformers
|
||||
hf_xet # Faster HuggingFace downloads (Xet Storage)
|
||||
|
||||
# ============================================================================
|
||||
# PHASE 7: Subtitle Processing and Translation
|
||||
# ============================================================================
|
||||
|
||||
pysrt # SRT file parsing
|
||||
srt # SRT utilities
|
||||
PySubtrans>=0.7.0 # AI-powered subtitle translation
|
||||
openai>=1.35.0 # Required for GPT provider in PySubtrans
|
||||
google-genai>=1.39.0 # Required for Gemini provider in PySubtrans
|
||||
|
||||
# ============================================================================
|
||||
# PHASE 8: Utilities and Configuration
|
||||
# ============================================================================
|
||||
|
||||
tqdm # Progress bars
|
||||
aiofiles # Async file I/O
|
||||
jsonschema # Config validation
|
||||
Pillow # Image processing (GUI)
|
||||
colorama # Colored console output
|
||||
requests # HTTP requests
|
||||
regex # Advanced regular expressions
|
||||
psutil>=5.9.0 # Process management (clean subprocess cleanup)
|
||||
|
||||
# Configuration system
|
||||
pydantic>=2.0,<3.0 # Data validation (used in config schemas)
|
||||
PyYAML>=6.0 # YAML config file parsing
|
||||
|
||||
# ============================================================================
|
||||
# PHASE 9: Performance Optimization
|
||||
# ============================================================================
|
||||
|
||||
numba # JIT compilation for speedups
|
||||
scikit-learn>=1.3.0 # Clustering for semantic scene detection
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
@echo off
|
||||
REM ===============================================================================
|
||||
REM WhisperJAV v1.7.5 Uninstaller
|
||||
REM ===============================================================================
|
||||
REM
|
||||
REM This script completely removes WhisperJAV v1.7.5 from your system:
|
||||
REM - Installation directory (all files)
|
||||
REM - Desktop shortcut
|
||||
REM - Optionally: User configuration
|
||||
REM - Optionally: Cached AI models (~3GB)
|
||||
REM
|
||||
REM This script should be run from the installation directory.
|
||||
REM
|
||||
|
||||
SETLOCAL EnableDelayedExpansion
|
||||
|
||||
echo ===============================================================================
|
||||
echo WhisperJAV v1.7.5 Uninstaller
|
||||
echo ===============================================================================
|
||||
echo.
|
||||
echo This will PERMANENTLY DELETE WhisperJAV v1.7.5 and all its files.
|
||||
echo.
|
||||
|
||||
REM Get the installation directory (where this script is located)
|
||||
set INSTALL_DIR=%~dp0
|
||||
set INSTALL_DIR=%INSTALL_DIR:~0,-1%
|
||||
|
||||
echo Installation Directory:
|
||||
echo %INSTALL_DIR%
|
||||
echo.
|
||||
|
||||
REM ===== Confirmation Prompt =====
|
||||
echo WARNING: This action cannot be undone!
|
||||
echo.
|
||||
set /p CONFIRM="Are you sure you want to uninstall WhisperJAV v1.7.5? (yes/no): "
|
||||
|
||||
if /i not "%CONFIRM%"=="yes" (
|
||||
echo.
|
||||
echo Uninstallation canceled.
|
||||
echo.
|
||||
pause
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
echo.
|
||||
echo ===============================================================================
|
||||
echo Starting uninstallation...
|
||||
echo ===============================================================================
|
||||
echo.
|
||||
|
||||
REM Create uninstall log
|
||||
set UNINSTALL_LOG=%TEMP%\whisperjav_uninstall_v1.7.5.txt
|
||||
echo WhisperJAV v1.7.5 Uninstallation Log > "%UNINSTALL_LOG%"
|
||||
echo Uninstall started: %DATE% %TIME% >> "%UNINSTALL_LOG%"
|
||||
echo Installation directory: %INSTALL_DIR% >> "%UNINSTALL_LOG%"
|
||||
echo. >> "%UNINSTALL_LOG%"
|
||||
|
||||
REM ===== Step 1: Remove Desktop Shortcut =====
|
||||
echo [Step 1/5] Removing desktop shortcut...
|
||||
echo [Step 1/5] Desktop shortcut >> "%UNINSTALL_LOG%"
|
||||
|
||||
set SHORTCUT_PATH=%USERPROFILE%\Desktop\WhisperJAV v1.7.5.lnk
|
||||
|
||||
if exist "%SHORTCUT_PATH%" (
|
||||
del "%SHORTCUT_PATH%"
|
||||
if exist "%SHORTCUT_PATH%" (
|
||||
echo WARNING: Could not delete shortcut
|
||||
echo WARNING: Shortcut deletion failed >> "%UNINSTALL_LOG%"
|
||||
) else (
|
||||
echo - Desktop shortcut removed
|
||||
echo - Desktop shortcut removed >> "%UNINSTALL_LOG%"
|
||||
)
|
||||
) else (
|
||||
echo - Desktop shortcut not found (already removed)
|
||||
echo - Desktop shortcut not found >> "%UNINSTALL_LOG%"
|
||||
)
|
||||
echo.
|
||||
|
||||
REM ===== Step 2: Optional - Remove User Configuration =====
|
||||
echo [Step 2/5] User configuration...
|
||||
echo [Step 2/5] User configuration >> "%UNINSTALL_LOG%"
|
||||
|
||||
set CONFIG_FILE=%INSTALL_DIR%\whisperjav_config.json
|
||||
|
||||
if exist "%CONFIG_FILE%" (
|
||||
echo.
|
||||
echo Found user configuration file: whisperjav_config.json
|
||||
echo This contains your settings and preferences.
|
||||
echo.
|
||||
set /p DEL_CONFIG=" Delete configuration file? (y/N): "
|
||||
|
||||
if /i "!DEL_CONFIG!"=="y" (
|
||||
del "%CONFIG_FILE%"
|
||||
echo - Configuration file deleted
|
||||
echo - Configuration file deleted >> "%UNINSTALL_LOG%"
|
||||
) else (
|
||||
echo - Configuration file kept
|
||||
echo - Configuration file kept (user choice) >> "%UNINSTALL_LOG%"
|
||||
)
|
||||
) else (
|
||||
echo - No configuration file found
|
||||
echo - No configuration file >> "%UNINSTALL_LOG%"
|
||||
)
|
||||
echo.
|
||||
|
||||
REM ===== Step 3: Optional - Remove Cached Models =====
|
||||
echo [Step 3/5] Cached AI models...
|
||||
echo [Step 3/5] Cached AI models >> "%UNINSTALL_LOG%"
|
||||
|
||||
set MODEL_CACHE=%USERPROFILE%\.cache\whisper
|
||||
|
||||
if exist "%MODEL_CACHE%" (
|
||||
REM Calculate cache size
|
||||
for /f "tokens=3" %%a in ('dir "%MODEL_CACHE%" /s /-c 2^>nul ^| findstr /c:"File(s)"') do set MODEL_SIZE=%%a
|
||||
set /a MODEL_SIZE_MB=!MODEL_SIZE! / 1048576
|
||||
|
||||
echo.
|
||||
echo Found cached AI models in: %MODEL_CACHE%
|
||||
echo Cache size: ~!MODEL_SIZE_MB! MB
|
||||
echo.
|
||||
echo Deleting these will free up disk space, but models will need
|
||||
echo to be re-downloaded if you reinstall WhisperJAV (~3GB, 5-10 min).
|
||||
echo.
|
||||
set /p DEL_MODELS=" Delete cached models? (y/N): "
|
||||
|
||||
if /i "!DEL_MODELS!"=="y" (
|
||||
echo - Deleting model cache... (this may take a minute)
|
||||
rmdir /s /q "%MODEL_CACHE%"
|
||||
if exist "%MODEL_CACHE%" (
|
||||
echo WARNING: Could not fully delete model cache
|
||||
echo WARNING: Model cache deletion failed >> "%UNINSTALL_LOG%"
|
||||
) else (
|
||||
echo - Model cache deleted (~!MODEL_SIZE_MB! MB freed)
|
||||
echo - Model cache deleted (~!MODEL_SIZE_MB! MB) >> "%UNINSTALL_LOG%"
|
||||
)
|
||||
) else (
|
||||
echo - Model cache kept
|
||||
echo - Model cache kept (user choice) >> "%UNINSTALL_LOG%"
|
||||
)
|
||||
) else (
|
||||
echo - No model cache found
|
||||
echo - No model cache found >> "%UNINSTALL_LOG%"
|
||||
)
|
||||
echo.
|
||||
|
||||
REM ===== Step 4: Remove Installation Directory =====
|
||||
echo [Step 4/5] Removing installation directory...
|
||||
echo [Step 4/5] Installation directory >> "%UNINSTALL_LOG%"
|
||||
|
||||
REM We need to delete the installation directory, but this script is running from it!
|
||||
REM Strategy: Create a temporary cleanup script that runs after this script exits
|
||||
|
||||
set CLEANUP_SCRIPT=%TEMP%\whisperjav_cleanup_v1.7.5.bat
|
||||
|
||||
echo @echo off > "%CLEANUP_SCRIPT%"
|
||||
echo REM Temporary cleanup script for WhisperJAV v1.7.5 >> "%CLEANUP_SCRIPT%"
|
||||
echo echo Removing installation directory... >> "%CLEANUP_SCRIPT%"
|
||||
echo timeout /t 2 /nobreak ^>nul >> "%CLEANUP_SCRIPT%"
|
||||
echo rmdir /s /q "%INSTALL_DIR%" >> "%CLEANUP_SCRIPT%"
|
||||
echo if exist "%INSTALL_DIR%" ( >> "%CLEANUP_SCRIPT%"
|
||||
echo echo ERROR: Could not delete installation directory >> "%CLEANUP_SCRIPT%"
|
||||
echo echo %INSTALL_DIR% >> "%CLEANUP_SCRIPT%"
|
||||
echo echo. >> "%CLEANUP_SCRIPT%"
|
||||
echo echo You may need to delete it manually. >> "%CLEANUP_SCRIPT%"
|
||||
echo pause >> "%CLEANUP_SCRIPT%"
|
||||
echo ) else ( >> "%CLEANUP_SCRIPT%"
|
||||
echo echo Installation directory removed successfully! >> "%CLEANUP_SCRIPT%"
|
||||
echo echo. >> "%CLEANUP_SCRIPT%"
|
||||
echo echo WhisperJAV v1.7.5 has been uninstalled. >> "%CLEANUP_SCRIPT%"
|
||||
echo timeout /t 3 /nobreak ^>nul >> "%CLEANUP_SCRIPT%"
|
||||
echo ) >> "%CLEANUP_SCRIPT%"
|
||||
echo del "%%~f0" >> "%CLEANUP_SCRIPT%"
|
||||
|
||||
echo - Cleanup script created: %CLEANUP_SCRIPT%
|
||||
echo - Cleanup script created >> "%UNINSTALL_LOG%"
|
||||
echo.
|
||||
|
||||
REM ===== Step 5: Final Summary =====
|
||||
echo [Step 5/5] Uninstallation summary
|
||||
echo [Step 5/5] Uninstallation summary >> "%UNINSTALL_LOG%"
|
||||
|
||||
echo.
|
||||
echo ===============================================================================
|
||||
echo Uninstallation Complete!
|
||||
echo ===============================================================================
|
||||
echo.
|
||||
echo The following items have been removed:
|
||||
echo - Desktop shortcut: WhisperJAV v1.7.5.lnk
|
||||
if /i "%DEL_CONFIG%"=="y" echo - User configuration file
|
||||
if /i "%DEL_MODELS%"=="y" echo - Cached AI models (~!MODEL_SIZE_MB! MB)
|
||||
echo.
|
||||
echo The installation directory will be deleted when you close this window:
|
||||
echo %INSTALL_DIR%
|
||||
echo.
|
||||
echo Uninstall log saved to:
|
||||
echo %UNINSTALL_LOG%
|
||||
echo.
|
||||
echo ===============================================================================
|
||||
|
||||
REM Log completion
|
||||
echo. >> "%UNINSTALL_LOG%"
|
||||
echo Uninstallation completed: %DATE% %TIME% >> "%UNINSTALL_LOG%"
|
||||
|
||||
REM Launch cleanup script and exit
|
||||
echo.
|
||||
echo Press any key to finish uninstallation and close this window...
|
||||
pause >nul
|
||||
|
||||
start "" "%CLEANUP_SCRIPT%"
|
||||
exit
|
||||
|
|
@ -0,0 +1,506 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
WhisperJAV v1.7.5 Installer Validation Script
|
||||
==============================================
|
||||
|
||||
This script performs comprehensive pre-build validation checks to ensure
|
||||
all required files are present and correctly configured before running
|
||||
the conda-constructor build.
|
||||
|
||||
Validation checks:
|
||||
1. All required files exist
|
||||
2. Version consistency across files
|
||||
3. Module paths are correct (no old GUI references)
|
||||
4. Requirements.txt encoding is valid (UTF-8)
|
||||
5. Icon file exists and is valid
|
||||
6. README and LICENSE present
|
||||
7. YAML syntax is valid
|
||||
|
||||
Run this before building the installer to catch issues early.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
# Configure UTF-8 encoding for Windows console
|
||||
if sys.platform == 'win32':
|
||||
try:
|
||||
import io
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
|
||||
except Exception:
|
||||
pass # Fall back to default encoding if reconfiguration fails
|
||||
|
||||
# ANSI color codes for better readability
|
||||
GREEN = '\033[92m'
|
||||
RED = '\033[91m'
|
||||
YELLOW = '\033[93m'
|
||||
BLUE = '\033[94m'
|
||||
RESET = '\033[0m'
|
||||
|
||||
# Expected version
|
||||
EXPECTED_VERSION = "1.7.5"
|
||||
|
||||
# Required files for v1.7.5 installer
|
||||
REQUIRED_FILES = [
|
||||
"construct_v1.7.5.yaml",
|
||||
"post_install_v1.7.5.bat",
|
||||
"post_install_v1.7.5.py",
|
||||
"requirements_v1.7.5.txt",
|
||||
"constraints_v1.7.5.txt", # Version pinning for problematic packages
|
||||
"WhisperJAV_Launcher_v1.7.5.py",
|
||||
"README_INSTALLER_v1.7.5.txt",
|
||||
"LICENSE.txt",
|
||||
"whisperjav_icon.ico",
|
||||
]
|
||||
|
||||
# Optional files (warnings only, not errors)
|
||||
# NOTE: WhisperJAV-GUI.exe is created by post_install from Scripts/whisperjav-gui.exe
|
||||
OPTIONAL_FILES = []
|
||||
|
||||
# Wheel file pattern - matches version with PEP 440 normalization
|
||||
# e.g., "1.7.0-beta" becomes "1.7.0b0" in wheel filename
|
||||
WHEEL_PATTERN = "whisperjav-1.7.5*.whl"
|
||||
WHEEL_PATTERN_FALLBACK = "whisperjav-*.whl"
|
||||
|
||||
# Expected module path (no old Tkinter GUI references)
|
||||
CORRECT_MODULE_PATH = "whisperjav.webview_gui.main"
|
||||
OLD_MODULE_PATHS = [
|
||||
"whisperjav.gui.whisperjav_gui",
|
||||
"whisperjav.gui",
|
||||
]
|
||||
|
||||
|
||||
def print_header(title):
|
||||
"""Print a formatted section header"""
|
||||
print()
|
||||
print("=" * 80)
|
||||
print(f" {title}")
|
||||
print("=" * 80)
|
||||
|
||||
|
||||
def print_check(description, passed, details=""):
|
||||
"""Print a check result with formatting"""
|
||||
status = f"{GREEN}✓ PASS{RESET}" if passed else f"{RED}✗ FAIL{RESET}"
|
||||
print(f" [{status}] {description}")
|
||||
if details:
|
||||
print(f" {details}")
|
||||
|
||||
|
||||
def check_file_exists(filepath):
|
||||
"""Check if a file exists"""
|
||||
return Path(filepath).exists()
|
||||
|
||||
|
||||
def validate_required_files():
|
||||
"""Check that all required files exist"""
|
||||
print_header("Phase 1: Required Files Check")
|
||||
|
||||
all_passed = True
|
||||
for filename in REQUIRED_FILES:
|
||||
exists = check_file_exists(filename)
|
||||
print_check(filename, exists)
|
||||
if not exists:
|
||||
all_passed = False
|
||||
|
||||
# Check for wheel file using glob pattern (try version-specific first)
|
||||
import glob
|
||||
wheels = glob.glob(WHEEL_PATTERN)
|
||||
if not wheels:
|
||||
# Fall back to any wheel file
|
||||
wheels = glob.glob(WHEEL_PATTERN_FALLBACK)
|
||||
|
||||
if wheels:
|
||||
# Prefer the one matching our version
|
||||
wheel_file = wheels[0]
|
||||
for w in wheels:
|
||||
if EXPECTED_VERSION.replace("-", "") in w or EXPECTED_VERSION in w:
|
||||
wheel_file = w
|
||||
break
|
||||
print_check(f"Wheel file", True, f"Found: {wheel_file}")
|
||||
else:
|
||||
print_check("Wheel file", False, "No wheel file found matching pattern")
|
||||
all_passed = False
|
||||
|
||||
return all_passed
|
||||
|
||||
|
||||
def validate_version_consistency():
|
||||
"""Check version consistency across files"""
|
||||
print_header("Phase 2: Version Consistency Check")
|
||||
|
||||
all_passed = True
|
||||
# Version regex supports both formats:
|
||||
# - PEP 440: 1.7.0b0, 1.7.0a1, 1.7.0rc1 (no hyphen)
|
||||
# - Display: 1.7.0-beta, 1.7.0-alpha (with hyphen)
|
||||
version_pattern = r"(\d+\.\d+\.\d+(?:(?:a|b|rc)\d+)?(?:-[a-zA-Z0-9]+)?)"
|
||||
version_files = [
|
||||
("construct_v1.7.5.yaml", rf"version:\s*['\"]?{version_pattern}"),
|
||||
("post_install_v1.7.5.py", rf"WhisperJAV v{version_pattern}"),
|
||||
("WhisperJAV_Launcher_v1.7.5.py", rf"v{version_pattern}"),
|
||||
("README_INSTALLER_v1.7.5.txt", rf"WhisperJAV v{version_pattern}"),
|
||||
]
|
||||
|
||||
for filename, pattern in version_files:
|
||||
if not check_file_exists(filename):
|
||||
print_check(f"Version in {filename}", False, "File not found")
|
||||
all_passed = False
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
match = re.search(pattern, content)
|
||||
if match:
|
||||
version = match.group(1)
|
||||
passed = (version == EXPECTED_VERSION)
|
||||
details = f"Found: {version}" if not passed else ""
|
||||
print_check(f"Version in {filename}: {version}", passed, details)
|
||||
if not passed:
|
||||
all_passed = False
|
||||
else:
|
||||
print_check(f"Version in {filename}", False, "Version pattern not found")
|
||||
all_passed = False
|
||||
|
||||
except Exception as e:
|
||||
print_check(f"Version in {filename}", False, f"Error: {e}")
|
||||
all_passed = False
|
||||
|
||||
return all_passed
|
||||
|
||||
|
||||
def validate_module_paths():
|
||||
"""Check that module paths reference the new PyWebView GUI, not old Tkinter GUI"""
|
||||
print_header("Phase 3: Module Path Validation")
|
||||
|
||||
all_passed = True
|
||||
# Files that contain GUI module references:
|
||||
# - WhisperJAV_Launcher_v1.7.5.py: Script launcher
|
||||
# - custom_template_v1.7.5.nsi.tmpl: NSIS shortcut creation (pythonw fallback)
|
||||
# NOTE: post_install_v1.7.5.py removed - it only handles pip installation,
|
||||
# shortcuts are now handled by NSIS
|
||||
files_to_check = [
|
||||
"WhisperJAV_Launcher_v1.7.5.py",
|
||||
"custom_template_v1.7.5.nsi.tmpl",
|
||||
]
|
||||
|
||||
for filename in files_to_check:
|
||||
if not check_file_exists(filename):
|
||||
print_check(f"Module paths in {filename}", False, "File not found")
|
||||
all_passed = False
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Check for correct module path
|
||||
has_correct_path = CORRECT_MODULE_PATH in content
|
||||
|
||||
# Check for old module paths (should not exist)
|
||||
has_old_paths = any(old_path in content for old_path in OLD_MODULE_PATHS)
|
||||
|
||||
passed = has_correct_path and not has_old_paths
|
||||
|
||||
if passed:
|
||||
print_check(f"Module paths in {filename}", True)
|
||||
else:
|
||||
if not has_correct_path:
|
||||
print_check(
|
||||
f"Module paths in {filename}",
|
||||
False,
|
||||
f"Missing correct path: {CORRECT_MODULE_PATH}"
|
||||
)
|
||||
if has_old_paths:
|
||||
print_check(
|
||||
f"Module paths in {filename}",
|
||||
False,
|
||||
"Contains old Tkinter GUI references!"
|
||||
)
|
||||
all_passed = False
|
||||
|
||||
except Exception as e:
|
||||
print_check(f"Module paths in {filename}", False, f"Error: {e}")
|
||||
all_passed = False
|
||||
|
||||
return all_passed
|
||||
|
||||
|
||||
def validate_requirements_encoding():
|
||||
"""Check that requirements.txt is valid UTF-8 without encoding issues"""
|
||||
print_header("Phase 4: Requirements File Encoding Check")
|
||||
|
||||
filename = "requirements_v1.7.5.txt"
|
||||
|
||||
if not check_file_exists(filename):
|
||||
print_check(f"{filename} encoding", False, "File not found")
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Check for weird spacing (sign of encoding issues)
|
||||
if ' ' in content[:100]: # Check first 100 chars for double spaces
|
||||
print_check(
|
||||
f"{filename} encoding",
|
||||
False,
|
||||
"Suspicious spacing detected - possible encoding issue"
|
||||
)
|
||||
return False
|
||||
|
||||
# Try to parse as requirements
|
||||
lines = [line.strip() for line in content.split('\n') if line.strip() and not line.startswith('#')]
|
||||
|
||||
if len(lines) < 5:
|
||||
print_check(
|
||||
f"{filename} encoding",
|
||||
False,
|
||||
f"Too few packages ({len(lines)}) - file may be corrupted"
|
||||
)
|
||||
return False
|
||||
|
||||
print_check(f"{filename} encoding", True, f"{len(lines)} packages found")
|
||||
return True
|
||||
|
||||
except UnicodeDecodeError as e:
|
||||
print_check(f"{filename} encoding", False, f"UTF-8 decode error: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print_check(f"{filename} encoding", False, f"Error: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def validate_optional_files():
|
||||
"""Check optional files (warnings only, not errors)"""
|
||||
print_header("Phase 5: Optional Files Check")
|
||||
|
||||
# Optional files don't cause validation failure
|
||||
for filename, description in OPTIONAL_FILES:
|
||||
exists = check_file_exists(filename)
|
||||
if exists:
|
||||
size = Path(filename).stat().st_size
|
||||
size_kb = size // 1024
|
||||
print_check(f"{filename}", True, f"{size_kb} KB - {description}")
|
||||
else:
|
||||
# Print as warning (yellow), not error
|
||||
print(f" [{YELLOW}! WARN{RESET}] {filename}")
|
||||
print(f" Not found: {description}")
|
||||
print(f" (Will be created during build)")
|
||||
|
||||
# Always return True - optional files don't fail validation
|
||||
return True
|
||||
|
||||
|
||||
def validate_assets():
|
||||
"""Check that icon and other assets exist and are valid"""
|
||||
print_header("Phase 6: Asset File Validation")
|
||||
|
||||
all_passed = True
|
||||
|
||||
# Check icon file
|
||||
icon_file = "whisperjav_icon.ico"
|
||||
if check_file_exists(icon_file):
|
||||
size = Path(icon_file).stat().st_size
|
||||
if size > 1000: # Should be at least 1KB for a valid icon
|
||||
print_check(f"{icon_file}", True, f"{size} bytes")
|
||||
else:
|
||||
print_check(f"{icon_file}", False, f"File too small ({size} bytes)")
|
||||
all_passed = False
|
||||
else:
|
||||
print_check(f"{icon_file}", False, "File not found")
|
||||
all_passed = False
|
||||
|
||||
# Check LICENSE
|
||||
license_file = "LICENSE.txt"
|
||||
if check_file_exists(license_file):
|
||||
size = Path(license_file).stat().st_size
|
||||
if size > 100: # Should be at least 100 bytes
|
||||
print_check(f"{license_file}", True, f"{size} bytes")
|
||||
else:
|
||||
print_check(f"{license_file}", False, f"File too small ({size} bytes)")
|
||||
all_passed = False
|
||||
else:
|
||||
print_check(f"{license_file}", False, "File not found")
|
||||
all_passed = False
|
||||
|
||||
return all_passed
|
||||
|
||||
|
||||
def validate_constraints_file():
|
||||
"""Check that constraints file contains critical version pins"""
|
||||
print_header("Phase 7: Constraints File Validation")
|
||||
|
||||
filename = "constraints_v1.7.5.txt"
|
||||
|
||||
if not check_file_exists(filename):
|
||||
print_check(f"{filename}", False, "File not found")
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Check for critical version constraints
|
||||
critical_constraints = [
|
||||
("numpy>=2.0", "NumPy 2.x for librosa compatibility"),
|
||||
("scipy>=1.10.1", "SciPy for audio processing"),
|
||||
("librosa>=0.11.0", "Librosa NumPy 2.x support"),
|
||||
("datasets>=2.14.0,<4.0", "Datasets cap for modelscope"),
|
||||
("pydantic>=2.0,<3.0", "Pydantic version cap"),
|
||||
]
|
||||
|
||||
all_found = True
|
||||
for constraint, description in critical_constraints:
|
||||
if constraint in content:
|
||||
print_check(f"{constraint}", True, description)
|
||||
else:
|
||||
print_check(f"{constraint}", False, f"Missing: {description}")
|
||||
all_found = False
|
||||
|
||||
return all_found
|
||||
|
||||
except Exception as e:
|
||||
print_check(f"{filename}", False, f"Error: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def validate_yaml_syntax():
|
||||
"""Basic YAML syntax validation for construct file"""
|
||||
print_header("Phase 8: YAML Syntax Validation")
|
||||
|
||||
filename = "construct_v1.7.5.yaml"
|
||||
|
||||
if not check_file_exists(filename):
|
||||
print_check(f"{filename} syntax", False, "File not found")
|
||||
return False
|
||||
|
||||
try:
|
||||
import yaml
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
data = yaml.safe_load(f)
|
||||
|
||||
# Check required keys
|
||||
required_keys = ['name', 'version', 'channels', 'specs', 'post_install', 'extra_files']
|
||||
missing_keys = [key for key in required_keys if key not in data]
|
||||
|
||||
if missing_keys:
|
||||
print_check(
|
||||
f"{filename} syntax",
|
||||
False,
|
||||
f"Missing required keys: {', '.join(missing_keys)}"
|
||||
)
|
||||
return False
|
||||
|
||||
# Verify version matches
|
||||
if data['version'] != EXPECTED_VERSION:
|
||||
print_check(
|
||||
f"{filename} syntax",
|
||||
False,
|
||||
f"Version mismatch: {data['version']} != {EXPECTED_VERSION}"
|
||||
)
|
||||
return False
|
||||
|
||||
# Check extra_files exist (excluding optional files that are built during build process)
|
||||
optional_filenames = [f for f, _ in OPTIONAL_FILES]
|
||||
missing_files = [
|
||||
f for f in data['extra_files']
|
||||
if not check_file_exists(f) and f not in optional_filenames
|
||||
]
|
||||
if missing_files:
|
||||
print_check(
|
||||
f"{filename} syntax",
|
||||
False,
|
||||
f"Extra files missing: {', '.join(missing_files)}"
|
||||
)
|
||||
return False
|
||||
|
||||
# Warn about optional files listed in extra_files that don't exist yet
|
||||
optional_missing = [
|
||||
f for f in data['extra_files']
|
||||
if not check_file_exists(f) and f in optional_filenames
|
||||
]
|
||||
if optional_missing:
|
||||
print(f" [{YELLOW}! NOTE{RESET}] Optional files in extra_files (built during build):")
|
||||
for f in optional_missing:
|
||||
print(f" - {f}")
|
||||
|
||||
print_check(f"{filename} syntax", True, "YAML structure valid")
|
||||
return True
|
||||
|
||||
except ImportError:
|
||||
print_check(f"{filename} syntax", False, "PyYAML not installed (skipping)")
|
||||
return True # Non-fatal if yaml module not available
|
||||
except Exception as e:
|
||||
print_check(f"{filename} syntax", False, f"Error: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Run all validation checks"""
|
||||
print()
|
||||
print("=" * 80)
|
||||
print(" " * 20 + "WhisperJAV v1.7.5 Installer Validation")
|
||||
print("=" * 80)
|
||||
print()
|
||||
print("Running pre-build validation checks...")
|
||||
|
||||
# Run all validation phases
|
||||
results = []
|
||||
results.append(("Required Files", validate_required_files()))
|
||||
results.append(("Version Consistency", validate_version_consistency()))
|
||||
results.append(("Module Paths", validate_module_paths()))
|
||||
results.append(("Requirements Encoding", validate_requirements_encoding()))
|
||||
results.append(("Optional Files", validate_optional_files()))
|
||||
results.append(("Asset Files", validate_assets()))
|
||||
results.append(("Constraints File", validate_constraints_file()))
|
||||
results.append(("YAML Syntax", validate_yaml_syntax()))
|
||||
|
||||
# Print summary
|
||||
print()
|
||||
print("=" * 80)
|
||||
print(" VALIDATION SUMMARY")
|
||||
print("=" * 80)
|
||||
|
||||
all_passed = True
|
||||
for phase, passed in results:
|
||||
status = f"{GREEN}✓ PASS{RESET}" if passed else f"{RED}✗ FAIL{RESET}"
|
||||
print(f" {phase:.<50} [{status}]")
|
||||
if not passed:
|
||||
all_passed = False
|
||||
|
||||
print("=" * 80)
|
||||
|
||||
if all_passed:
|
||||
print()
|
||||
print(f"{GREEN}✓ All validation checks passed!{RESET}")
|
||||
print()
|
||||
print("The installer is ready to build.")
|
||||
print("Run: build_installer_v1.7.5.bat")
|
||||
print()
|
||||
return 0
|
||||
else:
|
||||
print()
|
||||
print(f"{RED}✗ Validation failed!{RESET}")
|
||||
print()
|
||||
print("Please fix the issues above before building the installer.")
|
||||
print()
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
sys.exit(main())
|
||||
except KeyboardInterrupt:
|
||||
print()
|
||||
print("Validation interrupted by user.")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print()
|
||||
print(f"FATAL ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#!/bin/bash
|
||||
# ==============================================================================
|
||||
# WhisperJAV Linux Installation Script
|
||||
# WhisperJAV Linux Installation Script (v1.7.5 Compatible)
|
||||
# ==============================================================================
|
||||
#
|
||||
# This script handles the staged installation of WhisperJAV on Linux systems,
|
||||
|
|
@ -11,20 +11,39 @@
|
|||
# ./install_linux.sh
|
||||
#
|
||||
# Options:
|
||||
# --cpu-only Install CPU-only PyTorch (no CUDA)
|
||||
# --no-speech-enhancement Skip speech enhancement packages (clearvoice, etc.)
|
||||
# --minimal Install minimal version (no speech enhancement, no TEN VAD)
|
||||
# --cpu-only Install CPU-only PyTorch (no CUDA)
|
||||
# --cuda121 Install PyTorch for CUDA 12.1
|
||||
# --cuda124 Install PyTorch for CUDA 12.4
|
||||
# --cuda126 Install PyTorch for CUDA 12.6
|
||||
# --cuda128 Install PyTorch for CUDA 12.8 (default for driver 570+)
|
||||
# --no-speech-enhancement Skip speech enhancement packages
|
||||
# --minimal Install minimal version (no speech enhancement, no TEN VAD)
|
||||
# --dev Install in development/editable mode
|
||||
#
|
||||
# ==============================================================================
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Initialize log file
|
||||
INSTALL_LOG="$(dirname "$0")/install_log_linux.txt"
|
||||
echo "" > "$INSTALL_LOG"
|
||||
|
||||
# Logging function
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$INSTALL_LOG"
|
||||
}
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log "============================================================"
|
||||
log " WhisperJAV Linux Installation Script"
|
||||
log " Started: $(date)"
|
||||
log "============================================================"
|
||||
|
||||
echo "============================================================"
|
||||
echo " WhisperJAV Linux Installation Script"
|
||||
echo "============================================================"
|
||||
|
|
@ -56,19 +75,49 @@ if ! command -v ffmpeg &> /dev/null; then
|
|||
echo " Debian/Ubuntu: sudo apt-get install ffmpeg"
|
||||
echo " Fedora/RHEL: sudo dnf install ffmpeg"
|
||||
echo " Arch Linux: sudo pacman -S ffmpeg"
|
||||
log "ERROR: FFmpeg not found"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}FFmpeg found: $(ffmpeg -version | head -n1)${NC}"
|
||||
log "FFmpeg found"
|
||||
|
||||
# Check for Git
|
||||
if ! command -v git &> /dev/null; then
|
||||
echo -e "${RED}Error: Git is not installed.${NC}"
|
||||
echo "Please install Git first:"
|
||||
echo " Debian/Ubuntu: sudo apt-get install git"
|
||||
echo " Fedora/RHEL: sudo dnf install git"
|
||||
echo " Arch Linux: sudo pacman -S git"
|
||||
log "ERROR: Git not found"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}Git found${NC}"
|
||||
log "Git found"
|
||||
|
||||
# Parse arguments
|
||||
CPU_ONLY=false
|
||||
CUDA_VERSION="auto"
|
||||
NO_SPEECH_ENHANCEMENT=false
|
||||
MINIMAL=false
|
||||
DEV_MODE=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--cpu-only)
|
||||
CPU_ONLY=true
|
||||
CUDA_VERSION="cpu"
|
||||
;;
|
||||
--cuda121)
|
||||
CUDA_VERSION="cuda121"
|
||||
;;
|
||||
--cuda124)
|
||||
CUDA_VERSION="cuda124"
|
||||
;;
|
||||
--cuda126)
|
||||
CUDA_VERSION="cuda126"
|
||||
;;
|
||||
--cuda128)
|
||||
CUDA_VERSION="cuda128"
|
||||
;;
|
||||
--no-speech-enhancement)
|
||||
NO_SPEECH_ENHANCEMENT=true
|
||||
|
|
@ -77,129 +126,412 @@ for arg in "$@"; do
|
|||
MINIMAL=true
|
||||
NO_SPEECH_ENHANCEMENT=true
|
||||
;;
|
||||
--dev)
|
||||
DEV_MODE=true
|
||||
;;
|
||||
--help|-h)
|
||||
echo ""
|
||||
echo "WhisperJAV Linux Installation Script"
|
||||
echo ""
|
||||
echo "Usage: ./install_linux.sh [options]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --cpu-only Install CPU-only PyTorch (no CUDA)"
|
||||
echo " --cuda121 Install PyTorch for CUDA 12.1"
|
||||
echo " --cuda124 Install PyTorch for CUDA 12.4"
|
||||
echo " --cuda126 Install PyTorch for CUDA 12.6"
|
||||
echo " --cuda128 Install PyTorch for CUDA 12.8 (default for driver 570+)"
|
||||
echo " --no-speech-enhancement Skip speech enhancement packages"
|
||||
echo " --minimal Minimal install (no speech enhancement)"
|
||||
echo " --dev Install in development/editable mode"
|
||||
echo " --help, -h Show this help message"
|
||||
echo ""
|
||||
echo "The script will auto-detect your GPU and select the appropriate"
|
||||
echo "CUDA version. Use --cuda*** flags to override."
|
||||
echo ""
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Check Python version
|
||||
echo -e "${YELLOW}Checking Python version...${NC}"
|
||||
log "Checking Python version..."
|
||||
PYTHON_VERSION=$(python3 --version 2>&1 | awk '{print $2}')
|
||||
PYTHON_MAJOR=$(echo $PYTHON_VERSION | cut -d. -f1)
|
||||
PYTHON_MINOR=$(echo $PYTHON_VERSION | cut -d. -f2)
|
||||
|
||||
if [ "$PYTHON_MAJOR" -lt 3 ] || ([ "$PYTHON_MAJOR" -eq 3 ] && [ "$PYTHON_MINOR" -lt 9 ]); then
|
||||
echo -e "${RED}Error: Python 3.9 or higher is required. Found: $PYTHON_VERSION${NC}"
|
||||
log "ERROR: Python 3.9+ required. Found: $PYTHON_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$PYTHON_MINOR" -gt 12 ]; then
|
||||
echo -e "${YELLOW}Warning: Python 3.13+ may have compatibility issues with openai-whisper${NC}"
|
||||
log "WARNING: Python 3.13+ detected"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Python $PYTHON_VERSION detected${NC}"
|
||||
log "Python $PYTHON_VERSION detected"
|
||||
echo ""
|
||||
|
||||
# Auto-detect NVIDIA GPU
|
||||
# Auto-detect NVIDIA GPU and driver version
|
||||
if [ "$CPU_ONLY" = false ]; then
|
||||
echo -e "${YELLOW}Checking for NVIDIA GPU...${NC}"
|
||||
log "Checking for NVIDIA GPU..."
|
||||
if command -v nvidia-smi &> /dev/null; then
|
||||
GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -n1)
|
||||
DRIVER_VERSION=$(nvidia-smi --query-gpu=driver_version --format=csv,noheader 2>/dev/null | head -n1)
|
||||
if [ -n "$GPU_NAME" ]; then
|
||||
echo -e "${GREEN}NVIDIA GPU detected: $GPU_NAME${NC}"
|
||||
echo -e "${GREEN}Driver version: $DRIVER_VERSION${NC}"
|
||||
log "NVIDIA GPU detected: $GPU_NAME"
|
||||
log "Driver version: $DRIVER_VERSION"
|
||||
|
||||
# Auto-select CUDA version based on driver if not specified
|
||||
if [ "$CUDA_VERSION" = "auto" ]; then
|
||||
DRIVER_MAJOR=$(echo "$DRIVER_VERSION" | cut -d. -f1)
|
||||
if [ "$DRIVER_MAJOR" -ge 570 ] 2>/dev/null; then
|
||||
CUDA_VERSION="cuda128"
|
||||
echo -e "${GREEN}Auto-selecting CUDA 12.8 based on driver $DRIVER_VERSION${NC}"
|
||||
log "Auto-selecting CUDA 12.8"
|
||||
elif [ "$DRIVER_MAJOR" -ge 560 ] 2>/dev/null; then
|
||||
CUDA_VERSION="cuda126"
|
||||
echo -e "${GREEN}Auto-selecting CUDA 12.6 based on driver $DRIVER_VERSION${NC}"
|
||||
log "Auto-selecting CUDA 12.6"
|
||||
elif [ "$DRIVER_MAJOR" -ge 551 ] 2>/dev/null; then
|
||||
CUDA_VERSION="cuda124"
|
||||
echo -e "${GREEN}Auto-selecting CUDA 12.4 based on driver $DRIVER_VERSION${NC}"
|
||||
log "Auto-selecting CUDA 12.4"
|
||||
else
|
||||
CUDA_VERSION="cuda121"
|
||||
echo -e "${GREEN}Using CUDA 12.1 for driver $DRIVER_VERSION${NC}"
|
||||
log "Using CUDA 12.1"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}nvidia-smi found but no GPU detected${NC}"
|
||||
log "WARNING: nvidia-smi found but no GPU detected"
|
||||
CPU_ONLY=true
|
||||
CUDA_VERSION="cpu"
|
||||
fi
|
||||
elif [ -f "/proc/driver/nvidia/version" ]; then
|
||||
echo -e "${GREEN}NVIDIA driver detected${NC}"
|
||||
log "NVIDIA driver detected"
|
||||
if [ "$CUDA_VERSION" = "auto" ]; then
|
||||
CUDA_VERSION="cuda121"
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}No NVIDIA GPU detected!${NC}"
|
||||
echo -e "${YELLOW}Switching to CPU-only installation automatically.${NC}"
|
||||
log "No NVIDIA GPU - switching to CPU-only"
|
||||
CPU_ONLY=true
|
||||
CUDA_VERSION="cpu"
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Upgrade pip
|
||||
echo -e "${YELLOW}Step 1/6: Upgrading pip...${NC}"
|
||||
python3 -m pip install --upgrade pip
|
||||
echo ""
|
||||
|
||||
# Install PyTorch first (before anything else)
|
||||
echo -e "${YELLOW}Step 2/6: Installing PyTorch...${NC}"
|
||||
# Display configuration
|
||||
echo "============================================================"
|
||||
echo " Installation Configuration"
|
||||
echo "============================================================"
|
||||
if [ "$CPU_ONLY" = true ]; then
|
||||
echo "Installing CPU-only PyTorch..."
|
||||
pip3 install torch torchaudio --index-url https://download.pytorch.org/whl/cpu
|
||||
echo " PyTorch: CPU-only"
|
||||
log "Configuration: PyTorch=CPU-only"
|
||||
else
|
||||
echo "Installing PyTorch with CUDA support..."
|
||||
pip3 install torch torchaudio --index-url https://download.pytorch.org/whl/cu121
|
||||
echo " PyTorch: $CUDA_VERSION"
|
||||
log "Configuration: PyTorch=$CUDA_VERSION"
|
||||
fi
|
||||
if [ "$NO_SPEECH_ENHANCEMENT" = true ]; then
|
||||
echo " Speech Enhancement: Disabled"
|
||||
log "Configuration: Speech Enhancement=Disabled"
|
||||
else
|
||||
echo " Speech Enhancement: Enabled"
|
||||
log "Configuration: Speech Enhancement=Enabled"
|
||||
fi
|
||||
if [ "$DEV_MODE" = true ]; then
|
||||
echo " Mode: Development (editable)"
|
||||
log "Configuration: Mode=Development"
|
||||
else
|
||||
echo " Mode: Standard"
|
||||
log "Configuration: Mode=Standard"
|
||||
fi
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
|
||||
# Install core dependencies that don't have conflicts
|
||||
echo -e "${YELLOW}Step 3/6: Installing core dependencies...${NC}"
|
||||
pip3 install numpy>=2.0 scipy>=1.10.1 librosa>=0.11.0
|
||||
pip3 install soundfile pydub tqdm colorama requests regex
|
||||
# ==============================================================================
|
||||
# Phase 1: Upgrade pip
|
||||
# ==============================================================================
|
||||
log ""
|
||||
log "============================================================"
|
||||
log " Phase 1: Upgrade pip"
|
||||
log "============================================================"
|
||||
|
||||
echo -e "${YELLOW}Step 1/7: Upgrading pip...${NC}"
|
||||
python3 -m pip install --upgrade pip
|
||||
log "pip upgraded"
|
||||
echo ""
|
||||
|
||||
# ==============================================================================
|
||||
# Phase 2: Install PyTorch
|
||||
# ==============================================================================
|
||||
log ""
|
||||
log "============================================================"
|
||||
log " Phase 2: PyTorch Installation"
|
||||
log "============================================================"
|
||||
|
||||
echo -e "${YELLOW}Step 2/7: Installing PyTorch...${NC}"
|
||||
|
||||
# Select PyTorch URL based on CUDA version
|
||||
case $CUDA_VERSION in
|
||||
cpu)
|
||||
TORCH_URL="https://download.pytorch.org/whl/cpu"
|
||||
;;
|
||||
cuda121)
|
||||
TORCH_URL="https://download.pytorch.org/whl/cu121"
|
||||
;;
|
||||
cuda124)
|
||||
TORCH_URL="https://download.pytorch.org/whl/cu124"
|
||||
;;
|
||||
cuda126)
|
||||
TORCH_URL="https://download.pytorch.org/whl/cu126"
|
||||
;;
|
||||
cuda128)
|
||||
TORCH_URL="https://download.pytorch.org/whl/cu128"
|
||||
;;
|
||||
*)
|
||||
TORCH_URL="https://download.pytorch.org/whl/cu121"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "Installing PyTorch ($CUDA_VERSION)..."
|
||||
log "Installing PyTorch ($CUDA_VERSION) from $TORCH_URL..."
|
||||
pip3 install --progress-bar on torch torchaudio --index-url "$TORCH_URL"
|
||||
log "PyTorch installed"
|
||||
echo ""
|
||||
|
||||
# ==============================================================================
|
||||
# Phase 3: Core Dependencies
|
||||
# ==============================================================================
|
||||
log ""
|
||||
log "============================================================"
|
||||
log " Phase 3: Core Dependencies"
|
||||
log "============================================================"
|
||||
|
||||
echo -e "${YELLOW}Step 3/7: Installing core dependencies...${NC}"
|
||||
|
||||
# Phase 3.1: Core scientific stack (MUST install first)
|
||||
log "Phase 3.1: Installing scientific stack..."
|
||||
pip3 install "numpy>=2.0" "scipy>=1.10.1" "librosa>=0.11.0"
|
||||
|
||||
# Phase 3.2: Audio and utility packages (including fsspec constraint)
|
||||
log "Phase 3.2: Installing audio/utility packages..."
|
||||
pip3 install --progress-bar on soundfile pydub tqdm colorama requests regex "psutil>=5.9.0" "fsspec>=2025.3.0"
|
||||
|
||||
# Phase 3.3: Subtitle and async packages
|
||||
log "Phase 3.3: Installing subtitle/async packages..."
|
||||
pip3 install pysrt srt aiofiles jsonschema pyloudnorm
|
||||
pip3 install pydantic>=2.0 PyYAML>=6.0
|
||||
|
||||
# Phase 3.4: Config and optimization packages
|
||||
log "Phase 3.4: Installing config packages..."
|
||||
pip3 install "pydantic>=2.0,<3.0" "PyYAML>=6.0" numba
|
||||
|
||||
# Phase 3.5: Image packages (non-fatal)
|
||||
pip3 install Pillow || echo -e "${YELLOW}Warning: Pillow installation failed (non-fatal)${NC}"
|
||||
|
||||
log "Core dependencies installed"
|
||||
echo ""
|
||||
|
||||
# Install Whisper packages from git (avoid PyPI conflicts)
|
||||
echo -e "${YELLOW}Step 4/6: Installing Whisper packages...${NC}"
|
||||
# ==============================================================================
|
||||
# Phase 4: Whisper Packages
|
||||
# ==============================================================================
|
||||
log ""
|
||||
log "============================================================"
|
||||
log " Phase 4: Whisper Packages"
|
||||
log "============================================================"
|
||||
|
||||
echo -e "${YELLOW}Step 4/7: Installing Whisper packages...${NC}"
|
||||
log "Installing Whisper packages..."
|
||||
|
||||
pip3 install git+https://github.com/openai/whisper@main
|
||||
log "openai-whisper installed"
|
||||
|
||||
pip3 install git+https://github.com/meizhong986/stable-ts-fix-setup.git@main
|
||||
pip3 install faster-whisper>=1.1.0
|
||||
log "stable-ts installed"
|
||||
|
||||
# ffmpeg-python: PyPI tarball fails, use git URL
|
||||
pip3 install git+https://github.com/kkroening/ffmpeg-python.git || {
|
||||
echo -e "${YELLOW}Warning: ffmpeg-python from git failed, trying PyPI...${NC}"
|
||||
log "WARNING: ffmpeg-python git failed, trying PyPI"
|
||||
pip3 install ffmpeg-python
|
||||
}
|
||||
log "ffmpeg-python installed"
|
||||
|
||||
pip3 install "faster-whisper>=1.1.0"
|
||||
log "faster-whisper installed"
|
||||
echo ""
|
||||
|
||||
# Install optional packages
|
||||
echo -e "${YELLOW}Step 5/6: Installing optional packages...${NC}"
|
||||
# ==============================================================================
|
||||
# Phase 5: Optional Packages
|
||||
# ==============================================================================
|
||||
log ""
|
||||
log "============================================================"
|
||||
log " Phase 5: Optional Packages"
|
||||
log "============================================================"
|
||||
|
||||
echo -e "${YELLOW}Step 5/7: Installing optional packages...${NC}"
|
||||
|
||||
# HuggingFace / Transformers
|
||||
pip3 install huggingface-hub>=0.25.0 transformers>=4.40.0 accelerate>=0.26.0
|
||||
log "Installing HuggingFace packages..."
|
||||
pip3 install "huggingface-hub>=0.25.0" "transformers>=4.40.0" "accelerate>=0.26.0"
|
||||
|
||||
# hf_xet for faster HuggingFace downloads (optional)
|
||||
pip3 install hf_xet 2>/dev/null || log "Note: hf_xet not installed (optional)"
|
||||
|
||||
# Translation
|
||||
pip3 install PySubtrans>=0.7.0 openai>=1.35.0 google-genai>=1.39.0
|
||||
log "Installing translation packages..."
|
||||
pip3 install "PySubtrans>=0.7.0" "openai>=1.35.0" "google-genai>=1.39.0"
|
||||
|
||||
# VAD
|
||||
pip3 install silero-vad>=6.0 auditok
|
||||
log "Installing VAD packages..."
|
||||
pip3 install "silero-vad>=6.0" auditok
|
||||
|
||||
if [ "$MINIMAL" = false ]; then
|
||||
pip3 install ten-vad || echo -e "${YELLOW}Warning: ten-vad installation failed (optional)${NC}"
|
||||
pip3 install "scikit-learn>=1.3.0"
|
||||
fi
|
||||
log "Optional packages installed"
|
||||
echo ""
|
||||
|
||||
# ==============================================================================
|
||||
# Phase 6: Speech Enhancement (optional but recommended)
|
||||
# ==============================================================================
|
||||
log ""
|
||||
log "============================================================"
|
||||
log " Phase 6: Speech Enhancement"
|
||||
log "============================================================"
|
||||
|
||||
if [ "$NO_SPEECH_ENHANCEMENT" = false ]; then
|
||||
echo -e "${YELLOW}Step 6/7: Installing speech enhancement packages...${NC}"
|
||||
echo "Note: These packages can be tricky. If installation fails, re-run with --no-speech-enhancement"
|
||||
echo " Speech enhancement improves transcription quality in noisy audio."
|
||||
echo ""
|
||||
log "Installing speech enhancement packages..."
|
||||
|
||||
# Install ModelScope dependencies first (CRITICAL: datasets must be <4.0)
|
||||
log "Installing ModelScope dependencies..."
|
||||
pip3 install addict simplejson sortedcontainers packaging
|
||||
pip3 install "datasets>=2.14.0,<4.0" || {
|
||||
echo -e "${YELLOW}Warning: datasets installation failed - modelscope may not work${NC}"
|
||||
log "WARNING: datasets installation failed"
|
||||
}
|
||||
|
||||
# Install ModelScope (ZipEnhancer SOTA speech enhancement)
|
||||
echo "Installing ModelScope (ZipEnhancer)..."
|
||||
log "Installing ModelScope..."
|
||||
pip3 install "modelscope>=1.20" || {
|
||||
echo -e "${YELLOW}Warning: modelscope installation failed (optional)${NC}"
|
||||
log "WARNING: modelscope installation failed"
|
||||
}
|
||||
|
||||
# Install ClearVoice from NumPy 2.x compatible fork
|
||||
echo "Installing ClearVoice (48kHz denoising)..."
|
||||
log "Installing ClearVoice..."
|
||||
pip3 install "git+https://github.com/meizhong986/ClearerVoice-Studio.git#subdirectory=clearvoice" || {
|
||||
echo -e "${YELLOW}Warning: clearvoice installation failed (optional)${NC}"
|
||||
log "WARNING: clearvoice installation failed"
|
||||
}
|
||||
|
||||
# BS-RoFormer (vocal isolation)
|
||||
echo "Installing BS-RoFormer (vocal isolation)..."
|
||||
log "Installing BS-RoFormer..."
|
||||
pip3 install bs-roformer-infer || {
|
||||
echo -e "${YELLOW}Warning: bs-roformer-infer installation failed (optional)${NC}"
|
||||
log "WARNING: bs-roformer-infer installation failed"
|
||||
}
|
||||
|
||||
# ONNX Runtime for ZipEnhancer ONNX mode
|
||||
echo "Installing ONNX Runtime..."
|
||||
pip3 install "onnxruntime>=1.16.0" || log "Note: onnxruntime installation failed (optional)"
|
||||
|
||||
log "Speech enhancement packages installed"
|
||||
else
|
||||
echo -e "${YELLOW}Step 6/7: Skipping speech enhancement (--no-speech-enhancement)${NC}"
|
||||
log "Skipping speech enhancement (--no-speech-enhancement)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ==============================================================================
|
||||
# Phase 7: WhisperJAV Application
|
||||
# ==============================================================================
|
||||
log ""
|
||||
log "============================================================"
|
||||
log " Phase 7: WhisperJAV Application"
|
||||
log "============================================================"
|
||||
|
||||
echo -e "${YELLOW}Step 7/7: Installing WhisperJAV...${NC}"
|
||||
if [ "$DEV_MODE" = true ]; then
|
||||
log "Installing WhisperJAV in development mode..."
|
||||
pip3 install --no-deps -e .
|
||||
else
|
||||
log "Installing WhisperJAV..."
|
||||
pip3 install --no-deps git+https://github.com/meizhong986/whisperjav.git
|
||||
fi
|
||||
log "WhisperJAV installed"
|
||||
echo ""
|
||||
|
||||
# ==============================================================================
|
||||
# Verification Phase
|
||||
# ==============================================================================
|
||||
log ""
|
||||
log "============================================================"
|
||||
log " Verification Phase"
|
||||
log "============================================================"
|
||||
|
||||
echo -e "${YELLOW}Verifying installation...${NC}"
|
||||
log "Verifying installation..."
|
||||
|
||||
# Verify WhisperJAV
|
||||
python3 -c "import whisperjav; print(f'WhisperJAV {whisperjav.__version__} installed successfully!')" && {
|
||||
echo -e "${GREEN}Installation complete!${NC}"
|
||||
log "WhisperJAV verified successfully"
|
||||
} || {
|
||||
echo -e "${RED}Installation may have issues. Check the output above.${NC}"
|
||||
log "WARNING: WhisperJAV verification failed"
|
||||
}
|
||||
|
||||
# Verify PyTorch CUDA
|
||||
python3 -c "import torch; cuda_status = 'ENABLED' if torch.cuda.is_available() else 'DISABLED'; print(f'CUDA acceleration: {cuda_status}')" 2>/dev/null
|
||||
|
||||
# Verify critical packages
|
||||
echo ""
|
||||
echo "Verifying critical packages:"
|
||||
log "Verifying critical packages..."
|
||||
|
||||
python3 -c "import numpy; print(f' numpy: {numpy.__version__}')" 2>/dev/null || echo " numpy: FAILED"
|
||||
python3 -c "import scipy; print(f' scipy: {scipy.__version__}')" 2>/dev/null || echo " scipy: FAILED"
|
||||
python3 -c "import librosa; print(f' librosa: {librosa.__version__}')" 2>/dev/null || echo " librosa: FAILED"
|
||||
python3 -c "import faster_whisper; print(f' faster-whisper: {faster_whisper.__version__}')" 2>/dev/null || echo " faster-whisper: FAILED"
|
||||
python3 -c "import transformers; print(f' transformers: {transformers.__version__}')" 2>/dev/null || echo " transformers: FAILED"
|
||||
|
||||
# Speech Enhancement (optional, can cause conflicts)
|
||||
if [ "$NO_SPEECH_ENHANCEMENT" = false ]; then
|
||||
echo ""
|
||||
echo -e "${YELLOW}Installing speech enhancement packages...${NC}"
|
||||
echo "Note: These packages can be tricky. If installation fails, re-run with --no-speech-enhancement"
|
||||
|
||||
# Install modelscope and its dependencies first
|
||||
pip3 install addict simplejson sortedcontainers packaging
|
||||
pip3 install "datasets>=2.14.0,<4.0"
|
||||
pip3 install modelscope>=1.20 || echo -e "${YELLOW}Warning: modelscope installation failed${NC}"
|
||||
|
||||
# Install clearvoice from fork (supports numpy 2.x)
|
||||
pip3 install "git+https://github.com/meizhong986/ClearerVoice-Studio.git#subdirectory=clearvoice" || \
|
||||
echo -e "${YELLOW}Warning: clearvoice installation failed (speech enhancement will be disabled)${NC}"
|
||||
|
||||
# BS-RoFormer
|
||||
pip3 install bs-roformer-infer || echo -e "${YELLOW}Warning: bs-roformer-infer installation failed${NC}"
|
||||
|
||||
pip3 install onnxruntime>=1.16.0
|
||||
echo "Speech enhancement packages:"
|
||||
python3 -c "import modelscope; print(f' modelscope: {modelscope.__version__}')" 2>/dev/null || echo " modelscope: NOT INSTALLED"
|
||||
python3 -c "import clearvoice; print(' clearvoice: installed')" 2>/dev/null || echo " clearvoice: NOT INSTALLED"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Install WhisperJAV itself
|
||||
echo -e "${YELLOW}Step 6/6: Installing WhisperJAV...${NC}"
|
||||
pip3 install --no-deps git+https://github.com/meizhong986/whisperjav.git
|
||||
echo ""
|
||||
|
||||
# Verify installation
|
||||
echo -e "${YELLOW}Verifying installation...${NC}"
|
||||
python3 -c "import whisperjav; print(f'WhisperJAV {whisperjav.__version__} installed successfully!')" && \
|
||||
echo -e "${GREEN}Installation complete!${NC}" || \
|
||||
echo -e "${RED}Installation may have issues. Check the output above.${NC}"
|
||||
|
||||
echo ""
|
||||
|
||||
# ==============================================================================
|
||||
# Summary
|
||||
# ==============================================================================
|
||||
log ""
|
||||
log "============================================================"
|
||||
log " Installation Complete!"
|
||||
log "============================================================"
|
||||
|
||||
echo "============================================================"
|
||||
echo " Installation Summary"
|
||||
echo "============================================================"
|
||||
|
|
@ -213,3 +545,10 @@ echo ""
|
|||
echo " For help:"
|
||||
echo " whisperjav --help"
|
||||
echo ""
|
||||
echo " Log file: $INSTALL_LOG"
|
||||
echo ""
|
||||
echo " If you encounter issues with speech enhancement, re-run with:"
|
||||
echo " ./install_linux.sh --no-speech-enhancement"
|
||||
echo ""
|
||||
|
||||
log "Installation completed successfully at $(date)"
|
||||
|
|
|
|||
|
|
@ -17,9 +17,10 @@ REM
|
|||
REM Options:
|
||||
REM --cpu-only Install CPU-only PyTorch (no CUDA)
|
||||
REM --cuda118 Install PyTorch for CUDA 11.8
|
||||
REM --cuda121 Install PyTorch for CUDA 12.1 (default)
|
||||
REM --cuda121 Install PyTorch for CUDA 12.1
|
||||
REM --cuda124 Install PyTorch for CUDA 12.4
|
||||
REM --cuda126 Install PyTorch for CUDA 12.6
|
||||
REM --cuda128 Install PyTorch for CUDA 12.8 (default for driver 570+)
|
||||
REM --no-speech-enhancement Skip speech enhancement packages
|
||||
REM --minimal Minimal install (transcription only)
|
||||
REM --dev Install in development/editable mode
|
||||
|
|
@ -58,6 +59,7 @@ if /i "%~1"=="--cuda118" set "CUDA_VERSION=cuda118"
|
|||
if /i "%~1"=="--cuda121" set "CUDA_VERSION=cuda121"
|
||||
if /i "%~1"=="--cuda124" set "CUDA_VERSION=cuda124"
|
||||
if /i "%~1"=="--cuda126" set "CUDA_VERSION=cuda126"
|
||||
if /i "%~1"=="--cuda128" set "CUDA_VERSION=cuda128"
|
||||
if /i "%~1"=="--no-speech-enhancement" set "NO_SPEECH_ENHANCEMENT=1"
|
||||
if /i "%~1"=="--minimal" (
|
||||
set "MINIMAL=1"
|
||||
|
|
@ -220,12 +222,17 @@ if "%CPU_ONLY%"=="0" (
|
|||
REM Auto-select CUDA version based on driver if user didn't specify
|
||||
if "%CUDA_VERSION%"=="cuda121" (
|
||||
REM Check if driver supports newer CUDA
|
||||
REM Driver 570+ supports CUDA 12.8
|
||||
REM Driver 560+ supports CUDA 12.6
|
||||
REM Driver 551+ supports CUDA 12.4
|
||||
REM Driver 531+ supports CUDA 12.1
|
||||
for /f "tokens=1 delims=." %%d in ("!DRIVER_VERSION!") do set "DRIVER_MAJOR=%%d"
|
||||
if defined DRIVER_MAJOR (
|
||||
if !DRIVER_MAJOR! GEQ 560 (
|
||||
if !DRIVER_MAJOR! GEQ 570 (
|
||||
echo Auto-selecting CUDA 12.8 based on driver !DRIVER_VERSION!
|
||||
call :log "Auto-selecting CUDA 12.8 based on driver"
|
||||
set "CUDA_VERSION=cuda128"
|
||||
) else if !DRIVER_MAJOR! GEQ 560 (
|
||||
echo Auto-selecting CUDA 12.6 based on driver !DRIVER_VERSION!
|
||||
call :log "Auto-selecting CUDA 12.6 based on driver"
|
||||
set "CUDA_VERSION=cuda126"
|
||||
|
|
@ -286,6 +293,10 @@ if "%CUDA_VERSION%"=="cuda126" (
|
|||
set "TORCH_URL=https://download.pytorch.org/whl/cu126"
|
||||
set "TORCH_PACKAGES=torch torchaudio"
|
||||
)
|
||||
if "%CUDA_VERSION%"=="cuda128" (
|
||||
set "TORCH_URL=https://download.pytorch.org/whl/cu128"
|
||||
set "TORCH_PACKAGES=torch torchaudio"
|
||||
)
|
||||
|
||||
REM Display configuration
|
||||
echo.
|
||||
|
|
@ -350,7 +361,7 @@ if errorlevel 1 (
|
|||
echo.
|
||||
|
||||
REM ============================================================
|
||||
REM Phase 4: Core Dependencies
|
||||
REM Phase 4: Core Dependencies (with constraints)
|
||||
REM ============================================================
|
||||
call :log ""
|
||||
call :log "============================================================"
|
||||
|
|
@ -361,23 +372,31 @@ echo ============================================================
|
|||
echo [Step 3/7] Installing core dependencies
|
||||
echo ============================================================
|
||||
|
||||
REM Phase 4.1: Core scientific stack (MUST install first to establish versions)
|
||||
call :log "Phase 4.1: Installing core scientific stack..."
|
||||
call :run_pip_with_retry "install numpy>=2.0 scipy>=1.10.1 librosa>=0.11.0" "Install scientific packages"
|
||||
if errorlevel 1 goto :install_failed
|
||||
|
||||
call :run_pip_with_retry "install soundfile pydub tqdm colorama requests regex" "Install audio/utility packages"
|
||||
REM Phase 4.2: Audio and utility packages
|
||||
call :log "Phase 4.2: Installing audio/utility packages..."
|
||||
call :run_pip_with_retry "install --progress-bar on soundfile pydub tqdm colorama requests regex psutil>=5.9.0 fsspec>=2025.3.0" "Install audio/utility packages"
|
||||
if errorlevel 1 goto :install_failed
|
||||
|
||||
REM Phase 4.3: Subtitle and async packages
|
||||
call :log "Phase 4.3: Installing subtitle/async packages..."
|
||||
call :run_pip_with_retry "install pysrt srt aiofiles jsonschema pyloudnorm" "Install subtitle/async packages"
|
||||
if errorlevel 1 goto :install_failed
|
||||
|
||||
REM Phase 4.4: Config and optimization packages
|
||||
call :log "Phase 4.4: Installing config packages..."
|
||||
call :run_pip_with_retry "install pydantic>=2.0,<3.0 PyYAML>=6.0 numba" "Install config packages"
|
||||
if errorlevel 1 goto :install_failed
|
||||
|
||||
REM Additional packages from requirements_v1.7.4.txt
|
||||
call :run_pip_with_retry "install Pillow matplotlib" "Install image/plotting packages"
|
||||
REM Phase 4.5: Image/plotting packages (non-fatal)
|
||||
call :run_pip_with_retry "install Pillow" "Install image packages"
|
||||
if errorlevel 1 (
|
||||
echo WARNING: Pillow/matplotlib installation failed (non-fatal)
|
||||
call :log "WARNING: Pillow/matplotlib installation failed"
|
||||
echo WARNING: Pillow installation failed (non-fatal)
|
||||
call :log "WARNING: Pillow installation failed"
|
||||
)
|
||||
echo.
|
||||
|
||||
|
|
@ -405,10 +424,12 @@ if errorlevel 1 (
|
|||
exit /b 1
|
||||
)
|
||||
|
||||
call :run_pip_with_retry "install git+https://github.com/kkroening/ffmpeg-python.git" "Install ffmpeg-python"
|
||||
REM ffmpeg-python: PyPI tarball fails, use git URL
|
||||
call :run_pip_with_retry "install git+https://github.com/kkroening/ffmpeg-python.git" "Install ffmpeg-python (git)"
|
||||
if errorlevel 1 (
|
||||
echo WARNING: ffmpeg-python from git failed, trying PyPI version...
|
||||
call :run_pip_with_retry "install ffmpeg-python" "Install ffmpeg-python (PyPI)"
|
||||
call :log "WARNING: ffmpeg-python git failed, trying PyPI..."
|
||||
call :run_pip_with_retry "install ffmpeg-python" "Install ffmpeg-python (PyPI fallback)"
|
||||
)
|
||||
|
||||
call :run_pip_with_retry "install faster-whisper>=1.1.0" "Install faster-whisper"
|
||||
|
|
@ -438,6 +459,12 @@ if errorlevel 1 (
|
|||
call :log "WARNING: HuggingFace packages failed"
|
||||
)
|
||||
|
||||
REM hf_xet for faster HuggingFace downloads (optional)
|
||||
python -m pip install hf_xet 2>nul
|
||||
if errorlevel 1 (
|
||||
call :log "Note: hf_xet not installed (optional, faster HF downloads)"
|
||||
)
|
||||
|
||||
REM Translation
|
||||
call :log "Installing translation packages..."
|
||||
call :run_pip_with_retry "install PySubtrans>=0.7.0 openai>=1.35.0 google-genai>=1.39.0" "Install translation packages"
|
||||
|
|
@ -471,40 +498,58 @@ if "%MINIMAL%"=="0" (
|
|||
)
|
||||
echo.
|
||||
|
||||
REM Speech Enhancement (optional)
|
||||
REM Speech Enhancement (optional but recommended)
|
||||
if "%NO_SPEECH_ENHANCEMENT%"=="0" (
|
||||
echo ============================================================
|
||||
echo [Step 6/7] Installing speech enhancement packages
|
||||
echo ============================================================
|
||||
echo Note: These packages can be tricky. Failures here are non-fatal.
|
||||
echo Speech enhancement improves transcription quality in noisy audio.
|
||||
echo.
|
||||
call :log "Installing speech enhancement packages..."
|
||||
|
||||
REM Install ModelScope dependencies first (CRITICAL: datasets must be <4.0)
|
||||
call :log "Installing ModelScope dependencies..."
|
||||
python -m pip install addict simplejson sortedcontainers packaging 2>nul
|
||||
python -m pip install "datasets>=2.14.0,<4.0" 2>nul
|
||||
if errorlevel 1 (
|
||||
echo WARNING: datasets installation failed - modelscope may not work
|
||||
call :log "WARNING: datasets installation failed"
|
||||
)
|
||||
|
||||
echo Installing ModelScope...
|
||||
REM Install ModelScope (ZipEnhancer SOTA speech enhancement)
|
||||
echo Installing ModelScope ^(ZipEnhancer^)...
|
||||
call :log "Installing ModelScope..."
|
||||
python -m pip install "modelscope>=1.20" 2>nul
|
||||
if errorlevel 1 (
|
||||
echo WARNING: modelscope installation failed ^(optional^)
|
||||
call :log "WARNING: modelscope installation failed"
|
||||
)
|
||||
|
||||
echo Installing ClearVoice...
|
||||
REM Install ClearVoice from NumPy 2.x compatible fork
|
||||
echo Installing ClearVoice ^(48kHz denoising^)...
|
||||
call :log "Installing ClearVoice..."
|
||||
python -m pip install "git+https://github.com/meizhong986/ClearerVoice-Studio.git#subdirectory=clearvoice" 2>nul
|
||||
if errorlevel 1 (
|
||||
echo WARNING: clearvoice installation failed ^(optional^)
|
||||
call :log "WARNING: clearvoice installation failed"
|
||||
)
|
||||
|
||||
echo Installing BS-RoFormer...
|
||||
REM Install BS-RoFormer (vocal isolation)
|
||||
echo Installing BS-RoFormer ^(vocal isolation^)...
|
||||
call :log "Installing BS-RoFormer..."
|
||||
python -m pip install bs-roformer-infer 2>nul
|
||||
if errorlevel 1 (
|
||||
echo WARNING: bs-roformer-infer installation failed ^(optional^)
|
||||
call :log "WARNING: bs-roformer-infer installation failed"
|
||||
)
|
||||
|
||||
REM ONNX Runtime for ZipEnhancer ONNX mode
|
||||
echo Installing ONNX Runtime...
|
||||
python -m pip install "onnxruntime>=1.16.0" 2>nul
|
||||
if errorlevel 1 (
|
||||
call :log "Note: onnxruntime installation failed (optional)"
|
||||
)
|
||||
echo.
|
||||
) else (
|
||||
echo ============================================================
|
||||
|
|
@ -546,7 +591,7 @@ if errorlevel 1 (
|
|||
echo.
|
||||
|
||||
REM ============================================================
|
||||
REM Verification and Summary
|
||||
REM Verification Phase: Check Critical Dependencies
|
||||
REM ============================================================
|
||||
call :log ""
|
||||
call :log "============================================================"
|
||||
|
|
@ -556,9 +601,11 @@ call :log "============================================================"
|
|||
echo ============================================================
|
||||
echo Verifying Installation
|
||||
echo ============================================================
|
||||
|
||||
REM Verify WhisperJAV
|
||||
python -c "import whisperjav; print(f'WhisperJAV {whisperjav.__version__} installed successfully!')"
|
||||
if errorlevel 1 (
|
||||
echo WARNING: Could not verify installation
|
||||
echo WARNING: Could not verify WhisperJAV installation
|
||||
call :log "WARNING: WhisperJAV verification failed"
|
||||
) else (
|
||||
call :log "WhisperJAV verified successfully"
|
||||
|
|
@ -567,6 +614,24 @@ if errorlevel 1 (
|
|||
REM Verify PyTorch CUDA status
|
||||
python -c "import torch; cuda_status = 'ENABLED' if torch.cuda.is_available() else 'DISABLED'; print(f'CUDA acceleration: {cuda_status}')" 2>nul
|
||||
|
||||
REM Verify critical packages
|
||||
echo.
|
||||
echo Verifying critical packages:
|
||||
call :log "Verifying critical packages..."
|
||||
|
||||
python -c "import numpy; print(f' numpy: {numpy.__version__}')" 2>nul || echo numpy: FAILED
|
||||
python -c "import scipy; print(f' scipy: {scipy.__version__}')" 2>nul || echo scipy: FAILED
|
||||
python -c "import librosa; print(f' librosa: {librosa.__version__}')" 2>nul || echo librosa: FAILED
|
||||
python -c "import faster_whisper; print(f' faster-whisper: {faster_whisper.__version__}')" 2>nul || echo faster-whisper: FAILED
|
||||
python -c "import transformers; print(f' transformers: {transformers.__version__}')" 2>nul || echo transformers: FAILED
|
||||
|
||||
if "%NO_SPEECH_ENHANCEMENT%"=="0" (
|
||||
echo.
|
||||
echo Speech enhancement packages:
|
||||
python -c "import modelscope; print(f' modelscope: {modelscope.__version__}')" 2>nul || echo modelscope: NOT INSTALLED
|
||||
python -c "import clearvoice; print(' clearvoice: installed')" 2>nul || echo clearvoice: NOT INSTALLED
|
||||
)
|
||||
|
||||
echo.
|
||||
|
||||
REM Summary
|
||||
|
|
@ -674,9 +739,10 @@ echo.
|
|||
echo Options:
|
||||
echo --cpu-only Install CPU-only PyTorch ^(no CUDA^)
|
||||
echo --cuda118 Install PyTorch for CUDA 11.8
|
||||
echo --cuda121 Install PyTorch for CUDA 12.1 ^(default^)
|
||||
echo --cuda121 Install PyTorch for CUDA 12.1
|
||||
echo --cuda124 Install PyTorch for CUDA 12.4
|
||||
echo --cuda126 Install PyTorch for CUDA 12.6
|
||||
echo --cuda128 Install PyTorch for CUDA 12.8 ^(default for driver 570+^)
|
||||
echo --no-speech-enhancement Skip speech enhancement packages
|
||||
echo --minimal Minimal install ^(transcription only^)
|
||||
echo --dev Install in development/editable mode
|
||||
|
|
@ -685,9 +751,11 @@ echo.
|
|||
echo Examples:
|
||||
echo install_windows.bat # Standard install with auto CUDA detection
|
||||
echo install_windows.bat --cpu-only # CPU-only install
|
||||
echo install_windows.bat --cuda128 # Force CUDA 12.8
|
||||
echo install_windows.bat --minimal --dev # Minimal dev install
|
||||
echo.
|
||||
echo The script will auto-detect your GPU and select the appropriate
|
||||
echo CUDA version. Use --cuda*** flags to override.
|
||||
echo CUDA version ^(12.8 for driver 570+, 12.6 for 560+, etc.^).
|
||||
echo Use --cuda*** flags to override.
|
||||
echo.
|
||||
exit /b 0
|
||||
|
|
|
|||
|
|
@ -0,0 +1,565 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Unit tests for compute_type="auto" strategy.
|
||||
|
||||
Tests the CTranslate2 compute_type delegation implemented in:
|
||||
- whisperjav/config/resolver_v3.py
|
||||
- whisperjav/modules/faster_whisper_pro_asr.py
|
||||
- whisperjav/modules/kotoba_faster_whisper_asr.py
|
||||
- whisperjav/modules/speech_segmentation/backends/whisper_vad.py
|
||||
|
||||
Background:
|
||||
- CTranslate2's "auto" selects optimal compute_type based on device capabilities
|
||||
- Automatically handles RTX 50XX Blackwell (sm120) which doesn't support INT8
|
||||
- See: https://github.com/OpenNMT/CTranslate2/issues/1865
|
||||
|
||||
Run with: pytest tests/test_compute_type_auto.py -v
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Resolver Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestResolverComputeType:
|
||||
"""Tests for resolver_v3.py compute_type selection."""
|
||||
|
||||
def test_ctranslate2_providers_return_auto(self):
|
||||
"""Test that CTranslate2 providers get compute_type='auto'."""
|
||||
from whisperjav.config.resolver_v3 import _get_compute_type_for_device
|
||||
|
||||
# CTranslate2 providers should return "auto" regardless of device
|
||||
assert _get_compute_type_for_device("cuda", "faster_whisper") == "auto"
|
||||
assert _get_compute_type_for_device("cpu", "faster_whisper") == "auto"
|
||||
assert _get_compute_type_for_device("cuda", "kotoba_faster_whisper") == "auto"
|
||||
assert _get_compute_type_for_device("cpu", "kotoba_faster_whisper") == "auto"
|
||||
|
||||
def test_pytorch_providers_return_explicit_types(self):
|
||||
"""Test that PyTorch providers get explicit compute_type values."""
|
||||
from whisperjav.config.resolver_v3 import _get_compute_type_for_device
|
||||
|
||||
# PyTorch providers should return explicit types
|
||||
assert _get_compute_type_for_device("cuda", "openai_whisper") == "float16"
|
||||
assert _get_compute_type_for_device("mps", "openai_whisper") == "float16"
|
||||
assert _get_compute_type_for_device("cpu", "openai_whisper") == "float32"
|
||||
assert _get_compute_type_for_device("cuda", "stable_ts") == "float16"
|
||||
assert _get_compute_type_for_device("cpu", "stable_ts") == "float32"
|
||||
|
||||
def test_ctranslate2_providers_set(self):
|
||||
"""Test that CTRANSLATE2_PROVIDERS set is correctly defined."""
|
||||
from whisperjav.config.resolver_v3 import CTRANSLATE2_PROVIDERS
|
||||
|
||||
assert "faster_whisper" in CTRANSLATE2_PROVIDERS
|
||||
assert "kotoba_faster_whisper" in CTRANSLATE2_PROVIDERS
|
||||
# PyTorch providers should NOT be in this set
|
||||
assert "openai_whisper" not in CTRANSLATE2_PROVIDERS
|
||||
assert "stable_ts" not in CTRANSLATE2_PROVIDERS
|
||||
|
||||
|
||||
class TestResolverIntegration:
|
||||
"""Integration tests for resolver with compute_type='auto'."""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_device_detector(self):
|
||||
"""Mock device detector to return specific device."""
|
||||
with patch('whisperjav.config.resolver_v3.get_best_device') as mock:
|
||||
yield mock
|
||||
|
||||
def test_resolve_config_faster_whisper_cuda(self, mock_device_detector):
|
||||
"""Test that faster_whisper config has compute_type='auto' on CUDA."""
|
||||
mock_device_detector.return_value = "cuda"
|
||||
|
||||
# Mock the component registries
|
||||
with patch('whisperjav.config.resolver_v3.get_asr_registry') as mock_asr:
|
||||
with patch('whisperjav.config.resolver_v3.get_vad_registry') as mock_vad:
|
||||
# Setup mock ASR component
|
||||
mock_component = MagicMock()
|
||||
mock_component.provider = "faster_whisper"
|
||||
mock_component.model_id = "large-v3"
|
||||
mock_component.supported_tasks = ["transcribe"]
|
||||
mock_preset = MagicMock()
|
||||
mock_preset.model_dump.return_value = {"language": "ja", "task": "transcribe"}
|
||||
mock_component.get_preset.return_value = mock_preset
|
||||
|
||||
mock_asr.return_value = {"faster_whisper": mock_component}
|
||||
mock_vad.return_value = {}
|
||||
|
||||
from whisperjav.config.resolver_v3 import resolve_config_v3
|
||||
config = resolve_config_v3(asr="faster_whisper", vad="none")
|
||||
|
||||
assert config["model"]["compute_type"] == "auto"
|
||||
|
||||
def test_resolve_config_faster_whisper_cpu(self, mock_device_detector):
|
||||
"""Test that faster_whisper config has compute_type='auto' on CPU."""
|
||||
mock_device_detector.return_value = "cpu"
|
||||
|
||||
with patch('whisperjav.config.resolver_v3.get_asr_registry') as mock_asr:
|
||||
with patch('whisperjav.config.resolver_v3.get_vad_registry') as mock_vad:
|
||||
mock_component = MagicMock()
|
||||
mock_component.provider = "faster_whisper"
|
||||
mock_component.model_id = "large-v3"
|
||||
mock_component.supported_tasks = ["transcribe"]
|
||||
mock_preset = MagicMock()
|
||||
mock_preset.model_dump.return_value = {"language": "ja", "task": "transcribe"}
|
||||
mock_component.get_preset.return_value = mock_preset
|
||||
|
||||
mock_asr.return_value = {"faster_whisper": mock_component}
|
||||
mock_vad.return_value = {}
|
||||
|
||||
from whisperjav.config.resolver_v3 import resolve_config_v3
|
||||
config = resolve_config_v3(asr="faster_whisper", vad="none")
|
||||
|
||||
# Should be "auto" even on CPU - CTranslate2 handles selection
|
||||
assert config["model"]["compute_type"] == "auto"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# ASR Module Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestFasterWhisperProASR:
|
||||
"""Tests for FasterWhisperProASR compute_type handling."""
|
||||
|
||||
def test_uses_compute_type_from_config(self):
|
||||
"""Test that compute_type is taken from model_config."""
|
||||
# We can't easily test the full initialization without models
|
||||
# but we can verify the default behavior
|
||||
pass # Tested via integration tests
|
||||
|
||||
def test_default_compute_type_is_auto(self):
|
||||
"""Test that default compute_type is 'auto'."""
|
||||
# This is verified by checking the resolver returns "auto"
|
||||
from whisperjav.config.resolver_v3 import _get_compute_type_for_device
|
||||
assert _get_compute_type_for_device("cuda", "faster_whisper") == "auto"
|
||||
|
||||
|
||||
class TestKotobaFasterWhisperASR:
|
||||
"""Tests for KotobaFasterWhisperASR compute_type handling."""
|
||||
|
||||
def test_default_compute_type_is_auto(self):
|
||||
"""Test that default compute_type is 'auto'."""
|
||||
from whisperjav.config.resolver_v3 import _get_compute_type_for_device
|
||||
assert _get_compute_type_for_device("cuda", "kotoba_faster_whisper") == "auto"
|
||||
|
||||
|
||||
class TestWhisperVAD:
|
||||
"""Tests for WhisperVAD compute_type handling."""
|
||||
|
||||
def test_default_compute_type_is_auto(self):
|
||||
"""Test that default compute_type parameter is 'auto'."""
|
||||
import inspect
|
||||
from whisperjav.modules.speech_segmentation.backends.whisper_vad import WhisperVadSpeechSegmenter
|
||||
|
||||
sig = inspect.signature(WhisperVadSpeechSegmenter.__init__)
|
||||
compute_type_param = sig.parameters.get('compute_type')
|
||||
|
||||
assert compute_type_param is not None
|
||||
assert compute_type_param.default == "auto"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CTranslate2 Auto Selection Logic Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestCTranslate2AutoBehavior:
|
||||
"""Tests documenting expected CTranslate2 'auto' behavior."""
|
||||
|
||||
def test_auto_documented_behavior(self):
|
||||
"""
|
||||
Document expected CTranslate2 'auto' behavior.
|
||||
|
||||
According to CTranslate2 documentation:
|
||||
- "auto" selects the fastest supported compute type
|
||||
- GPU (CC ≥8.0): All types supported, likely selects int8_float16
|
||||
- GPU (CC 6.1-7.0): int8/float16 optimized
|
||||
- CPU: int8/int16/float32 based on instruction sets
|
||||
- Blackwell sm120: Disables INT8 (PR #1937)
|
||||
|
||||
This test documents the expected behavior rather than testing it,
|
||||
as the actual behavior depends on CTranslate2 internals.
|
||||
"""
|
||||
expected_behaviors = {
|
||||
"RTX 40XX (Ampere/Ada)": "int8_float16 (fastest with tensor cores)",
|
||||
"RTX 50XX (Blackwell sm120)": "float16 (INT8 disabled by PR #1937)",
|
||||
"CPU with AVX2": "int8 (quantized, fast)",
|
||||
"CPU without AVX2": "float32 (fallback)",
|
||||
}
|
||||
|
||||
# This is documentation, not a test
|
||||
assert len(expected_behaviors) > 0
|
||||
|
||||
|
||||
class TestBackwardsCompatibility:
|
||||
"""Tests for backwards compatibility with explicit compute_type."""
|
||||
|
||||
def test_explicit_compute_type_still_works(self):
|
||||
"""Test that explicit compute_type values are still respected."""
|
||||
# When users explicitly set compute_type, it should be used
|
||||
# The "auto" is only the default from resolver
|
||||
|
||||
from whisperjav.config.resolver_v3 import _get_compute_type_for_device
|
||||
|
||||
# Resolver returns "auto" for CTranslate2 providers
|
||||
result = _get_compute_type_for_device("cuda", "faster_whisper")
|
||||
assert result == "auto"
|
||||
|
||||
# But ASR modules accept explicit overrides via model_config
|
||||
# This is tested via the actual code path:
|
||||
# model_config.get("compute_type", "auto")
|
||||
# If user provides {"compute_type": "int8"}, that value is used
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CLI Device/Compute-Type Override Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestCLIDeviceOverride:
|
||||
"""Tests for --device and --compute-type CLI argument flow."""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_registries(self):
|
||||
"""Mock component registries for testing."""
|
||||
with patch('whisperjav.config.resolver_v3.get_asr_registry') as mock_asr:
|
||||
with patch('whisperjav.config.resolver_v3.get_vad_registry') as mock_vad:
|
||||
# Setup mock ASR component
|
||||
mock_component = MagicMock()
|
||||
mock_component.provider = "faster_whisper"
|
||||
mock_component.model_id = "large-v3"
|
||||
mock_component.supported_tasks = ["transcribe"]
|
||||
mock_preset = MagicMock()
|
||||
mock_preset.model_dump.return_value = {"language": "ja", "task": "transcribe"}
|
||||
mock_component.get_preset.return_value = mock_preset
|
||||
|
||||
mock_asr.return_value = {"faster_whisper": mock_component}
|
||||
mock_vad.return_value = {}
|
||||
yield mock_asr, mock_vad
|
||||
|
||||
def test_device_override_cuda(self, mock_registries):
|
||||
"""Test that explicit device='cuda' overrides auto-detection."""
|
||||
with patch('whisperjav.config.resolver_v3.get_best_device') as mock_device:
|
||||
mock_device.return_value = "cpu" # Would auto-detect CPU
|
||||
|
||||
from whisperjav.config.resolver_v3 import resolve_config_v3
|
||||
config = resolve_config_v3(
|
||||
asr="faster_whisper",
|
||||
vad="none",
|
||||
device="cuda", # Override to CUDA
|
||||
)
|
||||
|
||||
assert config["model"]["device"] == "cuda"
|
||||
# get_best_device should NOT be called when device is explicitly provided
|
||||
# (Actually it's still called for safety, but result is ignored)
|
||||
|
||||
def test_device_override_cpu(self, mock_registries):
|
||||
"""Test that explicit device='cpu' overrides auto-detection."""
|
||||
with patch('whisperjav.config.resolver_v3.get_best_device') as mock_device:
|
||||
mock_device.return_value = "cuda" # Would auto-detect CUDA
|
||||
|
||||
from whisperjav.config.resolver_v3 import resolve_config_v3
|
||||
config = resolve_config_v3(
|
||||
asr="faster_whisper",
|
||||
vad="none",
|
||||
device="cpu", # Override to CPU
|
||||
)
|
||||
|
||||
assert config["model"]["device"] == "cpu"
|
||||
|
||||
def test_device_auto_uses_detection(self, mock_registries):
|
||||
"""Test that device='auto' uses auto-detection."""
|
||||
with patch('whisperjav.config.resolver_v3.get_best_device') as mock_device:
|
||||
mock_device.return_value = "cuda"
|
||||
|
||||
from whisperjav.config.resolver_v3 import resolve_config_v3
|
||||
config = resolve_config_v3(
|
||||
asr="faster_whisper",
|
||||
vad="none",
|
||||
device="auto", # Explicit auto
|
||||
)
|
||||
|
||||
assert config["model"]["device"] == "cuda"
|
||||
mock_device.assert_called_once()
|
||||
|
||||
def test_device_none_uses_detection(self, mock_registries):
|
||||
"""Test that device=None (default) uses auto-detection."""
|
||||
with patch('whisperjav.config.resolver_v3.get_best_device') as mock_device:
|
||||
mock_device.return_value = "cpu"
|
||||
|
||||
from whisperjav.config.resolver_v3 import resolve_config_v3
|
||||
config = resolve_config_v3(
|
||||
asr="faster_whisper",
|
||||
vad="none",
|
||||
# device not specified = None
|
||||
)
|
||||
|
||||
assert config["model"]["device"] == "cpu"
|
||||
mock_device.assert_called_once()
|
||||
|
||||
def test_compute_type_override_int8(self, mock_registries):
|
||||
"""Test that explicit compute_type='int8' overrides auto."""
|
||||
with patch('whisperjav.config.resolver_v3.get_best_device') as mock_device:
|
||||
mock_device.return_value = "cuda"
|
||||
|
||||
from whisperjav.config.resolver_v3 import resolve_config_v3
|
||||
config = resolve_config_v3(
|
||||
asr="faster_whisper",
|
||||
vad="none",
|
||||
compute_type="int8", # Override
|
||||
)
|
||||
|
||||
assert config["model"]["compute_type"] == "int8"
|
||||
|
||||
def test_compute_type_override_float16(self, mock_registries):
|
||||
"""Test that explicit compute_type='float16' overrides auto."""
|
||||
with patch('whisperjav.config.resolver_v3.get_best_device') as mock_device:
|
||||
mock_device.return_value = "cuda"
|
||||
|
||||
from whisperjav.config.resolver_v3 import resolve_config_v3
|
||||
config = resolve_config_v3(
|
||||
asr="faster_whisper",
|
||||
vad="none",
|
||||
compute_type="float16",
|
||||
)
|
||||
|
||||
assert config["model"]["compute_type"] == "float16"
|
||||
|
||||
def test_compute_type_override_int8_float16(self, mock_registries):
|
||||
"""Test that explicit compute_type='int8_float16' works."""
|
||||
with patch('whisperjav.config.resolver_v3.get_best_device') as mock_device:
|
||||
mock_device.return_value = "cuda"
|
||||
|
||||
from whisperjav.config.resolver_v3 import resolve_config_v3
|
||||
config = resolve_config_v3(
|
||||
asr="faster_whisper",
|
||||
vad="none",
|
||||
compute_type="int8_float16",
|
||||
)
|
||||
|
||||
assert config["model"]["compute_type"] == "int8_float16"
|
||||
|
||||
def test_compute_type_auto_uses_provider_default(self, mock_registries):
|
||||
"""Test that compute_type='auto' uses provider-specific default."""
|
||||
with patch('whisperjav.config.resolver_v3.get_best_device') as mock_device:
|
||||
mock_device.return_value = "cuda"
|
||||
|
||||
from whisperjav.config.resolver_v3 import resolve_config_v3
|
||||
config = resolve_config_v3(
|
||||
asr="faster_whisper",
|
||||
vad="none",
|
||||
compute_type="auto", # Explicit auto
|
||||
)
|
||||
|
||||
# CTranslate2 provider should get "auto"
|
||||
assert config["model"]["compute_type"] == "auto"
|
||||
|
||||
def test_compute_type_none_uses_provider_default(self, mock_registries):
|
||||
"""Test that compute_type=None uses provider-specific default."""
|
||||
with patch('whisperjav.config.resolver_v3.get_best_device') as mock_device:
|
||||
mock_device.return_value = "cuda"
|
||||
|
||||
from whisperjav.config.resolver_v3 import resolve_config_v3
|
||||
config = resolve_config_v3(
|
||||
asr="faster_whisper",
|
||||
vad="none",
|
||||
# compute_type not specified = None
|
||||
)
|
||||
|
||||
assert config["model"]["compute_type"] == "auto"
|
||||
|
||||
def test_combined_device_and_compute_type_override(self, mock_registries):
|
||||
"""Test that both device and compute_type can be overridden together."""
|
||||
with patch('whisperjav.config.resolver_v3.get_best_device') as mock_device:
|
||||
mock_device.return_value = "cuda" # Would detect CUDA
|
||||
|
||||
from whisperjav.config.resolver_v3 import resolve_config_v3
|
||||
config = resolve_config_v3(
|
||||
asr="faster_whisper",
|
||||
vad="none",
|
||||
device="cpu", # Force CPU
|
||||
compute_type="float32", # Force float32
|
||||
)
|
||||
|
||||
assert config["model"]["device"] == "cpu"
|
||||
assert config["model"]["compute_type"] == "float32"
|
||||
|
||||
|
||||
class TestLegacyPipelineDeviceOverride:
|
||||
"""Tests for device/compute_type override through legacy.py."""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_resolve_v3(self):
|
||||
"""Mock resolve_config_v3 to verify parameters passed."""
|
||||
with patch('whisperjav.config.legacy.resolve_config_v3') as mock:
|
||||
# Return a minimal valid config
|
||||
mock.return_value = {
|
||||
'asr_name': 'faster_whisper',
|
||||
'vad_name': 'none',
|
||||
'sensitivity_name': 'balanced',
|
||||
'task': 'transcribe',
|
||||
'language': 'ja',
|
||||
'model': {
|
||||
'provider': 'faster_whisper',
|
||||
'model_name': 'large-v3',
|
||||
'device': 'cuda',
|
||||
'compute_type': 'auto',
|
||||
'supported_tasks': ['transcribe']
|
||||
},
|
||||
'params': {'asr': {}, 'vad': {}},
|
||||
'features': {},
|
||||
}
|
||||
yield mock
|
||||
|
||||
def test_resolve_legacy_pipeline_passes_device(self, mock_resolve_v3):
|
||||
"""Test that resolve_legacy_pipeline passes device parameter."""
|
||||
from whisperjav.config.legacy import resolve_legacy_pipeline
|
||||
|
||||
resolve_legacy_pipeline(
|
||||
pipeline_name="balanced",
|
||||
sensitivity="aggressive",
|
||||
device="cpu",
|
||||
)
|
||||
|
||||
# Verify resolve_config_v3 was called with device parameter
|
||||
mock_resolve_v3.assert_called_once()
|
||||
call_kwargs = mock_resolve_v3.call_args[1]
|
||||
assert call_kwargs.get('device') == "cpu"
|
||||
|
||||
def test_resolve_legacy_pipeline_passes_compute_type(self, mock_resolve_v3):
|
||||
"""Test that resolve_legacy_pipeline passes compute_type parameter."""
|
||||
from whisperjav.config.legacy import resolve_legacy_pipeline
|
||||
|
||||
resolve_legacy_pipeline(
|
||||
pipeline_name="balanced",
|
||||
sensitivity="balanced",
|
||||
compute_type="int8_float16",
|
||||
)
|
||||
|
||||
call_kwargs = mock_resolve_v3.call_args[1]
|
||||
assert call_kwargs.get('compute_type') == "int8_float16"
|
||||
|
||||
def test_resolve_legacy_pipeline_passes_both(self, mock_resolve_v3):
|
||||
"""Test that resolve_legacy_pipeline passes both device and compute_type."""
|
||||
from whisperjav.config.legacy import resolve_legacy_pipeline
|
||||
|
||||
resolve_legacy_pipeline(
|
||||
pipeline_name="faster",
|
||||
device="cuda",
|
||||
compute_type="float16",
|
||||
)
|
||||
|
||||
call_kwargs = mock_resolve_v3.call_args[1]
|
||||
assert call_kwargs.get('device') == "cuda"
|
||||
assert call_kwargs.get('compute_type') == "float16"
|
||||
|
||||
def test_resolve_legacy_pipeline_none_defaults(self, mock_resolve_v3):
|
||||
"""Test that resolve_legacy_pipeline passes None when not specified."""
|
||||
from whisperjav.config.legacy import resolve_legacy_pipeline
|
||||
|
||||
resolve_legacy_pipeline(
|
||||
pipeline_name="balanced",
|
||||
# device and compute_type not specified
|
||||
)
|
||||
|
||||
call_kwargs = mock_resolve_v3.call_args[1]
|
||||
assert call_kwargs.get('device') is None
|
||||
assert call_kwargs.get('compute_type') is None
|
||||
|
||||
|
||||
class TestEnsembleConfigDeviceOverride:
|
||||
"""Tests for device/compute_type override through ensemble config."""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_resolve_v3(self):
|
||||
"""Mock resolve_config_v3 for ensemble config tests."""
|
||||
with patch('whisperjav.config.legacy.resolve_config_v3') as mock:
|
||||
mock.return_value = {
|
||||
'asr_name': 'faster_whisper',
|
||||
'vad_name': 'none',
|
||||
'sensitivity_name': 'balanced',
|
||||
'task': 'transcribe',
|
||||
'language': 'ja',
|
||||
'model': {
|
||||
'provider': 'faster_whisper',
|
||||
'model_name': 'large-v3',
|
||||
'device': 'cuda',
|
||||
'compute_type': 'auto',
|
||||
'supported_tasks': ['transcribe']
|
||||
},
|
||||
'params': {'asr': {}, 'vad': {}},
|
||||
'features': {},
|
||||
}
|
||||
yield mock
|
||||
|
||||
def test_resolve_ensemble_config_passes_device(self, mock_resolve_v3):
|
||||
"""Test that resolve_ensemble_config passes device parameter."""
|
||||
from whisperjav.config.legacy import resolve_ensemble_config
|
||||
|
||||
resolve_ensemble_config(
|
||||
asr="faster_whisper",
|
||||
device="cpu",
|
||||
)
|
||||
|
||||
call_kwargs = mock_resolve_v3.call_args[1]
|
||||
assert call_kwargs.get('device') == "cpu"
|
||||
|
||||
def test_resolve_ensemble_config_passes_compute_type(self, mock_resolve_v3):
|
||||
"""Test that resolve_ensemble_config passes compute_type parameter."""
|
||||
from whisperjav.config.legacy import resolve_ensemble_config
|
||||
|
||||
resolve_ensemble_config(
|
||||
asr="faster_whisper",
|
||||
compute_type="int8",
|
||||
)
|
||||
|
||||
call_kwargs = mock_resolve_v3.call_args[1]
|
||||
assert call_kwargs.get('compute_type') == "int8"
|
||||
|
||||
|
||||
class TestCLIArgumentParsing:
|
||||
"""Tests for CLI argument parsing in main.py."""
|
||||
|
||||
def test_device_argument_choices(self):
|
||||
"""Test that --device accepts valid choices."""
|
||||
from whisperjav.main import parse_arguments
|
||||
import sys
|
||||
|
||||
# Test with valid choices
|
||||
for device in ["auto", "cuda", "cpu"]:
|
||||
with patch.object(sys, 'argv', ['whisperjav', 'test.mp4', f'--device={device}']):
|
||||
args = parse_arguments()
|
||||
assert args.device == device
|
||||
|
||||
def test_compute_type_argument_choices(self):
|
||||
"""Test that --compute-type accepts valid choices."""
|
||||
from whisperjav.main import parse_arguments
|
||||
import sys
|
||||
|
||||
valid_types = ["auto", "float16", "float32", "int8", "int8_float16", "int8_float32"]
|
||||
for compute_type in valid_types:
|
||||
with patch.object(sys, 'argv', ['whisperjav', 'test.mp4', f'--compute-type={compute_type}']):
|
||||
args = parse_arguments()
|
||||
assert args.compute_type == compute_type
|
||||
|
||||
def test_device_default_is_none(self):
|
||||
"""Test that --device defaults to None (auto-detect)."""
|
||||
from whisperjav.main import parse_arguments
|
||||
import sys
|
||||
|
||||
with patch.object(sys, 'argv', ['whisperjav', 'test.mp4']):
|
||||
args = parse_arguments()
|
||||
assert args.device is None
|
||||
|
||||
def test_compute_type_default_is_none(self):
|
||||
"""Test that --compute-type defaults to None (auto)."""
|
||||
from whisperjav.main import parse_arguments
|
||||
import sys
|
||||
|
||||
with patch.object(sys, 'argv', ['whisperjav', 'test.mp4']):
|
||||
args = parse_arguments()
|
||||
assert args.compute_type is None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
|
|
@ -0,0 +1,661 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Unit tests for WhisperJAV GUI Update Check API.
|
||||
|
||||
Tests the update check modal feature including:
|
||||
- check_for_updates() API method
|
||||
- dismiss_update_notification() API method
|
||||
- get_dismissed_update() API method
|
||||
- open_url() API method
|
||||
- start_update() API method
|
||||
|
||||
Run with: pytest tests/test_update_check_api.py -v
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import pytest
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import NamedTuple
|
||||
from unittest.mock import patch, MagicMock, Mock
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Mock Classes to simulate version_checker types
|
||||
# =============================================================================
|
||||
|
||||
class MockVersionInfo(NamedTuple):
|
||||
"""Mock VersionInfo to avoid importing actual module."""
|
||||
version: str
|
||||
release_url: str
|
||||
release_notes: str
|
||||
published_at: str
|
||||
is_prerelease: bool
|
||||
installer_url: str = None
|
||||
|
||||
|
||||
class MockUpdateCheckResult(NamedTuple):
|
||||
"""Mock UpdateCheckResult to avoid importing actual module."""
|
||||
update_available: bool
|
||||
current_version: str
|
||||
latest_version: str
|
||||
version_info: MockVersionInfo = None
|
||||
from_cache: bool = False
|
||||
error: str = None
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Fixtures
|
||||
# =============================================================================
|
||||
|
||||
@pytest.fixture
|
||||
def temp_cache_dir(tmp_path):
|
||||
"""Create a temporary cache directory."""
|
||||
cache_dir = tmp_path / ".whisperjav_cache"
|
||||
cache_dir.mkdir()
|
||||
return cache_dir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_api(tmp_path, monkeypatch):
|
||||
"""
|
||||
Create a mock API instance with patched dependencies.
|
||||
|
||||
This patches the environment to use temp directories and
|
||||
avoids importing heavy dependencies like webview.
|
||||
"""
|
||||
# Patch LOCALAPPDATA to use temp directory
|
||||
monkeypatch.setenv('LOCALAPPDATA', str(tmp_path))
|
||||
|
||||
# Create a minimal API class for testing
|
||||
# We import just the methods we need to test
|
||||
from whisperjav.webview_gui.api import WhisperJAVAPI
|
||||
|
||||
# Create API instance (may need window mock)
|
||||
with patch('webview.create_window'):
|
||||
api = WhisperJAVAPI.__new__(WhisperJAVAPI)
|
||||
api.window = None
|
||||
api._process = None
|
||||
api._output_queue = None
|
||||
api._log_thread = None
|
||||
api._status = {"running": False}
|
||||
|
||||
return api
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_update_result():
|
||||
"""Sample update check result with update available."""
|
||||
return MockUpdateCheckResult(
|
||||
update_available=True,
|
||||
current_version="1.7.5",
|
||||
latest_version="1.8.0",
|
||||
version_info=MockVersionInfo(
|
||||
version="1.8.0",
|
||||
release_url="https://github.com/meizhong986/whisperjav/releases/tag/v1.8.0",
|
||||
release_notes="## What's New\n- Feature A\n- Feature B\n- Bug fixes",
|
||||
published_at="2025-01-15T10:00:00Z",
|
||||
is_prerelease=False,
|
||||
installer_url="https://github.com/meizhong986/whisperjav/releases/download/v1.8.0/WhisperJAV-1.8.0-Windows-x86_64.exe"
|
||||
),
|
||||
from_cache=False,
|
||||
error=None
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_no_update_result():
|
||||
"""Sample update check result with no update available."""
|
||||
return MockUpdateCheckResult(
|
||||
update_available=False,
|
||||
current_version="1.8.0",
|
||||
latest_version="1.8.0",
|
||||
version_info=None,
|
||||
from_cache=True,
|
||||
error=None
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_error_result():
|
||||
"""Sample update check result with error."""
|
||||
return MockUpdateCheckResult(
|
||||
update_available=False,
|
||||
current_version="1.7.5",
|
||||
latest_version=None,
|
||||
version_info=None,
|
||||
from_cache=False,
|
||||
error="Network error: Unable to connect to GitHub"
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# check_for_updates() Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestCheckForUpdates:
|
||||
"""Tests for check_for_updates() API method."""
|
||||
|
||||
def test_update_available_returns_correct_structure(self, mock_api, sample_update_result):
|
||||
"""Test that update available response has correct structure."""
|
||||
with patch('whisperjav.version_checker.check_for_updates', return_value=sample_update_result):
|
||||
with patch('whisperjav.version_checker.get_update_notification_level', return_value='minor'):
|
||||
result = mock_api.check_for_updates(force=True)
|
||||
|
||||
assert result["success"] is True
|
||||
assert result["update_available"] is True
|
||||
assert result["current_version"] == "1.7.5"
|
||||
assert result["latest_version"] == "1.8.0"
|
||||
assert result["notification_level"] == "minor"
|
||||
assert result["release_url"] is not None
|
||||
assert result["release_notes"] is not None
|
||||
assert result["from_cache"] is False
|
||||
|
||||
def test_no_update_available(self, mock_api, sample_no_update_result):
|
||||
"""Test response when no update is available."""
|
||||
with patch('whisperjav.version_checker.check_for_updates', return_value=sample_no_update_result):
|
||||
result = mock_api.check_for_updates(force=False)
|
||||
|
||||
assert result["success"] is True
|
||||
assert result["update_available"] is False
|
||||
assert result["current_version"] == "1.8.0"
|
||||
assert result["from_cache"] is True
|
||||
# No notification_level, release_url, release_notes when no update
|
||||
assert "notification_level" not in result or result.get("notification_level") is None
|
||||
|
||||
def test_error_handling(self, mock_api, sample_error_result):
|
||||
"""Test response when update check fails."""
|
||||
with patch('whisperjav.version_checker.check_for_updates', return_value=sample_error_result):
|
||||
result = mock_api.check_for_updates(force=True)
|
||||
|
||||
assert result["success"] is True # Method succeeded, but check had error
|
||||
assert result["update_available"] is False
|
||||
assert "error" in result
|
||||
assert "Network error" in result["error"]
|
||||
|
||||
def test_exception_handling(self, mock_api):
|
||||
"""Test that exceptions are caught and returned as error."""
|
||||
with patch('whisperjav.version_checker.check_for_updates', side_effect=Exception("Import failed")):
|
||||
result = mock_api.check_for_updates(force=True)
|
||||
|
||||
assert result["success"] is False
|
||||
assert "Import failed" in result["error"]
|
||||
assert result["update_available"] is False
|
||||
|
||||
def test_force_bypasses_cache(self, mock_api, sample_update_result):
|
||||
"""Test that force=True bypasses cache."""
|
||||
mock_check = Mock(return_value=sample_update_result)
|
||||
with patch('whisperjav.version_checker.check_for_updates', mock_check):
|
||||
with patch('whisperjav.version_checker.get_update_notification_level', return_value='minor'):
|
||||
mock_api.check_for_updates(force=True)
|
||||
|
||||
# Verify force=True was passed
|
||||
mock_check.assert_called_once_with(force=True)
|
||||
|
||||
def test_release_notes_truncation(self, mock_api):
|
||||
"""Test that long release notes are truncated."""
|
||||
long_notes = "A" * 1000 # 1000 character release notes
|
||||
result_with_long_notes = MockUpdateCheckResult(
|
||||
update_available=True,
|
||||
current_version="1.7.5",
|
||||
latest_version="1.8.0",
|
||||
version_info=MockVersionInfo(
|
||||
version="1.8.0",
|
||||
release_url="https://example.com",
|
||||
release_notes=long_notes,
|
||||
published_at="2025-01-15T10:00:00Z",
|
||||
is_prerelease=False
|
||||
),
|
||||
from_cache=False,
|
||||
error=None
|
||||
)
|
||||
|
||||
with patch('whisperjav.version_checker.check_for_updates', return_value=result_with_long_notes):
|
||||
with patch('whisperjav.version_checker.get_update_notification_level', return_value='minor'):
|
||||
result = mock_api.check_for_updates(force=True)
|
||||
|
||||
# Release notes should be truncated to 500 chars
|
||||
assert len(result["release_notes"]) <= 500
|
||||
|
||||
|
||||
class TestNotificationLevels:
|
||||
"""Tests for notification level classification."""
|
||||
|
||||
@pytest.mark.parametrize("current,latest,expected_level", [
|
||||
("1.7.5", "1.7.6", "patch"), # Patch update
|
||||
("1.7.5", "1.8.0", "minor"), # Minor update
|
||||
("1.7.5", "2.0.0", "major"), # Major update
|
||||
("1.7.5", "1.7.5", "none"), # No update
|
||||
])
|
||||
def test_notification_level_classification(self, mock_api, current, latest, expected_level):
|
||||
"""Test that notification levels are correctly classified."""
|
||||
result = MockUpdateCheckResult(
|
||||
update_available=(current != latest),
|
||||
current_version=current,
|
||||
latest_version=latest,
|
||||
version_info=MockVersionInfo(
|
||||
version=latest,
|
||||
release_url="https://example.com",
|
||||
release_notes="Test notes",
|
||||
published_at="2025-01-15T10:00:00Z",
|
||||
is_prerelease=False
|
||||
) if current != latest else None,
|
||||
from_cache=False,
|
||||
error=None
|
||||
)
|
||||
|
||||
with patch('whisperjav.version_checker.check_for_updates', return_value=result):
|
||||
with patch('whisperjav.version_checker.get_update_notification_level', return_value=expected_level):
|
||||
response = mock_api.check_for_updates(force=True)
|
||||
|
||||
if current != latest:
|
||||
assert response.get("notification_level") == expected_level
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# dismiss_update_notification() Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestDismissUpdateNotification:
|
||||
"""Tests for dismiss_update_notification() API method."""
|
||||
|
||||
def test_dismiss_creates_file(self, mock_api, tmp_path, monkeypatch):
|
||||
"""Test that dismissing creates a dismissal file."""
|
||||
monkeypatch.setenv('LOCALAPPDATA', str(tmp_path))
|
||||
|
||||
result = mock_api.dismiss_update_notification("1.8.0")
|
||||
|
||||
assert result["success"] is True
|
||||
|
||||
# Check file was created
|
||||
dismissal_file = tmp_path / '.whisperjav_cache' / 'update_dismissed.json'
|
||||
assert dismissal_file.exists()
|
||||
|
||||
# Verify contents
|
||||
with open(dismissal_file) as f:
|
||||
data = json.load(f)
|
||||
assert data["version"] == "1.8.0"
|
||||
assert "dismissed_at" in data
|
||||
|
||||
def test_dismiss_overwrites_previous(self, mock_api, tmp_path, monkeypatch):
|
||||
"""Test that new dismissal overwrites previous."""
|
||||
monkeypatch.setenv('LOCALAPPDATA', str(tmp_path))
|
||||
|
||||
# First dismissal
|
||||
mock_api.dismiss_update_notification("1.7.6")
|
||||
|
||||
# Second dismissal
|
||||
mock_api.dismiss_update_notification("1.8.0")
|
||||
|
||||
# Check only latest version is stored
|
||||
dismissal_file = tmp_path / '.whisperjav_cache' / 'update_dismissed.json'
|
||||
with open(dismissal_file) as f:
|
||||
data = json.load(f)
|
||||
assert data["version"] == "1.8.0"
|
||||
|
||||
def test_dismiss_error_handling(self, mock_api, monkeypatch):
|
||||
"""Test error handling when dismissal fails."""
|
||||
# Mock the file open to raise an error
|
||||
with patch('builtins.open', side_effect=PermissionError("Access denied")):
|
||||
with patch('pathlib.Path.mkdir'): # Don't fail on mkdir
|
||||
result = mock_api.dismiss_update_notification("1.8.0")
|
||||
|
||||
# Should return error, not crash
|
||||
assert result["success"] is False
|
||||
assert "error" in result
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# get_dismissed_update() Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestGetDismissedUpdate:
|
||||
"""Tests for get_dismissed_update() API method."""
|
||||
|
||||
def test_get_dismissed_when_exists(self, mock_api, tmp_path, monkeypatch):
|
||||
"""Test retrieving dismissed update info."""
|
||||
monkeypatch.setenv('LOCALAPPDATA', str(tmp_path))
|
||||
|
||||
# Create dismissal first
|
||||
mock_api.dismiss_update_notification("1.8.0")
|
||||
|
||||
result = mock_api.get_dismissed_update()
|
||||
|
||||
assert result["success"] is True
|
||||
assert result["dismissed"]["version"] == "1.8.0"
|
||||
assert "dismissed_at" in result["dismissed"]
|
||||
|
||||
def test_get_dismissed_when_not_exists(self, mock_api, tmp_path, monkeypatch):
|
||||
"""Test when no dismissal exists."""
|
||||
monkeypatch.setenv('LOCALAPPDATA', str(tmp_path))
|
||||
# Don't create any dismissal file
|
||||
|
||||
result = mock_api.get_dismissed_update()
|
||||
|
||||
assert result["success"] is True
|
||||
assert result["dismissed"] is None
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# open_url() Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestOpenUrl:
|
||||
"""Tests for open_url() API method."""
|
||||
|
||||
def test_open_url_success(self, mock_api):
|
||||
"""Test successful URL opening."""
|
||||
with patch('webbrowser.open', return_value=True) as mock_open:
|
||||
result = mock_api.open_url("https://github.com/meizhong986/WhisperJAV/releases")
|
||||
|
||||
assert result["success"] is True
|
||||
mock_open.assert_called_once_with("https://github.com/meizhong986/WhisperJAV/releases")
|
||||
|
||||
def test_open_url_github_releases(self, mock_api):
|
||||
"""Test opening GitHub releases page specifically."""
|
||||
with patch('webbrowser.open', return_value=True) as mock_open:
|
||||
result = mock_api.open_url("https://github.com/meizhong986/WhisperJAV/releases")
|
||||
|
||||
assert result["success"] is True
|
||||
# Verify the exact URL used for major updates
|
||||
mock_open.assert_called_with("https://github.com/meizhong986/WhisperJAV/releases")
|
||||
|
||||
def test_open_url_error_handling(self, mock_api):
|
||||
"""Test error handling when browser fails to open."""
|
||||
with patch('webbrowser.open', side_effect=Exception("No browser available")):
|
||||
result = mock_api.open_url("https://example.com")
|
||||
|
||||
assert result["success"] is False
|
||||
assert "No browser available" in result["error"]
|
||||
|
||||
def test_open_url_various_urls(self, mock_api):
|
||||
"""Test opening various URL types."""
|
||||
urls = [
|
||||
"https://github.com/meizhong986/WhisperJAV",
|
||||
"https://github.com/meizhong986/WhisperJAV/releases/tag/v1.8.0",
|
||||
"https://github.com/meizhong986/WhisperJAV/issues",
|
||||
]
|
||||
|
||||
for url in urls:
|
||||
with patch('webbrowser.open', return_value=True) as mock_open:
|
||||
result = mock_api.open_url(url)
|
||||
assert result["success"] is True
|
||||
mock_open.assert_called_with(url)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# start_update() Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestStartUpdate:
|
||||
"""Tests for start_update() API method."""
|
||||
|
||||
def test_start_update_spawns_wrapper(self, mock_api, monkeypatch):
|
||||
"""Test that start_update spawns the update wrapper process."""
|
||||
mock_popen = MagicMock()
|
||||
|
||||
with patch('subprocess.Popen', mock_popen):
|
||||
with patch.object(Path, 'exists', return_value=True):
|
||||
result = mock_api.start_update(wheel_only=False)
|
||||
|
||||
assert result["success"] is True
|
||||
assert result["should_exit"] is True
|
||||
assert "Update process started" in result["message"]
|
||||
|
||||
# Verify Popen was called
|
||||
mock_popen.assert_called_once()
|
||||
call_args = mock_popen.call_args
|
||||
cmd = call_args[0][0]
|
||||
|
||||
# Should include update_wrapper module
|
||||
assert "-m" in cmd
|
||||
assert "whisperjav.update_wrapper" in cmd
|
||||
|
||||
def test_start_update_with_wheel_only(self, mock_api):
|
||||
"""Test start_update with wheel_only=True."""
|
||||
mock_popen = MagicMock()
|
||||
|
||||
with patch('subprocess.Popen', mock_popen):
|
||||
with patch.object(Path, 'exists', return_value=True):
|
||||
result = mock_api.start_update(wheel_only=True)
|
||||
|
||||
assert result["success"] is True
|
||||
|
||||
# Verify --wheel-only flag is present
|
||||
call_args = mock_popen.call_args
|
||||
cmd = call_args[0][0]
|
||||
assert "--wheel-only" in cmd
|
||||
|
||||
def test_start_update_includes_pid(self, mock_api):
|
||||
"""Test that start_update passes current PID to wrapper."""
|
||||
mock_popen = MagicMock()
|
||||
current_pid = os.getpid()
|
||||
|
||||
with patch('subprocess.Popen', mock_popen):
|
||||
with patch.object(Path, 'exists', return_value=True):
|
||||
mock_api.start_update()
|
||||
|
||||
call_args = mock_popen.call_args
|
||||
cmd = call_args[0][0]
|
||||
|
||||
# Should include --pid argument
|
||||
assert "--pid" in cmd
|
||||
pid_idx = cmd.index("--pid")
|
||||
assert cmd[pid_idx + 1] == str(current_pid)
|
||||
|
||||
def test_start_update_error_handling(self, mock_api):
|
||||
"""Test error handling when spawn fails."""
|
||||
with patch('subprocess.Popen', side_effect=Exception("Spawn failed")):
|
||||
with patch.object(Path, 'exists', return_value=True):
|
||||
result = mock_api.start_update()
|
||||
|
||||
assert result["success"] is False
|
||||
assert "Spawn failed" in result["error"]
|
||||
assert result["should_exit"] is False
|
||||
|
||||
@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific test")
|
||||
def test_start_update_windows_detached(self, mock_api):
|
||||
"""Test that Windows uses DETACHED_PROCESS flag."""
|
||||
mock_popen = MagicMock()
|
||||
|
||||
with patch('subprocess.Popen', mock_popen):
|
||||
with patch.object(Path, 'exists', return_value=True):
|
||||
mock_api.start_update()
|
||||
|
||||
call_kwargs = mock_popen.call_args[1]
|
||||
|
||||
# Should use Windows-specific creation flags
|
||||
assert "creationflags" in call_kwargs
|
||||
expected_flags = subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
|
||||
assert call_kwargs["creationflags"] == expected_flags
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Integration Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestUpdateCheckIntegration:
|
||||
"""Integration tests for the complete update check flow."""
|
||||
|
||||
def test_full_update_check_flow(self, mock_api, sample_update_result, tmp_path, monkeypatch):
|
||||
"""Test complete flow: check -> show update -> user action."""
|
||||
monkeypatch.setenv('LOCALAPPDATA', str(tmp_path))
|
||||
|
||||
# Step 1: Check for updates
|
||||
with patch('whisperjav.version_checker.check_for_updates', return_value=sample_update_result):
|
||||
with patch('whisperjav.version_checker.get_update_notification_level', return_value='minor'):
|
||||
check_result = mock_api.check_for_updates(force=True)
|
||||
|
||||
assert check_result["update_available"] is True
|
||||
assert check_result["notification_level"] == "minor"
|
||||
|
||||
# Step 2: User decides to dismiss
|
||||
dismiss_result = mock_api.dismiss_update_notification(check_result["latest_version"])
|
||||
assert dismiss_result["success"] is True
|
||||
|
||||
# Step 3: Verify dismissal was recorded
|
||||
dismissed = mock_api.get_dismissed_update()
|
||||
assert dismissed["dismissed"]["version"] == "1.8.0"
|
||||
|
||||
def test_major_update_triggers_download(self, mock_api, tmp_path, monkeypatch):
|
||||
"""Test that major updates trigger download page instead of update."""
|
||||
monkeypatch.setenv('LOCALAPPDATA', str(tmp_path))
|
||||
|
||||
major_update = MockUpdateCheckResult(
|
||||
update_available=True,
|
||||
current_version="1.7.5",
|
||||
latest_version="2.0.0",
|
||||
version_info=MockVersionInfo(
|
||||
version="2.0.0",
|
||||
release_url="https://github.com/meizhong986/whisperjav/releases/tag/v2.0.0",
|
||||
release_notes="## Breaking Changes\n- New architecture",
|
||||
published_at="2025-01-15T10:00:00Z",
|
||||
is_prerelease=False
|
||||
),
|
||||
from_cache=False,
|
||||
error=None
|
||||
)
|
||||
|
||||
with patch('whisperjav.version_checker.check_for_updates', return_value=major_update):
|
||||
with patch('whisperjav.version_checker.get_update_notification_level', return_value='major'):
|
||||
result = mock_api.check_for_updates(force=True)
|
||||
|
||||
# For major updates, frontend should use open_url() instead of start_update()
|
||||
assert result["notification_level"] == "major"
|
||||
|
||||
# Verify open_url works for this case
|
||||
with patch('webbrowser.open', return_value=True) as mock_open:
|
||||
url_result = mock_api.open_url("https://github.com/meizhong986/WhisperJAV/releases")
|
||||
|
||||
assert url_result["success"] is True
|
||||
|
||||
def test_patch_update_triggers_inline_update(self, mock_api, tmp_path, monkeypatch):
|
||||
"""Test that patch updates trigger inline update."""
|
||||
monkeypatch.setenv('LOCALAPPDATA', str(tmp_path))
|
||||
|
||||
patch_update = MockUpdateCheckResult(
|
||||
update_available=True,
|
||||
current_version="1.7.5",
|
||||
latest_version="1.7.6",
|
||||
version_info=MockVersionInfo(
|
||||
version="1.7.6",
|
||||
release_url="https://github.com/meizhong986/whisperjav/releases/tag/v1.7.6",
|
||||
release_notes="## Bug Fixes\n- Fixed MPS fallback",
|
||||
published_at="2025-01-15T10:00:00Z",
|
||||
is_prerelease=False
|
||||
),
|
||||
from_cache=False,
|
||||
error=None
|
||||
)
|
||||
|
||||
with patch('whisperjav.version_checker.check_for_updates', return_value=patch_update):
|
||||
with patch('whisperjav.version_checker.get_update_notification_level', return_value='patch'):
|
||||
result = mock_api.check_for_updates(force=True)
|
||||
|
||||
# For patch updates, frontend should use start_update()
|
||||
assert result["notification_level"] == "patch"
|
||||
|
||||
# Verify start_update works
|
||||
mock_popen = MagicMock()
|
||||
with patch('subprocess.Popen', mock_popen):
|
||||
with patch.object(Path, 'exists', return_value=True):
|
||||
update_result = mock_api.start_update(wheel_only=False)
|
||||
|
||||
assert update_result["success"] is True
|
||||
assert update_result["should_exit"] is True
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Edge Cases and Error Handling
|
||||
# =============================================================================
|
||||
|
||||
class TestEdgeCases:
|
||||
"""Tests for edge cases and error conditions."""
|
||||
|
||||
def test_network_timeout_handling(self, mock_api):
|
||||
"""Test handling of network timeout."""
|
||||
timeout_result = MockUpdateCheckResult(
|
||||
update_available=False,
|
||||
current_version="1.7.5",
|
||||
latest_version=None,
|
||||
version_info=None,
|
||||
from_cache=False,
|
||||
error="Connection timed out after 10 seconds"
|
||||
)
|
||||
|
||||
with patch('whisperjav.version_checker.check_for_updates', return_value=timeout_result):
|
||||
result = mock_api.check_for_updates(force=True)
|
||||
|
||||
assert result["update_available"] is False
|
||||
assert "timed out" in result["error"]
|
||||
|
||||
def test_invalid_version_format(self, mock_api):
|
||||
"""Test handling of invalid version format."""
|
||||
invalid_result = MockUpdateCheckResult(
|
||||
update_available=False,
|
||||
current_version="unknown",
|
||||
latest_version=None,
|
||||
version_info=None,
|
||||
from_cache=False,
|
||||
error="Could not parse version"
|
||||
)
|
||||
|
||||
with patch('whisperjav.version_checker.check_for_updates', return_value=invalid_result):
|
||||
result = mock_api.check_for_updates(force=True)
|
||||
|
||||
assert result["success"] is True # API call succeeded
|
||||
assert "error" in result
|
||||
|
||||
def test_prerelease_handling(self, mock_api):
|
||||
"""Test that prerelease versions are identified."""
|
||||
prerelease = MockUpdateCheckResult(
|
||||
update_available=True,
|
||||
current_version="1.7.5",
|
||||
latest_version="1.8.0-rc1",
|
||||
version_info=MockVersionInfo(
|
||||
version="1.8.0-rc1",
|
||||
release_url="https://example.com",
|
||||
release_notes="Release candidate",
|
||||
published_at="2025-01-15T10:00:00Z",
|
||||
is_prerelease=True
|
||||
),
|
||||
from_cache=False,
|
||||
error=None
|
||||
)
|
||||
|
||||
with patch('whisperjav.version_checker.check_for_updates', return_value=prerelease):
|
||||
with patch('whisperjav.version_checker.get_update_notification_level', return_value='minor'):
|
||||
result = mock_api.check_for_updates(force=True)
|
||||
|
||||
assert result["is_prerelease"] is True
|
||||
|
||||
def test_empty_release_notes(self, mock_api):
|
||||
"""Test handling of empty release notes."""
|
||||
empty_notes = MockUpdateCheckResult(
|
||||
update_available=True,
|
||||
current_version="1.7.5",
|
||||
latest_version="1.7.6",
|
||||
version_info=MockVersionInfo(
|
||||
version="1.7.6",
|
||||
release_url="https://example.com",
|
||||
release_notes="",
|
||||
published_at="2025-01-15T10:00:00Z",
|
||||
is_prerelease=False
|
||||
),
|
||||
from_cache=False,
|
||||
error=None
|
||||
)
|
||||
|
||||
with patch('whisperjav.version_checker.check_for_updates', return_value=empty_notes):
|
||||
with patch('whisperjav.version_checker.get_update_notification_level', return_value='patch'):
|
||||
result = mock_api.check_for_updates(force=True)
|
||||
|
||||
assert result["release_notes"] == ""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
|
|
@ -0,0 +1,517 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Frontend validation tests for WhisperJAV GUI Update Check feature.
|
||||
|
||||
Tests the HTML structure, CSS selectors, and JavaScript syntax for the
|
||||
"Check for Updates" modal and menu integration.
|
||||
|
||||
These tests validate:
|
||||
- Required HTML elements exist with correct IDs
|
||||
- CSS classes are properly defined
|
||||
- JavaScript UpdateCheckManager has required methods
|
||||
- Event handlers reference existing elements
|
||||
|
||||
Run with: pytest tests/test_update_check_frontend.py -v
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Fixtures
|
||||
# =============================================================================
|
||||
|
||||
@pytest.fixture
|
||||
def assets_dir():
|
||||
"""Get the path to the webview_gui assets directory."""
|
||||
repo_root = Path(__file__).parent.parent
|
||||
return repo_root / "whisperjav" / "webview_gui" / "assets"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def index_html(assets_dir):
|
||||
"""Load the index.html content."""
|
||||
html_path = assets_dir / "index.html"
|
||||
assert html_path.exists(), f"index.html not found at {html_path}"
|
||||
return html_path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app_js(assets_dir):
|
||||
"""Load the app.js content."""
|
||||
js_path = assets_dir / "app.js"
|
||||
assert js_path.exists(), f"app.js not found at {js_path}"
|
||||
return js_path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def style_css(assets_dir):
|
||||
"""Load the style.css content."""
|
||||
css_path = assets_dir / "style.css"
|
||||
assert css_path.exists(), f"style.css not found at {css_path}"
|
||||
return css_path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# HTML Structure Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestHtmlStructure:
|
||||
"""Tests for required HTML elements in index.html."""
|
||||
|
||||
def test_theme_menu_exists(self, index_html):
|
||||
"""Test that theme menu container exists."""
|
||||
assert 'id="themeMenu"' in index_html
|
||||
|
||||
def test_check_updates_button_exists(self, index_html):
|
||||
"""Test that Check for Updates button exists."""
|
||||
assert 'id="checkUpdatesBtn"' in index_html
|
||||
|
||||
def test_check_updates_button_in_theme_menu(self, index_html):
|
||||
"""Test that button is inside theme menu."""
|
||||
# Extract theme menu content
|
||||
menu_match = re.search(r'id="themeMenu"[^>]*>(.*?)</div>\s*</div>', index_html, re.DOTALL)
|
||||
assert menu_match, "Could not find themeMenu content"
|
||||
menu_content = menu_match.group(1)
|
||||
assert 'checkUpdatesBtn' in menu_content
|
||||
|
||||
def test_theme_menu_divider_exists(self, index_html):
|
||||
"""Test that menu divider exists before update check option."""
|
||||
assert 'class="theme-menu-divider"' in index_html
|
||||
|
||||
def test_update_check_icon_exists(self, index_html):
|
||||
"""Test that update icon span exists."""
|
||||
assert 'class="update-check-icon"' in index_html
|
||||
|
||||
def test_modal_overlay_exists(self, index_html):
|
||||
"""Test that update check modal overlay exists."""
|
||||
assert 'id="updateCheckModal"' in index_html
|
||||
|
||||
def test_modal_close_button_exists(self, index_html):
|
||||
"""Test that modal close button exists."""
|
||||
assert 'id="updateCheckModalClose"' in index_html
|
||||
|
||||
def test_loading_state_exists(self, index_html):
|
||||
"""Test that loading state container exists."""
|
||||
assert 'id="updateCheckLoading"' in index_html
|
||||
|
||||
def test_result_state_exists(self, index_html):
|
||||
"""Test that result state container exists."""
|
||||
assert 'id="updateCheckResult"' in index_html
|
||||
|
||||
def test_error_state_exists(self, index_html):
|
||||
"""Test that error state container exists."""
|
||||
assert 'id="updateCheckError"' in index_html
|
||||
|
||||
def test_version_elements_exist(self, index_html):
|
||||
"""Test that version display elements exist."""
|
||||
required_ids = [
|
||||
"updateCurrentVersion",
|
||||
"updateLatestVersion",
|
||||
"updateTypeBadge",
|
||||
]
|
||||
for element_id in required_ids:
|
||||
assert f'id="{element_id}"' in index_html, f"Missing element: {element_id}"
|
||||
|
||||
def test_up_to_date_message_exists(self, index_html):
|
||||
"""Test that up-to-date message exists."""
|
||||
assert 'id="updateUpToDate"' in index_html
|
||||
|
||||
def test_update_available_section_exists(self, index_html):
|
||||
"""Test that update available section exists."""
|
||||
assert 'id="updateAvailableSection"' in index_html
|
||||
|
||||
def test_release_notes_container_exists(self, index_html):
|
||||
"""Test that release notes container exists."""
|
||||
assert 'id="updateReleaseNotes"' in index_html
|
||||
|
||||
def test_action_buttons_exist(self, index_html):
|
||||
"""Test that action buttons exist."""
|
||||
required_buttons = [
|
||||
"updateCheckLater",
|
||||
"updateCheckNow",
|
||||
"updateCheckDownload",
|
||||
]
|
||||
for button_id in required_buttons:
|
||||
assert f'id="{button_id}"' in index_html, f"Missing button: {button_id}"
|
||||
|
||||
def test_modal_has_correct_class(self, index_html):
|
||||
"""Test that modal uses correct overlay class."""
|
||||
assert 'class="modal-overlay" id="updateCheckModal"' in index_html or \
|
||||
'id="updateCheckModal" class="modal-overlay"' in index_html
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CSS Validation Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestCssClasses:
|
||||
"""Tests for required CSS classes in style.css."""
|
||||
|
||||
def test_theme_menu_divider_style(self, style_css):
|
||||
"""Test that theme-menu-divider class is defined."""
|
||||
assert ".theme-menu-divider" in style_css
|
||||
|
||||
def test_update_check_option_style(self, style_css):
|
||||
"""Test that update-check-option class is defined."""
|
||||
assert ".update-check-option" in style_css
|
||||
|
||||
def test_update_check_icon_style(self, style_css):
|
||||
"""Test that update-check-icon class is defined."""
|
||||
assert ".update-check-icon" in style_css
|
||||
|
||||
def test_update_check_loading_style(self, style_css):
|
||||
"""Test that update-check-loading class is defined."""
|
||||
assert ".update-check-loading" in style_css
|
||||
|
||||
def test_spinner_style(self, style_css):
|
||||
"""Test that spinner class is defined."""
|
||||
assert ".spinner" in style_css or ".update-check-loading .spinner" in style_css
|
||||
|
||||
def test_version_info_styles(self, style_css):
|
||||
"""Test that version info styles are defined."""
|
||||
assert ".version-info" in style_css
|
||||
assert ".version-row" in style_css
|
||||
|
||||
def test_update_badge_styles(self, style_css):
|
||||
"""Test that update badge styles are defined."""
|
||||
assert ".update-badge" in style_css
|
||||
assert ".update-badge.patch" in style_css
|
||||
assert ".update-badge.minor" in style_css
|
||||
assert ".update-badge.major" in style_css
|
||||
assert ".update-badge.critical" in style_css
|
||||
|
||||
def test_up_to_date_style(self, style_css):
|
||||
"""Test that up-to-date message style is defined."""
|
||||
assert ".update-up-to-date" in style_css
|
||||
|
||||
def test_release_notes_styles(self, style_css):
|
||||
"""Test that release notes styles are defined."""
|
||||
assert ".release-notes-details" in style_css
|
||||
assert ".release-notes-content" in style_css
|
||||
|
||||
def test_update_check_error_style(self, style_css):
|
||||
"""Test that error state style is defined."""
|
||||
assert ".update-check-error" in style_css
|
||||
|
||||
def test_spin_animation_exists(self, style_css):
|
||||
"""Test that spin keyframe animation exists."""
|
||||
assert "@keyframes spin" in style_css
|
||||
|
||||
|
||||
class TestCssColorScheme:
|
||||
"""Tests for consistent color scheme in badge styles."""
|
||||
|
||||
def test_patch_badge_color(self, style_css):
|
||||
"""Test that patch badge has green color."""
|
||||
# Find the patch badge rule
|
||||
match = re.search(r'\.update-badge\.patch\s*\{([^}]+)\}', style_css)
|
||||
assert match, "Patch badge style not found"
|
||||
assert "#28a745" in match.group(1) or "green" in match.group(1).lower()
|
||||
|
||||
def test_minor_badge_color(self, style_css):
|
||||
"""Test that minor badge has blue color."""
|
||||
match = re.search(r'\.update-badge\.minor\s*\{([^}]+)\}', style_css)
|
||||
assert match, "Minor badge style not found"
|
||||
assert "#17a2b8" in match.group(1) or "blue" in match.group(1).lower()
|
||||
|
||||
def test_major_badge_color(self, style_css):
|
||||
"""Test that major badge has orange color."""
|
||||
match = re.search(r'\.update-badge\.major\s*\{([^}]+)\}', style_css)
|
||||
assert match, "Major badge style not found"
|
||||
assert "#fd7e14" in match.group(1) or "orange" in match.group(1).lower()
|
||||
|
||||
def test_critical_badge_color(self, style_css):
|
||||
"""Test that critical badge has red color."""
|
||||
match = re.search(r'\.update-badge\.critical\s*\{([^}]+)\}', style_css)
|
||||
assert match, "Critical badge style not found"
|
||||
assert "#dc3545" in match.group(1) or "red" in match.group(1).lower()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# JavaScript Structure Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestJavaScriptStructure:
|
||||
"""Tests for JavaScript UpdateCheckManager in app.js."""
|
||||
|
||||
def test_update_check_manager_exists(self, app_js):
|
||||
"""Test that UpdateCheckManager object is defined."""
|
||||
assert "const UpdateCheckManager" in app_js or "var UpdateCheckManager" in app_js
|
||||
|
||||
def test_init_method_exists(self, app_js):
|
||||
"""Test that init() method exists."""
|
||||
# Find UpdateCheckManager definition
|
||||
manager_match = re.search(
|
||||
r'const UpdateCheckManager\s*=\s*\{(.*?)\n\};',
|
||||
app_js,
|
||||
re.DOTALL
|
||||
)
|
||||
assert manager_match, "UpdateCheckManager not found"
|
||||
manager_body = manager_match.group(1)
|
||||
assert "init()" in manager_body or "init:" in manager_body
|
||||
|
||||
def test_show_method_exists(self, app_js):
|
||||
"""Test that show() method exists."""
|
||||
assert re.search(r'(async\s+)?show\s*\(\s*\)', app_js)
|
||||
|
||||
def test_close_method_exists(self, app_js):
|
||||
"""Test that close() method exists."""
|
||||
assert "close()" in app_js or "close:" in app_js
|
||||
|
||||
def test_show_loading_method_exists(self, app_js):
|
||||
"""Test that showLoading() method exists."""
|
||||
assert "showLoading()" in app_js or "showLoading:" in app_js
|
||||
|
||||
def test_show_result_method_exists(self, app_js):
|
||||
"""Test that showResult() method exists."""
|
||||
assert "showResult(" in app_js
|
||||
|
||||
def test_show_error_method_exists(self, app_js):
|
||||
"""Test that showError() method exists."""
|
||||
assert "showError(" in app_js
|
||||
|
||||
def test_start_update_method_exists(self, app_js):
|
||||
"""Test that startUpdate() method exists."""
|
||||
assert "startUpdate()" in app_js or "startUpdate:" in app_js
|
||||
|
||||
def test_open_download_page_method_exists(self, app_js):
|
||||
"""Test that openDownloadPage() method exists."""
|
||||
assert "openDownloadPage()" in app_js or "openDownloadPage:" in app_js
|
||||
|
||||
def test_parse_markdown_method_exists(self, app_js):
|
||||
"""Test that parseMarkdown() method exists."""
|
||||
assert "parseMarkdown(" in app_js
|
||||
|
||||
def test_init_called_on_dom_ready(self, app_js):
|
||||
"""Test that UpdateCheckManager.init() is called on DOMContentLoaded."""
|
||||
# Find the DOMContentLoaded handler
|
||||
dom_ready_match = re.search(
|
||||
r"DOMContentLoaded.*?\{(.*?)\}\s*\)",
|
||||
app_js,
|
||||
re.DOTALL
|
||||
)
|
||||
assert dom_ready_match, "DOMContentLoaded handler not found"
|
||||
handler_body = dom_ready_match.group(1)
|
||||
assert "UpdateCheckManager.init()" in handler_body
|
||||
|
||||
|
||||
class TestJavaScriptApiCalls:
|
||||
"""Tests for correct API call usage in JavaScript."""
|
||||
|
||||
def test_check_for_updates_api_call(self, app_js):
|
||||
"""Test that check_for_updates API is called correctly."""
|
||||
# Should call with force=true for modal
|
||||
assert "pywebview.api.check_for_updates" in app_js
|
||||
assert "check_for_updates(true)" in app_js
|
||||
|
||||
def test_open_url_api_call(self, app_js):
|
||||
"""Test that open_url API is called for download page."""
|
||||
assert "pywebview.api.open_url" in app_js
|
||||
# Should include GitHub releases URL
|
||||
assert "github.com/meizhong986/WhisperJAV/releases" in app_js
|
||||
|
||||
def test_start_update_delegation(self, app_js):
|
||||
"""Test that startUpdate delegates to UpdateManager."""
|
||||
# Should call UpdateManager.startUpdate() not api.start_update directly
|
||||
assert "UpdateManager.startUpdate()" in app_js
|
||||
|
||||
|
||||
class TestJavaScriptEventHandlers:
|
||||
"""Tests for event handler setup in JavaScript."""
|
||||
|
||||
def test_menu_button_click_handler(self, app_js):
|
||||
"""Test that menu button has click handler."""
|
||||
assert "checkUpdatesBtn" in app_js
|
||||
assert "addEventListener" in app_js
|
||||
|
||||
def test_modal_close_button_handler(self, app_js):
|
||||
"""Test that modal close button has handler."""
|
||||
assert "updateCheckModalClose" in app_js
|
||||
|
||||
def test_later_button_handler(self, app_js):
|
||||
"""Test that Later button has handler."""
|
||||
assert "updateCheckLater" in app_js
|
||||
|
||||
def test_update_now_button_handler(self, app_js):
|
||||
"""Test that Update Now button has handler."""
|
||||
assert "updateCheckNow" in app_js
|
||||
|
||||
def test_download_button_handler(self, app_js):
|
||||
"""Test that Download button has handler."""
|
||||
assert "updateCheckDownload" in app_js
|
||||
|
||||
def test_escape_key_handler(self, app_js):
|
||||
"""Test that Escape key closes modal."""
|
||||
assert "Escape" in app_js
|
||||
|
||||
def test_overlay_click_closes_modal(self, app_js):
|
||||
"""Test that clicking overlay closes modal."""
|
||||
# Should check if click target is the modal itself
|
||||
assert "e.target === this.modal" in app_js or \
|
||||
"event.target === this.modal" in app_js
|
||||
|
||||
|
||||
class TestJavaScriptLogic:
|
||||
"""Tests for JavaScript business logic."""
|
||||
|
||||
def test_major_update_shows_download_button(self, app_js):
|
||||
"""Test that major updates show Download button."""
|
||||
# Check for level === 'major' condition
|
||||
assert "'major'" in app_js
|
||||
assert "updateCheckDownload" in app_js
|
||||
|
||||
def test_patch_minor_show_update_button(self, app_js):
|
||||
"""Test that patch/minor updates show Update Now button."""
|
||||
assert "updateCheckNow" in app_js
|
||||
|
||||
def test_notification_level_badge_update(self, app_js):
|
||||
"""Test that badge class is updated based on level."""
|
||||
assert "update-badge" in app_js
|
||||
assert "className" in app_js or "classList" in app_js
|
||||
|
||||
def test_version_display_formatting(self, app_js):
|
||||
"""Test that versions are prefixed with 'v'."""
|
||||
assert "'v' + result.current_version" in app_js or \
|
||||
'"v" + result.current_version' in app_js
|
||||
|
||||
def test_up_to_date_display_logic(self, app_js):
|
||||
"""Test that up-to-date message shows when no update."""
|
||||
assert "updateUpToDate" in app_js
|
||||
# Should show when update_available is false
|
||||
assert "update_available" in app_js.lower() or "updateavailable" in app_js.lower()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Theme Compatibility Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestThemeCompatibility:
|
||||
"""Tests for theme file compatibility."""
|
||||
|
||||
@pytest.fixture
|
||||
def theme_files(self, assets_dir):
|
||||
"""Get all theme CSS files."""
|
||||
return list(assets_dir.glob("style.*.css"))
|
||||
|
||||
def test_themes_import_base_styles(self, assets_dir):
|
||||
"""Test that theme files import base style.css."""
|
||||
theme_files = [
|
||||
"style.google.css",
|
||||
"style.carbon.css",
|
||||
"style.primer.css",
|
||||
]
|
||||
for theme_file in theme_files:
|
||||
theme_path = assets_dir / theme_file
|
||||
if theme_path.exists():
|
||||
content = theme_path.read_text(encoding="utf-8")
|
||||
assert '@import url("style.css")' in content or \
|
||||
"@import url('style.css')" in content, \
|
||||
f"{theme_file} should import base styles"
|
||||
|
||||
def test_css_uses_variables(self, style_css):
|
||||
"""Test that update check styles use CSS variables where appropriate."""
|
||||
# Key elements should use CSS variables for theme compatibility
|
||||
update_section_match = re.search(
|
||||
r'/\* Update Check Modal \*/.*?(?=/\*|$)',
|
||||
style_css,
|
||||
re.DOTALL
|
||||
)
|
||||
if update_section_match:
|
||||
update_css = update_section_match.group(0)
|
||||
# Should use variables like var(--border-color)
|
||||
assert "var(--" in update_css
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Accessibility Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestAccessibility:
|
||||
"""Tests for accessibility features."""
|
||||
|
||||
def test_modal_close_has_aria_label(self, index_html):
|
||||
"""Test that close button has aria-label."""
|
||||
close_match = re.search(r'id="updateCheckModalClose"[^>]*>', index_html)
|
||||
assert close_match, "Close button not found"
|
||||
close_tag = close_match.group(0)
|
||||
assert 'aria-label' in close_tag
|
||||
|
||||
def test_menu_items_have_role(self, index_html):
|
||||
"""Test that menu items have proper role."""
|
||||
# Check updates button should have role="menuitem"
|
||||
assert 'id="checkUpdatesBtn"' in index_html
|
||||
btn_match = re.search(r'id="checkUpdatesBtn"[^>]*>', index_html)
|
||||
if btn_match:
|
||||
btn_tag = btn_match.group(0)
|
||||
assert 'role="menuitem"' in btn_tag
|
||||
|
||||
def test_buttons_have_labels(self, index_html):
|
||||
"""Test that buttons have visible text or aria-label."""
|
||||
# All action buttons should have text content
|
||||
assert "Update Now" in index_html
|
||||
assert "Download" in index_html
|
||||
assert "Close" in index_html or "Later" in index_html
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Consistency Tests
|
||||
# =============================================================================
|
||||
|
||||
class TestConsistency:
|
||||
"""Tests for consistency between HTML, CSS, and JS."""
|
||||
|
||||
def test_html_ids_match_js_selectors(self, index_html, app_js):
|
||||
"""Test that HTML element IDs match JavaScript selectors."""
|
||||
html_ids = [
|
||||
"updateCheckModal",
|
||||
"updateCheckModalClose",
|
||||
"checkUpdatesBtn",
|
||||
"updateCheckLoading",
|
||||
"updateCheckResult",
|
||||
"updateCheckError",
|
||||
"updateCurrentVersion",
|
||||
"updateLatestVersion",
|
||||
"updateTypeBadge",
|
||||
"updateUpToDate",
|
||||
"updateAvailableSection",
|
||||
"updateReleaseNotes",
|
||||
"updateCheckLater",
|
||||
"updateCheckNow",
|
||||
"updateCheckDownload",
|
||||
]
|
||||
|
||||
for html_id in html_ids:
|
||||
assert f'id="{html_id}"' in index_html, f"HTML missing id: {html_id}"
|
||||
# JS should reference this ID
|
||||
assert html_id in app_js, f"JS not referencing id: {html_id}"
|
||||
|
||||
def test_css_classes_match_html(self, index_html, style_css):
|
||||
"""Test that CSS classes used in HTML are defined."""
|
||||
html_classes = [
|
||||
"theme-menu-divider",
|
||||
"update-check-option",
|
||||
"update-check-icon",
|
||||
"update-check-loading",
|
||||
"update-check-result",
|
||||
"update-check-error",
|
||||
"version-info",
|
||||
"version-row",
|
||||
"update-badge",
|
||||
"update-up-to-date",
|
||||
"release-notes-details",
|
||||
"release-notes-content",
|
||||
]
|
||||
|
||||
for css_class in html_classes:
|
||||
# Check if class is used in HTML (may be part of multi-class declaration)
|
||||
assert css_class in index_html, f"HTML should use class: {css_class}"
|
||||
# Check if class is defined in CSS
|
||||
assert f".{css_class}" in style_css, f"CSS should define class: {css_class}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
|
|
@ -2,16 +2,16 @@
|
|||
"""Version information for WhisperJAV."""
|
||||
|
||||
# PEP 440 compliant version for pip/wheel
|
||||
__version__ = "1.7.4"
|
||||
__version__ = "1.7.5"
|
||||
|
||||
# Human-readable version for display in UI
|
||||
__version_display__ = "1.7.4"
|
||||
__version_display__ = "1.7.5"
|
||||
|
||||
# Version metadata
|
||||
__version_info__ = {
|
||||
"major": 1,
|
||||
"minor": 7,
|
||||
"patch": 4,
|
||||
"patch": 5,
|
||||
"release": "",
|
||||
"architecture": "v4.4"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,7 +102,13 @@ class ASRComponent(ComponentBase):
|
|||
supported_tasks: List[str] = ["transcribe"] # "transcribe", "translate"
|
||||
compatible_vad: List[str] = [] # List of compatible VAD component names
|
||||
|
||||
# === Compute ===
|
||||
# === Compute (Preferred Defaults) ===
|
||||
# NOTE: These are "preferred" defaults for optimal performance.
|
||||
# The resolver applies runtime device detection via get_best_device()
|
||||
# and auto-selects compute_type based on device and provider:
|
||||
# - CUDA → int8_float16 for CTranslate2, float16 for PyTorch
|
||||
# - CPU → int8 for CTranslate2, float32 for PyTorch
|
||||
# - MPS → float16 for PyTorch, falls back to CPU+int8 for CTranslate2
|
||||
default_device: str = "cuda"
|
||||
default_compute_type: str = "float16"
|
||||
|
||||
|
|
|
|||
|
|
@ -135,6 +135,8 @@ def resolve_legacy_pipeline(
|
|||
sensitivity: str = "balanced",
|
||||
task: str = "transcribe",
|
||||
overrides: Optional[Dict[str, Any]] = None,
|
||||
device: Optional[str] = None,
|
||||
compute_type: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Resolve configuration from legacy pipeline name.
|
||||
|
|
@ -146,12 +148,16 @@ def resolve_legacy_pipeline(
|
|||
sensitivity: Sensitivity level
|
||||
task: Task type
|
||||
overrides: Parameter overrides
|
||||
device: Device override (None/'auto' = auto-detect, 'cuda'/'cpu' = explicit)
|
||||
compute_type: Compute type override (None/'auto' = provider-specific default)
|
||||
|
||||
Returns:
|
||||
Resolved configuration dictionary.
|
||||
|
||||
Example:
|
||||
>>> config = resolve_legacy_pipeline('balanced', 'aggressive')
|
||||
>>> # With explicit hardware:
|
||||
>>> config = resolve_legacy_pipeline('balanced', device='cuda', compute_type='int8_float16')
|
||||
"""
|
||||
if pipeline_name not in LEGACY_PIPELINES:
|
||||
available = list(LEGACY_PIPELINES.keys())
|
||||
|
|
@ -167,6 +173,8 @@ def resolve_legacy_pipeline(
|
|||
task=task,
|
||||
features=pipeline_def["features"],
|
||||
overrides=overrides,
|
||||
device=device,
|
||||
compute_type=compute_type,
|
||||
)
|
||||
|
||||
# Add legacy compatibility fields
|
||||
|
|
@ -346,6 +354,8 @@ def resolve_ensemble_config(
|
|||
task: str = "transcribe",
|
||||
features: Optional[List[str]] = None,
|
||||
overrides: Optional[Dict[str, Any]] = None,
|
||||
device: Optional[str] = None,
|
||||
compute_type: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Resolve configuration for ensemble mode (direct component specification).
|
||||
|
|
@ -360,6 +370,8 @@ def resolve_ensemble_config(
|
|||
features: List of feature names (e.g., ['auditok_scene_detection'])
|
||||
overrides: Parameter overrides in flat dot notation
|
||||
(e.g., {'asr.beam_size': 10, 'vad.threshold': 0.25})
|
||||
device: Device override (None/'auto' = auto-detect, 'cuda'/'cpu' = explicit)
|
||||
compute_type: Compute type override (None/'auto' = provider-specific default)
|
||||
|
||||
Returns:
|
||||
Resolved configuration in legacy structure for pipeline compatibility.
|
||||
|
|
@ -395,6 +407,8 @@ def resolve_ensemble_config(
|
|||
task=task,
|
||||
features=features or [],
|
||||
overrides=nested_overrides,
|
||||
device=device,
|
||||
compute_type=compute_type,
|
||||
)
|
||||
|
||||
# Add ensemble-specific metadata
|
||||
|
|
|
|||
|
|
@ -23,9 +23,46 @@ from .components import (
|
|||
get_vad_registry,
|
||||
get_feature_registry,
|
||||
)
|
||||
from whisperjav.utils.device_detector import get_best_device
|
||||
|
||||
logger = logging.getLogger("whisperjav")
|
||||
|
||||
# CTranslate2-based providers support "auto" compute_type selection
|
||||
CTRANSLATE2_PROVIDERS = {"faster_whisper", "kotoba_faster_whisper"}
|
||||
|
||||
|
||||
def _get_compute_type_for_device(device: str, provider: str) -> str:
|
||||
"""
|
||||
Select optimal compute_type based on device and provider.
|
||||
|
||||
CTranslate2 providers (faster_whisper, kotoba_faster_whisper):
|
||||
- Returns "auto" to delegate selection to CTranslate2's internal logic
|
||||
- CTranslate2 auto-selects based on GPU capability and CPU instruction sets
|
||||
- Benefits: Automatic support for new GPUs (e.g., RTX 50XX Blackwell sm120)
|
||||
- See: https://github.com/OpenNMT/CTranslate2/issues/1865
|
||||
|
||||
PyTorch providers (openai_whisper, stable_ts):
|
||||
- CUDA/MPS: float16
|
||||
- CPU: float32
|
||||
|
||||
Args:
|
||||
device: Detected device ("cuda", "mps", "cpu")
|
||||
provider: ASR provider name
|
||||
|
||||
Returns:
|
||||
Optimal compute_type for the device/provider combination
|
||||
"""
|
||||
if provider in CTRANSLATE2_PROVIDERS:
|
||||
# Delegate to CTranslate2's internal "auto" selection logic
|
||||
# This automatically handles:
|
||||
# - GPU capability detection (int8_float16 for RTX 20/30/40, float16 for RTX 50)
|
||||
# - CPU instruction sets (AVX2, AVX512)
|
||||
# - Blackwell sm120 workaround (PR #1937)
|
||||
return "auto"
|
||||
else:
|
||||
# PyTorch-based providers can use float16 on GPU/MPS, float32 on CPU
|
||||
return "float16" if device in ("cuda", "mps") else "float32"
|
||||
|
||||
|
||||
def resolve_config_v3(
|
||||
asr: str,
|
||||
|
|
@ -34,6 +71,8 @@ def resolve_config_v3(
|
|||
task: str = "transcribe",
|
||||
features: Optional[List[str]] = None,
|
||||
overrides: Optional[Dict[str, Any]] = None,
|
||||
device: Optional[str] = None,
|
||||
compute_type: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Resolve configuration from component selections.
|
||||
|
|
@ -45,6 +84,9 @@ def resolve_config_v3(
|
|||
task: Task type ('transcribe', 'translate')
|
||||
features: List of feature names (e.g., ['auditok_scene_detection'])
|
||||
overrides: Parameter overrides (e.g., {'asr.beam_size': 10})
|
||||
device: Device override (None/'auto' = auto-detect, 'cuda'/'cpu' = explicit)
|
||||
compute_type: Compute type override (None/'auto' = provider-specific default,
|
||||
'float16'/'float32'/'int8'/'int8_float16'/'int8_float32' = explicit)
|
||||
|
||||
Returns:
|
||||
Resolved configuration dictionary.
|
||||
|
|
@ -58,6 +100,12 @@ def resolve_config_v3(
|
|||
... features=['auditok_scene_detection'],
|
||||
... overrides={'asr.beam_size': 10}
|
||||
... )
|
||||
>>> # With explicit hardware configuration:
|
||||
>>> config = resolve_config_v3(
|
||||
... asr='faster_whisper',
|
||||
... device='cuda',
|
||||
... compute_type='int8_float16'
|
||||
... )
|
||||
"""
|
||||
if features is None:
|
||||
features = []
|
||||
|
|
@ -125,12 +173,38 @@ def resolve_config_v3(
|
|||
for feature_type, feature_config in feature_configs.items():
|
||||
_apply_overrides(feature_config, overrides, feature_type)
|
||||
|
||||
# Build model configuration
|
||||
# Build model configuration with device/compute_type selection
|
||||
# Priority: 1) User-provided value, 2) Auto-detection
|
||||
#
|
||||
# Device selection:
|
||||
# - None or "auto" → auto-detect using get_best_device()
|
||||
# - "cuda"/"cpu" → use explicit value (MPS fallback handled by ASR modules)
|
||||
#
|
||||
# Compute type selection:
|
||||
# - None or "auto" → provider-specific default (CTranslate2 "auto" or PyTorch float16/32)
|
||||
# - Explicit value → pass through to ASR module
|
||||
if device and device != "auto":
|
||||
selected_device = device
|
||||
logger.debug(f"v3.0 resolver: Using user-specified device='{selected_device}'")
|
||||
else:
|
||||
selected_device = get_best_device()
|
||||
logger.debug(f"v3.0 resolver: Auto-detected device='{selected_device}'")
|
||||
|
||||
if compute_type and compute_type != "auto":
|
||||
selected_compute_type = compute_type
|
||||
logger.debug(f"v3.0 resolver: Using user-specified compute_type='{selected_compute_type}'")
|
||||
else:
|
||||
selected_compute_type = _get_compute_type_for_device(selected_device, asr_component.provider)
|
||||
logger.debug(
|
||||
f"v3.0 resolver: Selected compute_type='{selected_compute_type}' "
|
||||
f"for provider='{asr_component.provider}'"
|
||||
)
|
||||
|
||||
model_config = {
|
||||
'provider': asr_component.provider,
|
||||
'model_name': asr_component.model_id,
|
||||
'device': asr_component.default_device,
|
||||
'compute_type': asr_component.default_compute_type,
|
||||
'device': selected_device,
|
||||
'compute_type': selected_compute_type,
|
||||
'supported_tasks': asr_component.supported_tasks,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -696,6 +696,11 @@ def _build_pipeline(
|
|||
logger.debug("Pass %s: Override hf_scene = %s", pass_number, pass_config["scene_detector"])
|
||||
elif pass_config.get("scene_detector") == "none":
|
||||
hf_defaults["hf_scene"] = "none"
|
||||
# Apply source language override for transformers pipeline (fixes Issue #104)
|
||||
# The language code (e.g., 'en', 'ja') is set in main.py from --language argument
|
||||
if pass_config.get("language"):
|
||||
hf_defaults["hf_language"] = pass_config["language"]
|
||||
logger.debug("Pass %s: Override hf_language = %s", pass_number, pass_config["language"])
|
||||
# Apply speech enhancer override for transformers pipeline
|
||||
if pass_config.get("speech_enhancer"):
|
||||
enhancer_backend, enhancer_model = _parse_speech_enhancer(pass_config["speech_enhancer"])
|
||||
|
|
@ -737,8 +742,19 @@ def _build_pipeline(
|
|||
sensitivity=pass_config.get("sensitivity", "balanced"),
|
||||
task=asr_task, # Derived from subs_language above
|
||||
overrides=pass_config.get("overrides"),
|
||||
device=pass_config.get("device"), # None = auto-detect
|
||||
compute_type=pass_config.get("compute_type"), # None = auto
|
||||
)
|
||||
|
||||
# Apply source language from pass_config (fixes Issue #104)
|
||||
# The language code (e.g., 'en', 'ja') is set in main.py from --language argument
|
||||
if pass_config.get("language"):
|
||||
resolved_config["language"] = pass_config["language"]
|
||||
# Also update decoder params so ASR uses correct language
|
||||
if "params" in resolved_config and "decoder" in resolved_config["params"]:
|
||||
resolved_config["params"]["decoder"]["language"] = pass_config["language"]
|
||||
logger.debug("Pass %s: Applied source language = %s", pass_number, pass_config["language"])
|
||||
|
||||
# Apply GUI-specified overrides for legacy pipelines
|
||||
_apply_gui_overrides(resolved_config, pass_config, pass_number)
|
||||
|
||||
|
|
|
|||
|
|
@ -211,6 +211,19 @@ def parse_arguments():
|
|||
parser.add_argument("--accept-cpu-mode", action="store_true",
|
||||
help="Accept CPU-only mode without GPU warning (skip GPU performance check)")
|
||||
|
||||
# Hardware configuration (device and compute type override)
|
||||
hardware_group = parser.add_argument_group("Hardware Configuration")
|
||||
hardware_group.add_argument("--device", type=str, default=None,
|
||||
choices=["auto", "cuda", "cpu"],
|
||||
help="Device to use for ASR processing (default: auto-detect). "
|
||||
"Note: MPS (Apple Silicon) is auto-detected but CTranslate2 backends "
|
||||
"will fall back to CPU with Accelerate optimization.")
|
||||
hardware_group.add_argument("--compute-type", type=str, default=None,
|
||||
choices=["auto", "float16", "float32", "int8", "int8_float16", "int8_float32"],
|
||||
help="Compute type for ASR processing (default: auto). "
|
||||
"CTranslate2 backends (faster_whisper, kotoba) use 'auto' to optimize "
|
||||
"based on GPU capability. PyTorch backends use float16 (GPU) or float32 (CPU).")
|
||||
|
||||
# Path and logging
|
||||
path_group = parser.add_argument_group("Path and Logging Options")
|
||||
path_group.add_argument("--output-dir", default="./output", help="Output directory")
|
||||
|
|
@ -1100,6 +1113,8 @@ def main():
|
|||
task=task,
|
||||
features=features,
|
||||
overrides=overrides,
|
||||
device=args.device,
|
||||
compute_type=args.compute_type,
|
||||
)
|
||||
|
||||
elif args.mode == "transformers":
|
||||
|
|
@ -1114,6 +1129,8 @@ def main():
|
|||
pipeline_name=args.mode,
|
||||
sensitivity=args.sensitivity,
|
||||
task=task,
|
||||
device=args.device,
|
||||
compute_type=args.compute_type,
|
||||
)
|
||||
|
||||
if args.scene_detection_method:
|
||||
|
|
@ -1128,16 +1145,20 @@ def main():
|
|||
# Apply model override if specified via CLI (not for ensemble mode)
|
||||
if args.model and resolved_config is not None:
|
||||
logger.info(f"Overriding model with CLI argument: {args.model}")
|
||||
# Determine device and compute_type for model override
|
||||
# Priority: CLI args > auto-detection
|
||||
override_device = args.device if args.device and args.device != "auto" else get_best_device()
|
||||
override_compute_type = args.compute_type if args.compute_type and args.compute_type != "auto" else "int8"
|
||||
# Create a model configuration for the CLI-specified model
|
||||
override_model_config = {
|
||||
"provider": "openai_whisper", # Default provider
|
||||
"model_name": args.model,
|
||||
"device": get_best_device(), # Auto-detect: CUDA -> MPS -> CPU
|
||||
"compute_type": "int8", # Default to int8 for quantized models (CTranslate2)
|
||||
"device": override_device,
|
||||
"compute_type": override_compute_type,
|
||||
"supported_tasks": ["transcribe", "translate"]
|
||||
}
|
||||
resolved_config["model"] = override_model_config
|
||||
logger.debug(f"Model override applied: {args.model}")
|
||||
logger.debug(f"Model override applied: {args.model} (device={override_device}, compute_type={override_compute_type})")
|
||||
|
||||
# Apply language override to decoder params (not for ensemble mode)
|
||||
if resolved_config is not None and "params" in resolved_config and "decoder" in resolved_config["params"]:
|
||||
|
|
@ -1296,7 +1317,10 @@ def main():
|
|||
'speech_enhancer': args.pass1_speech_enhancer,
|
||||
'model': args.pass1_model,
|
||||
'params': pass1_params, # None = use defaults, object = custom
|
||||
'hf_params': pass1_hf_params # For transformers pipeline
|
||||
'hf_params': pass1_hf_params, # For transformers pipeline
|
||||
'language': language_code, # Source language code (e.g., 'en', 'ja')
|
||||
'device': args.device, # Hardware override (None = auto-detect)
|
||||
'compute_type': args.compute_type, # Compute type override (None = auto)
|
||||
}
|
||||
|
||||
pass2_config = None
|
||||
|
|
@ -1309,7 +1333,10 @@ def main():
|
|||
'speech_enhancer': args.pass2_speech_enhancer,
|
||||
'model': args.pass2_model,
|
||||
'params': pass2_params,
|
||||
'hf_params': pass2_hf_params # For transformers pipeline
|
||||
'hf_params': pass2_hf_params, # For transformers pipeline
|
||||
'language': language_code, # Source language code (e.g., 'en', 'ja')
|
||||
'device': args.device, # Hardware override (None = auto-detect)
|
||||
'compute_type': args.compute_type, # Compute type override (None = auto)
|
||||
}
|
||||
|
||||
# Create orchestrator
|
||||
|
|
|
|||
|
|
@ -47,21 +47,24 @@ class FasterWhisperProASR:
|
|||
# Use smart device detection: CUDA -> MPS -> CPU
|
||||
self.device = model_config.get("device", get_best_device())
|
||||
|
||||
# Smart compute_type default based on device (ctranslate2 backend)
|
||||
# - CUDA: int8_float16 (quantized weights + FP16 tensor cores = fastest)
|
||||
# - MPS: float16 (safe for Apple Silicon)
|
||||
# - CPU: int8 (quantized weights + FP32 compute = only fast option)
|
||||
requested_compute_type = model_config.get("compute_type")
|
||||
if requested_compute_type is None or requested_compute_type == "auto":
|
||||
if self.device == "cuda":
|
||||
self.compute_type = "int8_float16"
|
||||
elif self.device == "mps":
|
||||
self.compute_type = "float16"
|
||||
else: # cpu
|
||||
self.compute_type = "int8"
|
||||
logger.debug(f"Auto-selected compute_type='{self.compute_type}' for device='{self.device}'")
|
||||
else:
|
||||
self.compute_type = requested_compute_type
|
||||
# CTRANSLATE2 COMPATIBILITY: MPS is not supported by ctranslate2/faster-whisper
|
||||
# CTranslate2 only supports "cuda" or "cpu" devices.
|
||||
# See: https://github.com/OpenNMT/CTranslate2/issues/1562
|
||||
# See: https://github.com/SYSTRAN/faster-whisper/issues/911
|
||||
if self.device == "mps":
|
||||
logger.warning(
|
||||
"Apple Silicon MPS detected, but faster-whisper/ctranslate2 doesn't support MPS. "
|
||||
"Using CPU mode with Apple Accelerate optimization. "
|
||||
"For GPU acceleration on Mac, use --mode transformers instead."
|
||||
)
|
||||
self.device = "cpu"
|
||||
|
||||
# Use compute_type from resolver (defaults to "auto" for CTranslate2 providers)
|
||||
# CTranslate2's "auto" handles device-specific optimization internally:
|
||||
# - GPU capability detection (int8_float16 for RTX 20/30/40, float16 for RTX 50 Blackwell)
|
||||
# - CPU instruction sets (AVX2, AVX512 → int8)
|
||||
# See: https://github.com/OpenNMT/CTranslate2/issues/1865
|
||||
self.compute_type = model_config.get("compute_type", "auto")
|
||||
|
||||
decoder_params = params["decoder"]
|
||||
vad_params = params["vad"]
|
||||
|
|
@ -173,25 +176,7 @@ class FasterWhisperProASR:
|
|||
def _initialize_models(self):
|
||||
"""Initialize Faster-Whisper model (direct API)."""
|
||||
# Note: Speech segmentation is handled by external Speech Segmenter (set in __init__)
|
||||
|
||||
# CPU COMPATIBILITY: float16/bfloat16 are NOT supported on CPU by ctranslate2
|
||||
# Only float32 and int8 work on CPU. This check is ONLY for CPU - GPU paths unchanged.
|
||||
# Note: Apple Silicon MPS (device="mps") DOES support float16/bfloat16 - this only affects CPU mode.
|
||||
if self.device == "cpu" and self.compute_type in ("float16", "bfloat16", "int8_float16", "int8_bfloat16"):
|
||||
import platform
|
||||
original_compute_type = self.compute_type
|
||||
self.compute_type = "int8" # int8 is faster than float32 on CPU
|
||||
|
||||
# Tailor message based on platform
|
||||
if platform.system() == "Darwin":
|
||||
hint = "For GPU acceleration on Mac, ensure MPS is available (Apple Silicon required)."
|
||||
else:
|
||||
hint = "For GPU acceleration, use a CUDA-compatible GPU."
|
||||
|
||||
logger.warning(
|
||||
f"Compute type '{original_compute_type}' is not supported on CPU. "
|
||||
f"Automatically switching to 'int8' (best CPU performance). {hint}"
|
||||
)
|
||||
# Note: compute_type="auto" (default) lets CTranslate2 handle device-specific selection
|
||||
|
||||
# Faster-Whisper direct API initialization
|
||||
logger.debug(
|
||||
|
|
|
|||
|
|
@ -52,7 +52,22 @@ class KotobaFasterWhisperASR:
|
|||
# Model configuration
|
||||
self.model_name = model_config.get("model_name", self.DEFAULT_MODEL)
|
||||
self.device = model_config.get("device", get_best_device())
|
||||
self.compute_type = model_config.get("compute_type", "float16")
|
||||
|
||||
# CTRANSLATE2 COMPATIBILITY: MPS is not supported by ctranslate2/faster-whisper
|
||||
# CTranslate2 only supports "cuda" or "cpu" devices.
|
||||
# See: https://github.com/OpenNMT/CTranslate2/issues/1562
|
||||
if self.device == "mps":
|
||||
logger.warning(
|
||||
"Apple Silicon MPS detected, but faster-whisper/ctranslate2 doesn't support MPS. "
|
||||
"Using CPU mode with Apple Accelerate optimization. "
|
||||
"For GPU acceleration on Mac, use --mode transformers instead."
|
||||
)
|
||||
self.device = "cpu"
|
||||
|
||||
# Use compute_type from resolver (defaults to "auto" for CTranslate2 providers)
|
||||
# CTranslate2's "auto" handles device-specific optimization internally
|
||||
# See: https://github.com/OpenNMT/CTranslate2/issues/1865
|
||||
self.compute_type = model_config.get("compute_type", "auto")
|
||||
|
||||
# Task
|
||||
self.task = task
|
||||
|
|
@ -122,31 +137,22 @@ class KotobaFasterWhisperASR:
|
|||
logger.info(f"Loading Kotoba model: {self.model_name}")
|
||||
logger.info(f"Device: {self.device}, Compute type: {self.compute_type}")
|
||||
|
||||
# Determine compute type based on device capabilities
|
||||
# On CPU, float16 is generally not supported by CTranslate2, so we fallback to int8
|
||||
# unless float32 is explicitly requested.
|
||||
if self.device == "cuda":
|
||||
actual_compute_type = self.compute_type
|
||||
else:
|
||||
if self.compute_type == "float16":
|
||||
logger.warning("float16 compute type is not supported on CPU. Falling back to int8.")
|
||||
actual_compute_type = "int8"
|
||||
else:
|
||||
actual_compute_type = self.compute_type
|
||||
# Note: compute_type="auto" (default) lets CTranslate2 handle device-specific selection
|
||||
# See: https://github.com/OpenNMT/CTranslate2/issues/1865
|
||||
|
||||
try:
|
||||
self.model = WhisperModel(
|
||||
model_size_or_path=self.model_name,
|
||||
device=self.device,
|
||||
compute_type=actual_compute_type,
|
||||
compute_type=self.compute_type,
|
||||
download_root=None, # Use default HuggingFace cache
|
||||
cpu_threads=0, # Use default
|
||||
num_workers=1,
|
||||
)
|
||||
logger.info("Kotoba model loaded successfully")
|
||||
except Exception as e:
|
||||
# C4: VRAM exhaustion fallback - try int8 if float16 fails on CUDA
|
||||
if self.device == "cuda" and actual_compute_type == "float16":
|
||||
# VRAM exhaustion fallback - try int8 if auto/float16 fails on CUDA
|
||||
if self.device == "cuda" and self.compute_type != "int8":
|
||||
vram_error_indicators = [
|
||||
"out of memory",
|
||||
"cuda out of memory",
|
||||
|
|
@ -158,7 +164,7 @@ class KotobaFasterWhisperASR:
|
|||
error_str = str(e).lower()
|
||||
if any(indicator in error_str for indicator in vram_error_indicators):
|
||||
logger.warning(
|
||||
f"CUDA float16 failed (likely VRAM exhaustion): {e}. "
|
||||
f"CUDA {self.compute_type} failed (likely VRAM exhaustion): {e}. "
|
||||
"Falling back to int8 compute type."
|
||||
)
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ class WhisperVadSpeechSegmenter:
|
|||
no_speech_threshold: float = 0.6,
|
||||
logprob_threshold: float = -1.0,
|
||||
language: str = "ja",
|
||||
compute_type: str = "float16",
|
||||
compute_type: str = "auto",
|
||||
device: str = "auto",
|
||||
cache_results: bool = True,
|
||||
cache_dir: Optional[Path] = None,
|
||||
|
|
@ -265,16 +265,12 @@ class WhisperVadSpeechSegmenter:
|
|||
device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||
logger.info(f"[WhisperVAD] Auto-detected device: {device}")
|
||||
|
||||
# Adjust compute_type for CPU
|
||||
compute_type = self.compute_type
|
||||
if device == "cpu" and compute_type in ("float16", "int8_float16"):
|
||||
compute_type = "int8"
|
||||
logger.info(f"[WhisperVAD] Adjusted compute_type for CPU: {compute_type}")
|
||||
|
||||
# Note: compute_type="auto" (default) lets CTranslate2 handle device-specific selection
|
||||
# See: https://github.com/OpenNMT/CTranslate2/issues/1865
|
||||
self._model = WhisperModel(
|
||||
self._model_size,
|
||||
device=device,
|
||||
compute_type=compute_type,
|
||||
compute_type=self.compute_type,
|
||||
)
|
||||
logger.info(f"[WhisperVAD] Model loaded successfully on {device}")
|
||||
|
||||
|
|
|
|||
|
|
@ -148,9 +148,22 @@ class StableTSASR:
|
|||
self.model_name = model_config.get("model_name", "large-v2")
|
||||
# Use smart device detection: CUDA -> MPS -> CPU
|
||||
self.device = model_config.get("device", get_best_device())
|
||||
self.turbo_mode = turbo_mode
|
||||
|
||||
# CTRANSLATE2 COMPATIBILITY: MPS is not supported by ctranslate2/faster-whisper
|
||||
# In turbo_mode, stable-ts uses faster-whisper which uses ctranslate2.
|
||||
# CTranslate2 only supports "cuda" or "cpu" devices.
|
||||
# See: https://github.com/OpenNMT/CTranslate2/issues/1562
|
||||
if self.device == "mps" and self.turbo_mode:
|
||||
logger.warning(
|
||||
"Apple Silicon MPS detected, but faster-whisper/ctranslate2 doesn't support MPS. "
|
||||
"Using CPU mode with Apple Accelerate optimization. "
|
||||
"For GPU acceleration on Mac, use --mode transformers instead."
|
||||
)
|
||||
self.device = "cpu"
|
||||
|
||||
# Default to int8 for quantized models (faster-whisper uses CTranslate2 quantized models)
|
||||
self.compute_type = model_config.get("compute_type", "int8")
|
||||
self.turbo_mode = turbo_mode
|
||||
self.model_repo = model_config.get("hf_repo")
|
||||
if not self.model_repo:
|
||||
self.model_repo = self._derive_model_repo(self.model_name)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
WhisperJAV Version Checker
|
||||
|
||||
Checks for new versions on GitHub and provides update notifications.
|
||||
Supports both stable releases and development (latest commit) updates.
|
||||
Uses GitHub API with caching and retry logic.
|
||||
"""
|
||||
|
||||
|
|
@ -10,11 +11,12 @@ import os
|
|||
import sys
|
||||
import json
|
||||
import time
|
||||
import subprocess
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
from pathlib import Path
|
||||
from typing import Optional, Tuple, Dict
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Tuple, Dict, List
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Import version - handle both installed and dev scenarios
|
||||
|
|
@ -40,14 +42,23 @@ except ImportError:
|
|||
# set WHISPERJAV_RELEASES_URL=http://localhost:8000/releases
|
||||
# =============================================================================
|
||||
|
||||
# GitHub API endpoint
|
||||
# GitHub API endpoints
|
||||
GITHUB_REPO = os.environ.get('WHISPERJAV_REPO', 'meizhong986/whisperjav')
|
||||
GITHUB_API_URL = os.environ.get(
|
||||
'WHISPERJAV_UPDATE_API_URL',
|
||||
'https://api.github.com/repos/meizhong986/whisperjav/releases/latest'
|
||||
f'https://api.github.com/repos/{GITHUB_REPO}/releases/latest'
|
||||
)
|
||||
GITHUB_COMMITS_API_URL = os.environ.get(
|
||||
'WHISPERJAV_COMMITS_API_URL',
|
||||
f'https://api.github.com/repos/{GITHUB_REPO}/commits/main'
|
||||
)
|
||||
GITHUB_COMPARE_API_URL = os.environ.get(
|
||||
'WHISPERJAV_COMPARE_API_URL',
|
||||
f'https://api.github.com/repos/{GITHUB_REPO}/compare'
|
||||
)
|
||||
GITHUB_RELEASES_URL = os.environ.get(
|
||||
'WHISPERJAV_RELEASES_URL',
|
||||
'https://github.com/meizhong986/WhisperJAV/releases'
|
||||
f'https://github.com/{GITHUB_REPO}/releases'
|
||||
)
|
||||
|
||||
# Cache settings
|
||||
|
|
@ -67,13 +78,44 @@ class VersionInfo:
|
|||
download_url: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class CommitInfo:
|
||||
"""Information about a single commit."""
|
||||
sha: str
|
||||
short_sha: str
|
||||
message: str
|
||||
author: str
|
||||
date: str
|
||||
url: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class DevUpdateInfo:
|
||||
"""Information about development (latest commit) updates."""
|
||||
commits_ahead: int
|
||||
latest_commit: Optional[CommitInfo] = None
|
||||
recent_commits: List[CommitInfo] = field(default_factory=list)
|
||||
base_tag: Optional[str] = None # The release tag we're comparing against
|
||||
|
||||
|
||||
@dataclass
|
||||
class UpdateCheckResult:
|
||||
"""Result of an update check."""
|
||||
update_available: bool
|
||||
# Current installation info
|
||||
current_version: str
|
||||
current_commit: Optional[str] = None # Short SHA if known
|
||||
installation_type: str = "release" # "release" or "dev"
|
||||
|
||||
# Stable release track
|
||||
update_available: bool = False
|
||||
latest_version: Optional[str] = None
|
||||
version_info: Optional[VersionInfo] = None
|
||||
|
||||
# Development track
|
||||
dev_update_available: bool = False
|
||||
dev_info: Optional[DevUpdateInfo] = None
|
||||
|
||||
# Meta
|
||||
error: Optional[str] = None
|
||||
from_cache: bool = False
|
||||
|
||||
|
|
@ -207,17 +249,160 @@ def _fetch_latest_release(timeout: int = 10, retries: int = 2) -> Optional[Dict]
|
|||
return None
|
||||
|
||||
|
||||
def check_for_updates(force: bool = False) -> UpdateCheckResult:
|
||||
def _fetch_github_api(url: str, timeout: int = 10) -> Optional[Dict]:
|
||||
"""
|
||||
Fetch data from GitHub API.
|
||||
|
||||
Args:
|
||||
url: GitHub API URL
|
||||
timeout: Request timeout in seconds
|
||||
|
||||
Returns:
|
||||
JSON response dict or None on failure
|
||||
"""
|
||||
try:
|
||||
request = urllib.request.Request(
|
||||
url,
|
||||
headers={
|
||||
'Accept': 'application/vnd.github.v3+json',
|
||||
'User-Agent': f'WhisperJAV/{CURRENT_VERSION}'
|
||||
}
|
||||
)
|
||||
with urllib.request.urlopen(request, timeout=timeout) as response:
|
||||
return json.loads(response.read().decode('utf-8'))
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _get_current_commit() -> Optional[str]:
|
||||
"""
|
||||
Get the current installation's commit hash.
|
||||
|
||||
Tries multiple methods:
|
||||
1. Check if running from git repo (development)
|
||||
2. Check stored commit hash from pip install
|
||||
|
||||
Returns:
|
||||
Short commit hash (7 chars) or None
|
||||
"""
|
||||
# Method 1: Running from git repo (development)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['git', 'rev-parse', '--short', 'HEAD'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
cwd=Path(__file__).parent.parent # whisperjav package root
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()[:7]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Method 2: Check for stored commit hash (from pip install git+...)
|
||||
try:
|
||||
commit_file = CACHE_DIR / 'installed_commit.txt'
|
||||
if commit_file.exists():
|
||||
return commit_file.read_text().strip()[:7]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _fetch_latest_commit(timeout: int = 10) -> Optional[CommitInfo]:
|
||||
"""
|
||||
Fetch the latest commit from the main branch.
|
||||
|
||||
Returns:
|
||||
CommitInfo or None on failure
|
||||
"""
|
||||
data = _fetch_github_api(GITHUB_COMMITS_API_URL, timeout)
|
||||
if not data:
|
||||
return None
|
||||
|
||||
try:
|
||||
commit = data.get('commit', {})
|
||||
return CommitInfo(
|
||||
sha=data.get('sha', ''),
|
||||
short_sha=data.get('sha', '')[:7],
|
||||
message=commit.get('message', '').split('\n')[0][:100], # First line, truncated
|
||||
author=commit.get('author', {}).get('name', 'Unknown'),
|
||||
date=commit.get('author', {}).get('date', ''),
|
||||
url=data.get('html_url', '')
|
||||
)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _fetch_commits_since_tag(tag: str, timeout: int = 10) -> Optional[DevUpdateInfo]:
|
||||
"""
|
||||
Fetch commits between a release tag and the main branch.
|
||||
|
||||
Args:
|
||||
tag: Release tag to compare from (e.g., 'v1.7.5')
|
||||
|
||||
Returns:
|
||||
DevUpdateInfo with commit list, or None on failure
|
||||
"""
|
||||
# Use GitHub compare API: /repos/{owner}/{repo}/compare/{base}...{head}
|
||||
compare_url = f"{GITHUB_COMPARE_API_URL}/{tag}...main"
|
||||
data = _fetch_github_api(compare_url, timeout)
|
||||
|
||||
if not data:
|
||||
return None
|
||||
|
||||
try:
|
||||
commits_data = data.get('commits', [])
|
||||
ahead_by = data.get('ahead_by', len(commits_data))
|
||||
|
||||
# Parse recent commits (up to 10)
|
||||
recent_commits = []
|
||||
for commit_data in commits_data[-10:]: # Last 10 commits
|
||||
commit = commit_data.get('commit', {})
|
||||
recent_commits.append(CommitInfo(
|
||||
sha=commit_data.get('sha', ''),
|
||||
short_sha=commit_data.get('sha', '')[:7],
|
||||
message=commit.get('message', '').split('\n')[0][:80],
|
||||
author=commit.get('author', {}).get('name', 'Unknown'),
|
||||
date=commit.get('author', {}).get('date', ''),
|
||||
url=commit_data.get('html_url', '')
|
||||
))
|
||||
|
||||
# Reverse to show newest first
|
||||
recent_commits.reverse()
|
||||
|
||||
return DevUpdateInfo(
|
||||
commits_ahead=ahead_by,
|
||||
latest_commit=recent_commits[0] if recent_commits else None,
|
||||
recent_commits=recent_commits,
|
||||
base_tag=tag
|
||||
)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def check_for_updates(force: bool = False, include_dev: bool = True) -> UpdateCheckResult:
|
||||
"""
|
||||
Check if a newer version of WhisperJAV is available.
|
||||
|
||||
Checks both:
|
||||
1. Stable releases (tagged versions on GitHub)
|
||||
2. Development updates (commits on main branch since last release)
|
||||
|
||||
Args:
|
||||
force: If True, bypass cache and check immediately
|
||||
include_dev: If True, also check for dev updates (commits since release)
|
||||
|
||||
Returns:
|
||||
UpdateCheckResult with update information
|
||||
UpdateCheckResult with both release and dev update information
|
||||
"""
|
||||
current = CURRENT_VERSION
|
||||
current_commit = _get_current_commit()
|
||||
|
||||
# Determine installation type
|
||||
# If we can get a commit hash, likely installed from git
|
||||
installation_type = "dev" if current_commit else "release"
|
||||
|
||||
# Check cache first (unless forced)
|
||||
if not force:
|
||||
|
|
@ -239,31 +424,76 @@ def check_for_updates(force: bool = False) -> UpdateCheckResult:
|
|||
download_url=vi.get('download_url')
|
||||
)
|
||||
|
||||
# Reconstruct dev_info from cache if available
|
||||
dev_info = None
|
||||
dev_update_available = False
|
||||
if cached.get('dev_info'):
|
||||
di = cached['dev_info']
|
||||
commits_ahead = di.get('commits_ahead', 0)
|
||||
dev_update_available = commits_ahead > 0
|
||||
|
||||
recent_commits = []
|
||||
for c in di.get('recent_commits', []):
|
||||
recent_commits.append(CommitInfo(
|
||||
sha=c.get('sha', ''),
|
||||
short_sha=c.get('short_sha', ''),
|
||||
message=c.get('message', ''),
|
||||
author=c.get('author', ''),
|
||||
date=c.get('date', ''),
|
||||
url=c.get('url', '')
|
||||
))
|
||||
|
||||
latest_commit = None
|
||||
if di.get('latest_commit'):
|
||||
lc = di['latest_commit']
|
||||
latest_commit = CommitInfo(
|
||||
sha=lc.get('sha', ''),
|
||||
short_sha=lc.get('short_sha', ''),
|
||||
message=lc.get('message', ''),
|
||||
author=lc.get('author', ''),
|
||||
date=lc.get('date', ''),
|
||||
url=lc.get('url', '')
|
||||
)
|
||||
|
||||
dev_info = DevUpdateInfo(
|
||||
commits_ahead=commits_ahead,
|
||||
latest_commit=latest_commit,
|
||||
recent_commits=recent_commits,
|
||||
base_tag=di.get('base_tag')
|
||||
)
|
||||
|
||||
return UpdateCheckResult(
|
||||
update_available=update_available,
|
||||
current_version=current,
|
||||
current_commit=current_commit,
|
||||
installation_type=installation_type,
|
||||
update_available=update_available,
|
||||
latest_version=latest,
|
||||
version_info=version_info,
|
||||
dev_update_available=dev_update_available,
|
||||
dev_info=dev_info,
|
||||
from_cache=True
|
||||
)
|
||||
|
||||
# Fetch from GitHub
|
||||
# Fetch latest release from GitHub
|
||||
release_data = _fetch_latest_release()
|
||||
|
||||
if not release_data:
|
||||
return UpdateCheckResult(
|
||||
update_available=False,
|
||||
current_version=current,
|
||||
current_commit=current_commit,
|
||||
installation_type=installation_type,
|
||||
error="Could not check for updates"
|
||||
)
|
||||
|
||||
# Parse release data
|
||||
latest = release_data.get('tag_name', '').lstrip('v')
|
||||
release_tag = release_data.get('tag_name', f'v{latest}')
|
||||
|
||||
if not latest:
|
||||
return UpdateCheckResult(
|
||||
update_available=False,
|
||||
current_version=current,
|
||||
current_commit=current_commit,
|
||||
installation_type=installation_type,
|
||||
error="Invalid release data"
|
||||
)
|
||||
|
||||
|
|
@ -283,6 +513,26 @@ def check_for_updates(force: bool = False) -> UpdateCheckResult:
|
|||
version_info.download_url = asset.get('browser_download_url')
|
||||
break
|
||||
|
||||
# Compare versions for stable track
|
||||
update_available = compare_versions(current, latest) < 0
|
||||
|
||||
# Fetch dev track info (commits since release)
|
||||
dev_info = None
|
||||
dev_update_available = False
|
||||
|
||||
if include_dev:
|
||||
dev_info = _fetch_commits_since_tag(release_tag)
|
||||
if dev_info:
|
||||
# Dev update available if there are commits ahead
|
||||
# AND user doesn't already have the latest commit
|
||||
if dev_info.commits_ahead > 0:
|
||||
if current_commit and dev_info.latest_commit:
|
||||
# User has a commit hash - check if it matches latest
|
||||
dev_update_available = current_commit != dev_info.latest_commit.short_sha
|
||||
else:
|
||||
# Can't compare commits, assume update available
|
||||
dev_update_available = True
|
||||
|
||||
# Save to cache
|
||||
cache_data = {
|
||||
'latest_version': latest,
|
||||
|
|
@ -295,16 +545,44 @@ def check_for_updates(force: bool = False) -> UpdateCheckResult:
|
|||
'download_url': version_info.download_url
|
||||
}
|
||||
}
|
||||
|
||||
# Cache dev info if available
|
||||
if dev_info:
|
||||
cache_data['dev_info'] = {
|
||||
'commits_ahead': dev_info.commits_ahead,
|
||||
'base_tag': dev_info.base_tag,
|
||||
'latest_commit': {
|
||||
'sha': dev_info.latest_commit.sha,
|
||||
'short_sha': dev_info.latest_commit.short_sha,
|
||||
'message': dev_info.latest_commit.message,
|
||||
'author': dev_info.latest_commit.author,
|
||||
'date': dev_info.latest_commit.date,
|
||||
'url': dev_info.latest_commit.url
|
||||
} if dev_info.latest_commit else None,
|
||||
'recent_commits': [
|
||||
{
|
||||
'sha': c.sha,
|
||||
'short_sha': c.short_sha,
|
||||
'message': c.message,
|
||||
'author': c.author,
|
||||
'date': c.date,
|
||||
'url': c.url
|
||||
}
|
||||
for c in dev_info.recent_commits[:5] # Cache up to 5 commits
|
||||
]
|
||||
}
|
||||
|
||||
_save_cache(cache_data)
|
||||
|
||||
# Compare versions
|
||||
update_available = compare_versions(current, latest) < 0
|
||||
|
||||
return UpdateCheckResult(
|
||||
update_available=update_available,
|
||||
current_version=current,
|
||||
current_commit=current_commit,
|
||||
installation_type=installation_type,
|
||||
update_available=update_available,
|
||||
latest_version=latest,
|
||||
version_info=version_info,
|
||||
dev_update_available=dev_update_available,
|
||||
dev_info=dev_info,
|
||||
from_cache=False
|
||||
)
|
||||
|
||||
|
|
@ -385,16 +663,52 @@ def should_show_notification(level: str, last_dismissed: Optional[str] = None) -
|
|||
# CLI interface
|
||||
if __name__ == "__main__":
|
||||
print(f"Current version: {CURRENT_VERSION}")
|
||||
print("Checking for updates...")
|
||||
if _get_current_commit():
|
||||
print(f"Current commit: {_get_current_commit()}")
|
||||
print("Checking for updates...\n")
|
||||
|
||||
result = check_for_updates(force=True)
|
||||
|
||||
if result.error:
|
||||
print(f"Error: {result.error}")
|
||||
elif result.update_available:
|
||||
print(f"Update available: {result.latest_version}")
|
||||
print(f"Release URL: {result.version_info.release_url}")
|
||||
sys.exit(1)
|
||||
|
||||
# Stable release track
|
||||
print("=" * 50)
|
||||
print("[STABLE RELEASE]")
|
||||
print("=" * 50)
|
||||
if result.update_available:
|
||||
print(f" Update available: v{result.latest_version}")
|
||||
print(f" Release URL: {result.version_info.release_url}")
|
||||
if result.version_info.download_url:
|
||||
print(f"Download: {result.version_info.download_url}")
|
||||
print(f" Download: {result.version_info.download_url}")
|
||||
else:
|
||||
print("You have the latest version!")
|
||||
print(f" [OK] You have the latest stable release (v{result.latest_version})")
|
||||
|
||||
# Development track
|
||||
print("\n" + "=" * 50)
|
||||
print("[DEVELOPMENT] (main branch)")
|
||||
print("=" * 50)
|
||||
if result.dev_info:
|
||||
if result.dev_info.commits_ahead > 0:
|
||||
print(f" {result.dev_info.commits_ahead} commit(s) ahead of {result.dev_info.base_tag}")
|
||||
if result.dev_update_available:
|
||||
print(" -> Updates available!")
|
||||
else:
|
||||
print(" [OK] You have the latest development version")
|
||||
|
||||
if result.dev_info.recent_commits:
|
||||
print("\n Recent commits:")
|
||||
for commit in result.dev_info.recent_commits[:5]:
|
||||
# Encode safely for Windows console
|
||||
msg = commit.message[:60].encode('ascii', 'replace').decode('ascii')
|
||||
print(f" * [{commit.short_sha}] {msg}")
|
||||
else:
|
||||
print(" [OK] Release is up to date with main branch")
|
||||
else:
|
||||
print(" Could not fetch development info")
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("To update:")
|
||||
print(" Stable: pip install -U whisperjav")
|
||||
print(" Dev: pip install -U git+https://github.com/meizhong986/whisperjav.git")
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import time
|
|||
import queue
|
||||
import threading
|
||||
import subprocess
|
||||
import webbrowser
|
||||
from pathlib import Path
|
||||
from typing import Optional, List, Dict, Any
|
||||
import webview
|
||||
|
|
@ -2104,17 +2105,30 @@ class WhisperJAVAPI:
|
|||
"""
|
||||
Check if a newer version of WhisperJAV is available.
|
||||
|
||||
Checks both stable releases and development (latest commit) updates.
|
||||
|
||||
Args:
|
||||
force: If True, bypass cache and check immediately
|
||||
|
||||
Returns:
|
||||
dict: Update check result with:
|
||||
- update_available: bool
|
||||
- success: bool
|
||||
- current_version: str
|
||||
- current_commit: str or None (short SHA)
|
||||
- installation_type: "release" or "dev"
|
||||
|
||||
Stable track:
|
||||
- update_available: bool
|
||||
- latest_version: str (if available)
|
||||
- notification_level: str (critical/major/minor/patch/none)
|
||||
- release_url: str (if update available)
|
||||
- release_notes: str (if update available)
|
||||
|
||||
Development track:
|
||||
- dev_update_available: bool
|
||||
- dev_commits_ahead: int
|
||||
- dev_recent_commits: list of {sha, message, author, date}
|
||||
|
||||
- error: str (if check failed)
|
||||
"""
|
||||
try:
|
||||
|
|
@ -2123,25 +2137,55 @@ class WhisperJAVAPI:
|
|||
get_update_notification_level,
|
||||
)
|
||||
|
||||
result = check_for_updates(force=force)
|
||||
result = check_for_updates(force=force, include_dev=True)
|
||||
|
||||
response = {
|
||||
"success": True,
|
||||
"update_available": result.update_available,
|
||||
# Current installation info
|
||||
"current_version": result.current_version,
|
||||
"current_commit": result.current_commit,
|
||||
"installation_type": result.installation_type,
|
||||
# Stable release track
|
||||
"update_available": result.update_available,
|
||||
"latest_version": result.latest_version,
|
||||
"from_cache": result.from_cache,
|
||||
# Development track
|
||||
"dev_update_available": result.dev_update_available,
|
||||
}
|
||||
|
||||
if result.error:
|
||||
response["error"] = result.error
|
||||
|
||||
# Stable release info
|
||||
if result.version_info:
|
||||
response["notification_level"] = get_update_notification_level(result)
|
||||
response["release_url"] = result.version_info.release_url
|
||||
response["release_notes"] = result.version_info.release_notes[:500] # Truncate
|
||||
response["is_prerelease"] = result.version_info.is_prerelease
|
||||
|
||||
# Development track info
|
||||
if result.dev_info:
|
||||
response["dev_commits_ahead"] = result.dev_info.commits_ahead
|
||||
response["dev_base_tag"] = result.dev_info.base_tag
|
||||
response["dev_recent_commits"] = [
|
||||
{
|
||||
"sha": c.short_sha,
|
||||
"message": c.message,
|
||||
"author": c.author,
|
||||
"date": c.date,
|
||||
"url": c.url
|
||||
}
|
||||
for c in result.dev_info.recent_commits[:5]
|
||||
]
|
||||
if result.dev_info.latest_commit:
|
||||
response["dev_latest_commit"] = {
|
||||
"sha": result.dev_info.latest_commit.short_sha,
|
||||
"message": result.dev_info.latest_commit.message,
|
||||
"author": result.dev_info.latest_commit.author,
|
||||
"date": result.dev_info.latest_commit.date,
|
||||
"url": result.dev_info.latest_commit.url
|
||||
}
|
||||
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
|
|
@ -2149,6 +2193,7 @@ class WhisperJAVAPI:
|
|||
"success": False,
|
||||
"error": str(e),
|
||||
"update_available": False,
|
||||
"dev_update_available": False,
|
||||
"current_version": "unknown"
|
||||
}
|
||||
|
||||
|
|
@ -2274,3 +2319,19 @@ class WhisperJAVAPI:
|
|||
"error": str(e),
|
||||
"should_exit": False
|
||||
}
|
||||
|
||||
def open_url(self, url: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Open a URL in the system's default web browser.
|
||||
|
||||
Args:
|
||||
url: The URL to open
|
||||
|
||||
Returns:
|
||||
dict: Success status
|
||||
"""
|
||||
try:
|
||||
webbrowser.open(url)
|
||||
return {"success": True}
|
||||
except Exception as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
|
|
|
|||
|
|
@ -4076,6 +4076,240 @@ const UpdateManager = {
|
|||
}
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Update Check Manager (manual update check from menu)
|
||||
// ============================================================
|
||||
const UpdateCheckManager = {
|
||||
modal: null,
|
||||
|
||||
init() {
|
||||
this.modal = document.getElementById('updateCheckModal');
|
||||
if (!this.modal) {
|
||||
console.warn('UpdateCheckManager: modal not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Menu item click
|
||||
const checkBtn = document.getElementById('checkUpdatesBtn');
|
||||
if (checkBtn) {
|
||||
checkBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
document.getElementById('themeMenu')?.classList.remove('active');
|
||||
this.show();
|
||||
});
|
||||
}
|
||||
|
||||
// Modal close handlers
|
||||
document.getElementById('updateCheckModalClose')?.addEventListener('click', () => this.close());
|
||||
document.getElementById('updateCheckLater')?.addEventListener('click', () => this.close());
|
||||
document.getElementById('updateCheckNow')?.addEventListener('click', () => this.startUpdate());
|
||||
document.getElementById('updateCheckDownload')?.addEventListener('click', () => this.openDownloadPage());
|
||||
|
||||
// Close on overlay click
|
||||
this.modal.addEventListener('click', (e) => {
|
||||
if (e.target === this.modal) this.close();
|
||||
});
|
||||
|
||||
// Close on Escape key
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && this.modal.classList.contains('active')) {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async show() {
|
||||
// Reset and show loading state
|
||||
this.showLoading();
|
||||
this.modal.classList.add('active');
|
||||
|
||||
try {
|
||||
// Force fresh check (bypass cache)
|
||||
const result = await pywebview.api.check_for_updates(true);
|
||||
this.showResult(result);
|
||||
} catch (err) {
|
||||
console.error('Update check error:', err);
|
||||
this.showError(err);
|
||||
}
|
||||
},
|
||||
|
||||
// Store the last result for update actions
|
||||
lastResult: null,
|
||||
|
||||
showLoading() {
|
||||
document.getElementById('updateCheckLoading').style.display = 'block';
|
||||
document.getElementById('updateCheckResult').style.display = 'none';
|
||||
document.getElementById('updateCheckError').style.display = 'none';
|
||||
},
|
||||
|
||||
showResult(result) {
|
||||
this.lastResult = result;
|
||||
document.getElementById('updateCheckLoading').style.display = 'none';
|
||||
document.getElementById('updateCheckResult').style.display = 'block';
|
||||
|
||||
// Set current version info
|
||||
document.getElementById('updateCurrentVersion').textContent = 'v' + result.current_version;
|
||||
|
||||
// Show commit hash if available
|
||||
const commitEl = document.getElementById('updateCurrentCommit');
|
||||
if (commitEl && result.current_commit) {
|
||||
commitEl.textContent = '(' + result.current_commit.substring(0, 7) + ')';
|
||||
commitEl.style.display = 'inline';
|
||||
} else if (commitEl) {
|
||||
commitEl.style.display = 'none';
|
||||
}
|
||||
|
||||
// === Stable Release Track ===
|
||||
this.updateStableTrack(result);
|
||||
|
||||
// === Development Track ===
|
||||
this.updateDevTrack(result);
|
||||
},
|
||||
|
||||
updateStableTrack(result) {
|
||||
const stableUpToDate = document.getElementById('stableUpToDate');
|
||||
const stableUpdateAvailable = document.getElementById('stableUpdateAvailable');
|
||||
const stableReleaseNotes = document.getElementById('stableReleaseNotes');
|
||||
const stableActions = document.getElementById('stableActions');
|
||||
const stableLatestVersion = document.getElementById('stableLatestVersion');
|
||||
const stableNewVersion = document.getElementById('stableNewVersion');
|
||||
const stableBadge = document.getElementById('stableUpdateBadge');
|
||||
|
||||
if (result.update_available && result.latest_version) {
|
||||
// Stable update available
|
||||
stableUpToDate.style.display = 'none';
|
||||
stableUpdateAvailable.style.display = 'flex';
|
||||
stableNewVersion.textContent = result.latest_version;
|
||||
|
||||
// Set badge color based on notification level
|
||||
const level = result.notification_level || 'patch';
|
||||
stableBadge.textContent = level.toUpperCase();
|
||||
stableBadge.className = 'update-badge ' + level;
|
||||
|
||||
// Show release notes if available
|
||||
if (result.release_notes) {
|
||||
const notesContent = document.getElementById('stableReleaseNotesContent');
|
||||
notesContent.innerHTML = this.parseMarkdown(result.release_notes);
|
||||
stableReleaseNotes.style.display = 'block';
|
||||
} else {
|
||||
stableReleaseNotes.style.display = 'none';
|
||||
}
|
||||
|
||||
// Show action button (Download for major, Update for others)
|
||||
if (level === 'major') {
|
||||
stableActions.innerHTML = '<button id="updateToStable" class="btn btn-primary btn-sm">Download from GitHub</button>';
|
||||
document.getElementById('updateToStable').addEventListener('click', () => this.openDownloadPage());
|
||||
} else {
|
||||
stableActions.innerHTML = '<button id="updateToStable" class="btn btn-primary btn-sm">Update to Stable</button>';
|
||||
document.getElementById('updateToStable').addEventListener('click', () => this.startUpdate('stable'));
|
||||
}
|
||||
stableActions.style.display = 'block';
|
||||
} else {
|
||||
// Up to date with stable
|
||||
stableUpToDate.style.display = 'flex';
|
||||
stableLatestVersion.textContent = result.current_version;
|
||||
stableUpdateAvailable.style.display = 'none';
|
||||
stableReleaseNotes.style.display = 'none';
|
||||
stableActions.style.display = 'none';
|
||||
}
|
||||
},
|
||||
|
||||
updateDevTrack(result) {
|
||||
const devUpToDate = document.getElementById('devUpToDate');
|
||||
const devUpdateAvailable = document.getElementById('devUpdateAvailable');
|
||||
const devCommitsList = document.getElementById('devCommitsList');
|
||||
const devActions = document.getElementById('devActions');
|
||||
const devCommitsAhead = document.getElementById('devCommitsAhead');
|
||||
const devRecentCommits = document.getElementById('devRecentCommits');
|
||||
|
||||
if (result.dev_update_available && result.dev_commits_ahead > 0) {
|
||||
// Dev updates available
|
||||
devUpToDate.style.display = 'none';
|
||||
devUpdateAvailable.style.display = 'flex';
|
||||
devCommitsAhead.textContent = result.dev_commits_ahead;
|
||||
|
||||
// Show recent commits list
|
||||
if (result.dev_recent_commits && result.dev_recent_commits.length > 0) {
|
||||
devRecentCommits.innerHTML = result.dev_recent_commits
|
||||
.slice(0, 5) // Show max 5 recent commits
|
||||
.map(commit => {
|
||||
const shortSha = commit.short_sha || commit.sha.substring(0, 7);
|
||||
const message = this.escapeHtml(commit.message.split('\n')[0]); // First line only
|
||||
return `<li><code>${shortSha}</code> ${message}</li>`;
|
||||
})
|
||||
.join('');
|
||||
devCommitsList.style.display = 'block';
|
||||
} else {
|
||||
devCommitsList.style.display = 'none';
|
||||
}
|
||||
|
||||
// Show action button
|
||||
devActions.innerHTML = '<button id="updateToDev" class="btn btn-secondary btn-sm">Update to Latest Dev</button>';
|
||||
document.getElementById('updateToDev').addEventListener('click', () => this.startUpdate('dev'));
|
||||
devActions.style.display = 'block';
|
||||
} else {
|
||||
// Up to date with dev
|
||||
devUpToDate.style.display = 'flex';
|
||||
devUpdateAvailable.style.display = 'none';
|
||||
devCommitsList.style.display = 'none';
|
||||
devActions.style.display = 'none';
|
||||
}
|
||||
},
|
||||
|
||||
showError(error) {
|
||||
document.getElementById('updateCheckLoading').style.display = 'none';
|
||||
document.getElementById('updateCheckResult').style.display = 'none';
|
||||
document.getElementById('updateCheckError').style.display = 'block';
|
||||
},
|
||||
|
||||
async startUpdate(track = 'stable') {
|
||||
// Close modal
|
||||
this.close();
|
||||
|
||||
// Use existing UpdateManager to show progress and start update
|
||||
// Both tracks use the same update mechanism (pip install from GitHub)
|
||||
// The difference is just which version was chosen
|
||||
console.log('Starting update from track:', track);
|
||||
UpdateManager.startUpdate();
|
||||
},
|
||||
|
||||
async openDownloadPage() {
|
||||
// Open GitHub releases in browser
|
||||
try {
|
||||
await pywebview.api.open_url('https://github.com/meizhong986/WhisperJAV/releases');
|
||||
} catch (err) {
|
||||
// Fallback: open in new window
|
||||
window.open('https://github.com/meizhong986/WhisperJAV/releases', '_blank');
|
||||
}
|
||||
this.close();
|
||||
},
|
||||
|
||||
close() {
|
||||
this.modal.classList.remove('active');
|
||||
},
|
||||
|
||||
escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
},
|
||||
|
||||
parseMarkdown(text) {
|
||||
// Simple markdown to HTML conversion
|
||||
if (!text) return '';
|
||||
return text
|
||||
.replace(/^### (.+)$/gm, '<h4>$1</h4>')
|
||||
.replace(/^## (.+)$/gm, '<h3>$1</h3>')
|
||||
.replace(/^# (.+)$/gm, '<h2>$1</h2>')
|
||||
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
||||
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
||||
.replace(/^\* (.+)$/gm, '<li>$1</li>')
|
||||
.replace(/^- (.+)$/gm, '<li>$1</li>')
|
||||
.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>')
|
||||
.replace(/\n\n/g, '<br><br>');
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Initialization
|
||||
// ============================================================
|
||||
|
|
@ -4098,6 +4332,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
ThemeManager.init();
|
||||
EnsembleManager.init();
|
||||
UpdateManager.init();
|
||||
UpdateCheckManager.init();
|
||||
|
||||
// Initial validation
|
||||
FormManager.validateForm();
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@
|
|||
<button class="theme-option" data-theme="google" role="menuitem">Google Theme</button>
|
||||
<button class="theme-option" data-theme="carbon" role="menuitem">IBM Carbon Theme</button>
|
||||
<button class="theme-option" data-theme="primer" role="menuitem">GitHub Primer Theme</button>
|
||||
<div class="theme-menu-divider"></div>
|
||||
<button class="theme-option update-check-option" id="checkUpdatesBtn" role="menuitem">
|
||||
<span class="update-check-icon">⬆</span> Check for Updates
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
|
@ -838,6 +842,95 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Update Check Modal -->
|
||||
<div class="modal-overlay" id="updateCheckModal">
|
||||
<div class="modal-dialog modal-update-check">
|
||||
<div class="modal-header">
|
||||
<h2>Check for Updates</h2>
|
||||
<button class="modal-close" id="updateCheckModalClose" aria-label="Close">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- Loading State -->
|
||||
<div id="updateCheckLoading" class="update-check-loading">
|
||||
<div class="spinner"></div>
|
||||
<p>Checking for updates...</p>
|
||||
</div>
|
||||
|
||||
<!-- Result State -->
|
||||
<div id="updateCheckResult" class="update-check-result" style="display: none;">
|
||||
<!-- Current Version Info -->
|
||||
<div class="current-version-info">
|
||||
<span class="version-label">Installed:</span>
|
||||
<span class="version-value" id="updateCurrentVersion">v1.7.5</span>
|
||||
<span class="commit-hash" id="updateCurrentCommit"></span>
|
||||
</div>
|
||||
|
||||
<!-- Stable Release Track -->
|
||||
<div class="update-track update-track-stable" id="stableTrack">
|
||||
<div class="track-header">
|
||||
<span class="track-icon">📦</span>
|
||||
<span class="track-title">Stable Release</span>
|
||||
</div>
|
||||
<div class="track-content">
|
||||
<div id="stableUpToDate" class="track-status track-status-ok" style="display: none;">
|
||||
<span class="status-icon">✓</span>
|
||||
<span>v<span id="stableLatestVersion">1.7.5</span> - You're up to date</span>
|
||||
</div>
|
||||
<div id="stableUpdateAvailable" class="track-status track-status-update" style="display: none;">
|
||||
<span class="status-icon">↑</span>
|
||||
<span>v<span id="stableNewVersion">1.7.6</span> available</span>
|
||||
<span class="update-badge" id="stableUpdateBadge">PATCH</span>
|
||||
</div>
|
||||
<div id="stableReleaseNotes" class="release-notes-section" style="display: none;">
|
||||
<details class="release-notes-details">
|
||||
<summary>Release Notes</summary>
|
||||
<div id="stableReleaseNotesContent" class="release-notes-content"></div>
|
||||
</details>
|
||||
</div>
|
||||
<div id="stableActions" class="track-actions" style="display: none;">
|
||||
<button id="updateToStable" class="btn btn-primary btn-sm">Update to Stable</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Development Track -->
|
||||
<div class="update-track update-track-dev" id="devTrack">
|
||||
<div class="track-header">
|
||||
<span class="track-icon">⚡</span>
|
||||
<span class="track-title">Development</span>
|
||||
<span class="track-subtitle">(main branch)</span>
|
||||
</div>
|
||||
<div class="track-content">
|
||||
<div id="devUpToDate" class="track-status track-status-ok" style="display: none;">
|
||||
<span class="status-icon">✓</span>
|
||||
<span>Up to date with main branch</span>
|
||||
</div>
|
||||
<div id="devUpdateAvailable" class="track-status track-status-update" style="display: none;">
|
||||
<span class="status-icon">↑</span>
|
||||
<span><span id="devCommitsAhead">0</span> commit(s) ahead</span>
|
||||
</div>
|
||||
<div id="devCommitsList" class="dev-commits-list" style="display: none;">
|
||||
<div class="commits-header">Recent commits:</div>
|
||||
<ul id="devRecentCommits" class="commits-list"></ul>
|
||||
</div>
|
||||
<div id="devActions" class="track-actions" style="display: none;">
|
||||
<button id="updateToDev" class="btn btn-secondary btn-sm">Update to Latest Dev</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
<div id="updateCheckError" class="update-check-error" style="display: none;">
|
||||
<p>Failed to check for updates. Please check your internet connection and try again.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" id="updateCheckFooter">
|
||||
<button id="updateCheckLater" class="btn btn-secondary">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Customize Component Modal -->
|
||||
<div class="modal-overlay" id="customizeModal">
|
||||
<div class="modal-dialog modal-lg">
|
||||
|
|
|
|||
|
|
@ -2405,3 +2405,379 @@ button:focus-visible,
|
|||
.changelog-error p {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Update Check Modal
|
||||
============================================================ */
|
||||
|
||||
/* Theme menu divider */
|
||||
.theme-menu-divider {
|
||||
height: 1px;
|
||||
background: var(--border-color);
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
/* Check for Updates menu item */
|
||||
.update-check-option {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.update-check-icon {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
/* Update check loading state */
|
||||
.update-check-loading {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.update-check-loading .spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid var(--border-color);
|
||||
border-top-color: var(--primary-color);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 16px;
|
||||
}
|
||||
|
||||
.update-check-loading p {
|
||||
color: var(--text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Result container */
|
||||
.update-check-result {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Version info display */
|
||||
.version-info {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.version-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.version-label {
|
||||
color: var(--text-secondary);
|
||||
min-width: 70px;
|
||||
}
|
||||
|
||||
.version-value {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.version-current .version-value {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.version-latest .version-value {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Update type badges */
|
||||
.update-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
margin-left: 8px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.update-badge.patch {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.update-badge.minor {
|
||||
background: #17a2b8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.update-badge.major {
|
||||
background: #fd7e14;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.update-badge.critical {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Up to date message */
|
||||
.update-up-to-date {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 30px 20px;
|
||||
color: #28a745;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.update-up-to-date .checkmark {
|
||||
font-size: 48px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* Release notes */
|
||||
.release-notes-details {
|
||||
margin-top: 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.release-notes-details summary {
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
background: var(--hover-tab-bg);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.release-notes-details summary:hover {
|
||||
background: var(--disabled-bg);
|
||||
}
|
||||
|
||||
.release-notes-content {
|
||||
padding: 12px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.release-notes-content h3,
|
||||
.release-notes-content h4 {
|
||||
margin: 12px 0 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.release-notes-content h3:first-child,
|
||||
.release-notes-content h4:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.release-notes-content li {
|
||||
margin: 4px 0;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.release-notes-content code {
|
||||
background: var(--disabled-bg);
|
||||
padding: 1px 4px;
|
||||
border-radius: 3px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Error state */
|
||||
.update-check-error {
|
||||
text-align: center;
|
||||
padding: 30px 20px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.update-check-error p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Dual-Track Update Modal Styles
|
||||
============================================================ */
|
||||
|
||||
/* Modal sizing */
|
||||
.modal-update-check {
|
||||
max-width: 520px;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
/* Current version info at top */
|
||||
.current-version-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
background: var(--inactive-tab-bg);
|
||||
border-radius: var(--border-radius);
|
||||
margin-bottom: 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.current-version-info .version-label {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.current-version-info .version-value {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.current-version-info .commit-hash {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Update track containers */
|
||||
.update-track {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius-lg);
|
||||
margin-bottom: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.update-track:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Track header */
|
||||
.track-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 14px;
|
||||
background: var(--inactive-tab-bg);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.track-icon {
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.track-title {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.track-subtitle {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* Stable track specific colors */
|
||||
.update-track-stable .track-header {
|
||||
background: linear-gradient(135deg, rgba(40, 167, 69, 0.08) 0%, rgba(40, 167, 69, 0.04) 100%);
|
||||
}
|
||||
|
||||
.update-track-stable .track-icon {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
/* Dev track specific colors */
|
||||
.update-track-dev .track-header {
|
||||
background: linear-gradient(135deg, rgba(253, 126, 20, 0.08) 0%, rgba(253, 126, 20, 0.04) 100%);
|
||||
}
|
||||
|
||||
.update-track-dev .track-icon {
|
||||
color: #fd7e14;
|
||||
}
|
||||
|
||||
/* Track content */
|
||||
.track-content {
|
||||
padding: 12px 14px;
|
||||
}
|
||||
|
||||
/* Track status line */
|
||||
.track-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.track-status .status-icon {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.track-status-ok {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.track-status-ok .status-icon {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.track-status-update {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.track-status-update .status-icon {
|
||||
color: #fd7e14;
|
||||
}
|
||||
|
||||
/* Release notes section */
|
||||
.release-notes-section {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* Track actions */
|
||||
.track-actions {
|
||||
margin-top: 12px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
/* Dev commits list */
|
||||
.dev-commits-list {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
background: var(--inactive-tab-bg);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.commits-header {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.3px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.commits-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.commits-list li {
|
||||
font-size: 12px;
|
||||
padding: 4px 0;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
line-height: 1.4;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.commits-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.commits-list code {
|
||||
background: var(--disabled-bg);
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10px;
|
||||
color: var(--primary-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Ensure spinner shows properly in loading state */
|
||||
.update-check-loading .spinner {
|
||||
display: block;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue