Initial commit: Add playlist downloader project with UI and core functionality

This commit is contained in:
2026-06-28 10:34:48 +02:00
commit 605dede363
49 changed files with 8300 additions and 0 deletions
@@ -0,0 +1 @@
{"specId": "08bd2768-eb0f-4c89-a4c0-485de050e110", "workflowType": "requirements-first", "specType": "feature"}
@@ -0,0 +1,867 @@
# Volume Normalization Modal - Design Document
## Overview
The Volume Normalization Modal is a PyQt6 dialog component that enables users to configure audio loudness normalization settings before downloading tracks. The feature integrates with the existing Download tab and provides both preset configurations and custom parameter tuning. Settings persist globally across application sessions, ensuring consistent audio processing behavior.
### Key Design Goals
1. **User-Friendly Configuration**: Provide preset options for non-technical users while allowing advanced customization
2. **Persistent Settings**: Store user preferences to avoid reconfiguration across sessions
3. **Seamless Integration**: Integrate with existing downloader and config modules without breaking changes
4. **Validation and Safety**: Ensure all parameters conform to FFmpeg loudnorm specifications
5. **Consistent UI/UX**: Match existing modal styling and application design patterns
---
## Architecture
### System Context
The Volume Normalization Modal operates within a three-layer architecture:
```
┌─────────────────────────────────────────────────────────────┐
│ UI Layer (PyQt6) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ DownloadTab │ │
│ │ ├─ Normalization Settings Button │ │
│ │ └─ Preset Indicator │ │
│ │ │ │
│ │ NormalizationModal (Dialog) │ │
│ │ ├─ Preset Selection (Radio Buttons) │ │
│ │ ├─ Parameter Input Fields (I, TP, LRA) │ │
│ │ ├─ Validation & Error Display │ │
│ │ └─ Apply/Cancel Buttons │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Config Layer (Persistence) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Config Module (src/core/config.py) │ │
│ │ ├─ Normalization Settings Storage │ │
│ │ ├─ Preset Definitions │ │
│ │ └─ Default Configuration │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Core Layer (Processing) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ YouTubeDownloader (src/core/downloader.py) │ │
│ │ ├─ normalize_volume() - Uses Config settings │ │
│ │ ├─ Parameter Validation │ │
│ │ └─ FFmpeg Command Construction │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### Data Flow
1. **Initialization**: Application starts → Config loads saved settings → Modal displays current preset
2. **User Interaction**: User selects preset/custom params → Modal validates → User clicks Apply
3. **Persistence**: Modal saves settings to Config → Config persists to storage
4. **Download**: Download initiated → Downloader reads settings from Config → FFmpeg applies normalization
---
## Components and Interfaces
### 1. NormalizationModal Class
**File**: `src/ui/components/normalization_modal_qt.py`
**Responsibilities**:
- Display preset selection interface
- Manage custom parameter input fields
- Validate user input against FFmpeg specifications
- Handle apply/cancel operations
- Load and display current settings
**Key Methods**:
```python
class NormalizationModal(QDialog):
"""Modal dialog for configuring volume normalization settings."""
settings_applied = pyqtSignal(dict) # Emitted when Apply is clicked
def __init__(self, parent, config):
"""Initialize modal with current settings from config."""
def _load_current_settings(self):
"""Load saved settings from Config module."""
def _on_preset_selected(self, preset_name: str):
"""Handle preset selection - update fields and enable/disable editing."""
def _on_parameter_changed(self, param_name: str, value: str):
"""Handle parameter input changes - validate and update UI."""
def _validate_parameters(self) -> tuple[bool, str]:
"""Validate all parameters against FFmpeg specifications."""
def _update_parameter_fields(self, params: dict):
"""Update input fields with parameter values."""
def _enable_custom_editing(self, enabled: bool):
"""Enable/disable custom parameter input fields."""
def _on_apply(self):
"""Save settings and close modal."""
def _on_cancel(self):
"""Close modal without saving."""
def get_current_settings(self) -> dict:
"""Return current settings as dict."""
```
**Signals**:
- `settings_applied(dict)`: Emitted when Apply is clicked with new settings
### 2. DownloadTab Integration
**File**: `src/ui/tabs/download_tab_qt.py` (modifications)
**Changes**:
- Add "Normalization Settings" button to control panel
- Add preset indicator label showing current active preset
- Connect button to open NormalizationModal
- Display tooltip with current parameters on hover
**New Methods**:
```python
def _open_normalization_modal(self):
"""Open the normalization settings modal."""
def _update_preset_indicator(self, preset_name: str):
"""Update the preset indicator label."""
def _on_normalization_settings_applied(self, settings: dict):
"""Handle settings applied from modal."""
```
### 3. Config Module Extensions
**File**: `src/core/config.py` (modifications)
**New Class**: `NormalizationSettings`
```python
class NormalizationSettings:
"""Manages normalization settings persistence."""
# Preset definitions
PRESETS = {
'Quiet': {'I': -18, 'TP': -1.5, 'LRA': 11},
'Normal': {'I': -14, 'TP': -1.5, 'LRA': 11},
'Loud': {'I': -10, 'TP': -1.5, 'LRA': 11},
'Very Loud': {'I': -6, 'TP': -1.5, 'LRA': 11},
}
# Parameter validation ranges
PARAM_RANGES = {
'I': (-23, 0), # Integrated Loudness (LUFS)
'TP': (-5, 0), # True Peak (dBFS)
'LRA': (1, 20), # Loudness Range (LU)
}
@classmethod
def load_settings(cls) -> dict:
"""Load normalization settings from storage."""
@classmethod
def save_settings(cls, preset: str, custom_params: dict = None) -> bool:
"""Save normalization settings to storage."""
@classmethod
def get_default_settings(cls) -> dict:
"""Return default settings (Normal preset)."""
@classmethod
def validate_parameters(cls, params: dict) -> tuple[bool, str]:
"""Validate parameters against ranges."""
@classmethod
def get_preset_params(cls, preset_name: str) -> dict:
"""Get parameters for a preset."""
```
**Storage Format** (JSON in config file):
```json
{
"normalization": {
"current_preset": "Normal",
"custom_parameters": {
"I": -14,
"TP": -1.5,
"LRA": 11
}
}
}
```
### 4. Downloader Module Integration
**File**: `src/core/downloader.py` (modifications)
**Changes to `normalize_volume` method**:
```python
def normalize_volume(self, filepath: str, timeout: int = 60) -> bool:
"""Normalize audio loudness using configured settings."""
if not self.ffmpeg:
return True
# Load current settings from Config
settings = NormalizationSettings.load_settings()
params = settings.get('custom_parameters', {})
# Validate parameters
valid, error_msg = NormalizationSettings.validate_parameters(params)
if not valid:
logger.error(f"Invalid normalization parameters: {error_msg}")
return False
# Construct FFmpeg filter string
filter_str = f"loudnorm=I={params['I']}:TP={params['TP']}:LRA={params['LRA']}"
# ... rest of implementation
```
---
## Data Models
### Normalization Settings Structure
```python
{
"current_preset": str, # "Quiet", "Normal", "Loud", "Very Loud", or "Custom"
"custom_parameters": {
"I": float, # Integrated Loudness (-23 to 0 LUFS)
"TP": float, # True Peak (-5 to 0 dBFS)
"LRA": float, # Loudness Range (1 to 20 LU)
}
}
```
### Preset Definitions
```python
PRESETS = {
"Quiet": {
"I": -18,
"TP": -1.5,
"LRA": 11,
"description": "For content that should remain quiet"
},
"Normal": {
"I": -14,
"TP": -1.5,
"LRA": 11,
"description": "Default, suitable for most music"
},
"Loud": {
"I": -10,
"TP": -1.5,
"LRA": 11,
"description": "For content that should be louder"
},
"Very Loud": {
"I": -6,
"TP": -1.5,
"LRA": 11,
"description": "For maximum loudness"
}
}
```
### Parameter Validation Ranges
```python
PARAM_RANGES = {
"I": {
"min": -23,
"max": 0,
"unit": "LUFS",
"description": "Integrated Loudness"
},
"TP": {
"min": -5,
"max": 0,
"unit": "dBFS",
"description": "True Peak"
},
"LRA": {
"min": 1,
"max": 20,
"unit": "LU",
"description": "Loudness Range"
}
}
```
---
## Module Changes
### Config Module (`src/core/config.py`)
**Additions**:
1. Add `NormalizationSettings` class with preset definitions and validation logic
2. Add storage path constant for normalization settings
3. Add methods to load/save normalization settings
4. Add default settings initialization
**Storage Location**: Settings stored in application config directory (platform-specific):
- Windows: `%APPDATA%/dl_yt/config.json`
- macOS: `~/Library/Application Support/dl_yt/config.json`
- Linux: `~/.config/dl_yt/config.json`
### Downloader Module (`src/core/downloader.py`)
**Modifications**:
1. Update `__init__` to load normalization settings from Config
2. Modify `normalize_volume` to use configured parameters instead of hardcoded values
3. Add parameter validation before FFmpeg command construction
4. Add logging for parameter usage and validation failures
**Backward Compatibility**: If no settings are configured, default to "Normal" preset (existing behavior)
### DownloadTab Module (`src/ui/tabs/download_tab_qt.py`)
**Additions**:
1. Add "Normalization Settings" button to control panel
2. Add preset indicator label
3. Add method to open NormalizationModal
4. Add method to handle settings changes
5. Connect modal signals to update UI
**Button Placement**: In the control panel area, positioned after "Browse Output" button
---
## UI/UX Design
### Modal Layout
```
┌─────────────────────────────────────────────────────────┐
│ Volume Normalization Settings [X] │
├─────────────────────────────────────────────────────────┤
│ │
│ Current Preset: Normal │
│ Parameters: I=-14 TP=-1.5 LRA=11 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Presets: │ │
│ │ ○ Quiet (I=-18, TP=-1.5, LRA=11) │ │
│ │ ● Normal (I=-14, TP=-1.5, LRA=11) │ │
│ │ ○ Loud (I=-10, TP=-1.5, LRA=11) │ │
│ │ ○ Very Loud (I=-6, TP=-1.5, LRA=11) │ │
│ │ ○ Custom │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Custom Parameters (disabled): │ │
│ │ Integrated Loudness (I): [-14] LUFS │ │
│ │ True Peak (TP): [-1.5] dBFS │ │
│ │ Loudness Range (LRA): [11] LU │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ [Error message area - hidden when no errors] │
│ │
│ [Cancel] [Apply] │
└─────────────────────────────────────────────────────────┘
```
### Styling
- **Background**: #212121 (dark gray, matching DeleteModal)
- **Text Color**: #ffffff (white for primary, #cccccc for secondary)
- **Input Fields**: QLineEdit/QDoubleSpinBox with dark theme
- **Buttons**:
- Cancel: Gray (#757575)
- Apply: Blue (#2196F3) when enabled, gray when disabled
- **Modal Size**: 500x400 pixels
- **Font**: 11pt for labels, 12pt for buttons
### Error Display
Error messages appear in a red-tinted area above the buttons:
- Non-numeric input: "Parameter must be a number"
- Out of range: "Parameter {name} must be between {min} and {max}"
- Save failure: "Failed to save settings. Please try again."
### Download Tab Integration
Preset indicator in control panel:
```
[Normalization Settings] | Preset: Normal (I=-14, TP=-1.5, LRA=11)
```
Tooltip on hover shows full parameter details.
---
## State Management
### Modal State Lifecycle
```
┌─────────────────────────────────────────────────────────┐
│ CLOSED │
│ (Settings not loaded, modal not visible) │
└────────────────────┬────────────────────────────────────┘
│ User clicks "Normalization Settings"
┌─────────────────────────────────────────────────────────┐
│ OPENING │
│ (Load settings from Config, populate UI) │
└────────────────────┬────────────────────────────────────┘
│ Settings loaded
┌─────────────────────────────────────────────────────────┐
│ OPEN │
│ (Modal visible, user can interact) │
└────────┬──────────────────────────────────┬─────────────┘
│ User clicks Cancel │ User clicks Apply
↓ ↓
CLOSED VALIDATING
(Discard changes) (Check parameters)
┌────┴────┐
│ │
Valid │ │ Invalid
↓ ↓
SAVING ERROR
(Persist) (Show error)
│ │
└────┬─────┘
CLOSED
```
### Settings Loading
1. **Application Startup**:
- Config module loads settings from storage
- If missing/corrupted, use defaults
- Log warning if loading fails
2. **Modal Open**:
- Load current settings from Config
- Display current preset as selected
- Populate parameter fields
- Enable/disable custom fields based on preset
3. **Download Initiation**:
- Downloader reads settings from Config
- Validate parameters before use
- Log parameters being applied
### Settings Saving
1. **Apply Button Clicked**:
- Validate all parameters
- If invalid, show error and don't save
- If valid, save to Config
- Emit signal with new settings
- Close modal
2. **Config Save**:
- Write to persistent storage
- Log success/failure
- Notify UI of changes
---
## Integration Points
### 1. DownloadTab ↔ NormalizationModal
**Connection**:
```python
# In DownloadTab.__init__
self.normalization_modal = NormalizationModal(self, self.config)
self.normalization_modal.settings_applied.connect(self._on_normalization_settings_applied)
self.normalization_btn.clicked.connect(self._open_normalization_modal)
```
**Data Flow**:
- DownloadTab passes Config instance to modal
- Modal emits `settings_applied` signal when Apply clicked
- DownloadTab updates preset indicator
### 2. NormalizationModal ↔ Config
**Connection**:
```python
# In NormalizationModal
settings = NormalizationSettings.load_settings()
NormalizationSettings.save_settings(preset, custom_params)
```
**Data Flow**:
- Modal reads current settings on open
- Modal saves settings on Apply
- Config handles persistence
### 3. Downloader ↔ Config
**Connection**:
```python
# In YouTubeDownloader.normalize_volume
settings = NormalizationSettings.load_settings()
params = settings['custom_parameters']
```
**Data Flow**:
- Downloader reads settings before normalization
- Downloader validates parameters
- Downloader constructs FFmpeg command with parameters
### 4. Backward Compatibility
**Existing Code**:
- Current `normalize_volume` uses hardcoded: `loudnorm=I=-14:TP=-1.5:LRA=11`
- This matches the "Normal" preset
**Migration**:
- If no settings exist, default to "Normal" preset
- Existing behavior preserved
- No breaking changes to API
---
## Correctness Properties
*A property is a characteristic or behavior that should hold true across all valid executions of a system—essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
### Property 1: Preset Selection Updates Parameters
*For any* preset selection (Quiet, Normal, Loud, Very Loud), selecting that preset shall update the parameter input fields to display the correct values for that preset.
**Validates: Requirements 2.3**
### Property 2: Non-Custom Presets Disable Editing
*For any* non-Custom preset selection, the parameter input fields shall be disabled (read-only) and cannot be edited by the user.
**Validates: Requirements 2.4**
### Property 3: Custom Preset Enables Editing
*When* the Custom preset is selected, the parameter input fields shall be enabled and editable by the user.
**Validates: Requirements 2.5**
### Property 4: Modal Loads Current Settings
*For any* application state with saved normalization settings, opening the NormalizationModal shall display the currently active preset as pre-selected.
**Validates: Requirements 2.6**
### Property 5: Parameter Validation Enforces Ranges
*For any* parameter value entered in a custom parameter field, if the value is outside the allowed range (I: -23 to 0, TP: -5 to 0, LRA: 1 to 20), an error message shall be displayed and the Apply button shall remain disabled.
**Validates: Requirements 3.3, 3.4, 8.2, 8.3**
### Property 6: Cancel Discards Changes
*For any* changes made in the NormalizationModal, clicking Cancel shall close the modal without saving any changes, and the previous settings shall remain active.
**Validates: Requirements 1.9**
### Property 7: Apply Saves Settings
*For any* valid parameter configuration in the NormalizationModal, clicking Apply shall save the settings to persistent storage and close the modal.
**Validates: Requirements 1.10, 4.1**
### Property 8: Settings Persist Across Sessions
*For any* normalization settings saved via Apply, when the application is restarted, the NormalizationModal shall load and display the previously saved settings.
**Validates: Requirements 4.3**
### Property 9: Downloader Uses Configured Settings
*For any* download initiated after normalization settings are configured, the Downloader shall use the current settings from Config to construct the FFmpeg loudnorm filter string.
**Validates: Requirements 5.1, 5.2, 5.3**
### Property 10: Settings Button Always Enabled
*For any* application state (downloads running or idle), the "Normalization Settings" button in the Download tab shall be enabled and clickable.
**Validates: Requirements 6.4**
### Property 11: Modal Opens on Button Click
*When* the user clicks the "Normalization Settings" button in the Download tab, the NormalizationModal shall open and display the current settings.
**Validates: Requirements 6.3**
### Property 12: Preset Indicator Shows Current Preset
*For any* active normalization preset, the Download tab shall display an indicator showing the current preset name.
**Validates: Requirements 7.4**
### Property 13: Default Settings on First Run
*When* the application starts for the first time with no saved normalization settings, the NormalizationModal shall display the "Normal" preset as selected with parameters I=-14, TP=-1.5, LRA=11.
**Validates: Requirements 4.5, 12.1, 12.2**
### Property 14: All Presets Valid
*For all* preset definitions (Quiet, Normal, Loud, Very Loud), all parameters shall conform to FFmpeg loudnorm specifications: I between -23 and 0, TP between -5 and 0, LRA between 1 and 20.
**Validates: Requirements 10.1, 10.2, 10.3, 10.4**
### Property 15: Configuration Structure Correct
*For any* saved normalization settings, the stored configuration shall include the keys: `current_preset` (string) and `custom_parameters` (dict with I, TP, LRA keys).
**Validates: Requirements 11.2**
### Property 16: Invalid Parameters Prevent Apply
*For any* invalid parameter value (non-numeric or out of range), the Apply button shall remain disabled until all parameters are corrected to valid values.
**Validates: Requirements 8.1, 8.3, 8.4**
### Property 17: Downloader Validates Before Processing
*When* the Downloader receives normalization parameters, the parameters shall be validated against the allowed ranges before constructing the FFmpeg command.
**Validates: Requirements 10.5**
### Property 18: Failed Normalization Doesn't Stop Download
*If* normalization fails during audio processing, the download process shall continue and the file shall be saved without normalization applied.
**Validates: Requirements 5.5**
### Property 19: Modal Changes Don't Affect Running Downloads
*When* the NormalizationModal is open and the user changes settings, any currently running downloads shall continue using the previously configured settings until the next download is initiated.
**Validates: Requirements 6.6**
### Property 20: Corrupted Config Uses Defaults
*If* the configuration file is corrupted or missing, the application shall use the default "Normal" preset settings and log a warning.
**Validates: Requirements 11.5, 12.4**
---
## Error Handling
### Input Validation Errors
| Error Condition | Message | Action |
|---|---|---|
| Non-numeric input | "Parameter must be a number" | Show error, disable Apply |
| Value < minimum | "Parameter {name} must be between {min} and {max}" | Show error, disable Apply |
| Value > maximum | "Parameter {name} must be between {min} and {max}" | Show error, disable Apply |
| Empty field | "Parameter {name} is required" | Show error, disable Apply |
### Configuration Errors
| Error Condition | Message | Action |
|---|---|---|
| Config file missing | Log warning, use defaults | Application continues |
| Config file corrupted | Log warning, use defaults | Application continues |
| Save failure | "Failed to save settings. Please try again." | Show error in modal |
| Load failure | Log warning, use defaults | Application continues |
### FFmpeg Errors
| Error Condition | Action |
|---|---|
| Invalid parameters | Log error, skip normalization |
| FFmpeg timeout | Log error, skip normalization |
| FFmpeg not found | Log error, skip normalization |
| File processing error | Log error, skip normalization |
### Recovery Strategies
1. **Validation Failure**: User corrects input, error clears, Apply becomes enabled
2. **Save Failure**: User clicks Apply again, or closes and reopens modal
3. **Load Failure**: Application uses defaults, user can reconfigure
4. **FFmpeg Failure**: Download continues without normalization, user can retry
---
## Testing Strategy
### Unit Testing
**Test Categories**:
1. **Modal UI Tests**:
- Verify modal opens/closes correctly
- Verify preset selection updates fields
- Verify custom fields enable/disable appropriately
- Verify buttons are positioned and styled correctly
2. **Validation Tests**:
- Test numeric validation (reject non-numeric input)
- Test range validation (reject out-of-range values)
- Test error message display
- Test Apply button enable/disable based on validation
3. **Settings Persistence Tests**:
- Test saving settings to Config
- Test loading settings from Config
- Test default settings when no config exists
- Test corrupted config handling
4. **Integration Tests**:
- Test modal signal emission
- Test DownloadTab receives settings changes
- Test Downloader uses configured settings
- Test FFmpeg command construction with parameters
5. **Edge Cases**:
- Test with boundary values (min/max for each parameter)
- Test with missing config file
- Test with corrupted config file
- Test rapid preset switching
- Test modal open/close cycles
### Property-Based Testing
**Property Test Configuration**:
- Minimum 100 iterations per property test
- Use hypothesis or similar PBT library for Python
- Tag each test with: `Feature: volume-normalization-modal, Property {number}: {property_text}`
**Property Tests**:
1. **Property 1: Preset Selection Updates Parameters**
- Generate random preset names
- Verify parameter fields match preset values
- Tag: `Feature: volume-normalization-modal, Property 1: Preset Selection Updates Parameters`
2. **Property 5: Parameter Validation Enforces Ranges**
- Generate random parameter values (in and out of range)
- Verify validation result matches expected range
- Tag: `Feature: volume-normalization-modal, Property 5: Parameter Validation Enforces Ranges`
3. **Property 7: Apply Saves Settings**
- Generate random valid parameter configurations
- Apply settings, verify they're saved to Config
- Tag: `Feature: volume-normalization-modal, Property 7: Apply Saves Settings`
4. **Property 8: Settings Persist Across Sessions**
- Generate random settings, save them
- Simulate application restart
- Verify settings are loaded correctly
- Tag: `Feature: volume-normalization-modal, Property 8: Settings Persist Across Sessions`
5. **Property 9: Downloader Uses Configured Settings**
- Generate random valid parameters
- Configure settings, initiate download
- Verify FFmpeg command uses correct parameters
- Tag: `Feature: volume-normalization-modal, Property 9: Downloader Uses Configured Settings`
6. **Property 14: All Presets Valid**
- For each preset, verify all parameters are in valid ranges
- Tag: `Feature: volume-normalization-modal, Property 14: All Presets Valid`
7. **Property 15: Configuration Structure Correct**
- Generate random settings, save them
- Verify saved structure contains required keys
- Tag: `Feature: volume-normalization-modal, Property 15: Configuration Structure Correct`
### Test Coverage Goals
- **Unit Tests**: 90%+ coverage of modal and config classes
- **Property Tests**: All 20 correctness properties covered
- **Integration Tests**: All component interactions tested
- **Edge Cases**: All boundary conditions and error scenarios tested
---
## Implementation Phases
### Phase 1: Core Infrastructure
- Create NormalizationSettings class in Config module
- Define preset configurations and validation ranges
- Implement settings load/save functionality
### Phase 2: Modal Component
- Create NormalizationModal class
- Implement preset selection UI
- Implement custom parameter input fields
- Implement validation and error display
### Phase 3: Integration
- Integrate modal with DownloadTab
- Add "Normalization Settings" button
- Add preset indicator
- Connect signals and slots
### Phase 4: Downloader Updates
- Modify normalize_volume to use Config settings
- Add parameter validation
- Update FFmpeg command construction
### Phase 5: Testing
- Write unit tests for all components
- Write property-based tests for correctness properties
- Write integration tests
- Test edge cases and error scenarios
---
## Deployment Considerations
### Configuration Migration
For users upgrading from previous versions:
1. Check if normalization settings exist
2. If not, create with default "Normal" preset
3. Log migration event
4. No user action required
### Backward Compatibility
- Existing `normalize_volume` behavior preserved
- Default settings match current hardcoded values
- No breaking changes to public APIs
- Graceful degradation if settings unavailable
### Performance Impact
- Minimal: Settings loaded once at startup
- Settings lookup is O(1) dictionary access
- No additional FFmpeg overhead
- No UI performance impact
---
## Future Enhancements
1. **Preset Management**: Allow users to create and save custom presets
2. **Per-Track Settings**: Apply different settings to different tracks
3. **Batch Operations**: Apply settings to multiple files
4. **Settings Export/Import**: Share settings between users
5. **Advanced Visualization**: Show loudness graphs and analysis
6. **A/B Comparison**: Compare before/after audio samples
@@ -0,0 +1,186 @@
# Volume Normalization Modal - Requirements Document
## Introduction
The dl_yt application currently applies volume normalization to downloaded audio files using FFmpeg's loudnorm filter with fixed parameters (I=-14:TP=-1.5:LRA=11). Users have reported that the default normalization level is too low, resulting in audio that requires manual volume adjustment. This feature introduces a modal dialog in the Download tab that allows users to configure volume normalization settings before initiating downloads. The modal provides preset options and custom parameter configuration, with settings persisted globally for future sessions.
## Glossary
- **Volume_Normalization**: The process of adjusting audio loudness to a consistent level using FFmpeg's loudnorm filter
- **Loudnorm_Filter**: FFmpeg audio filter that normalizes loudness according to ITU-R BS.1770 standard
- **Integrated_Loudness**: The overall loudness of an audio file measured in LUFS (Loudness Units relative to Full Scale), controlled by the 'I' parameter
- **True_Peak**: The maximum instantaneous peak level in the audio, controlled by the 'TP' parameter
- **Loudness_Range**: The range between the quietest and loudest parts of the audio, controlled by the 'LRA' parameter
- **Download_Tab**: The PyQt6 UI component that manages playlist downloads and audio processing
- **Normalization_Modal**: A dialog window that allows users to configure volume normalization settings
- **Global_Settings**: Configuration values persisted to disk that apply to all future downloads until changed
- **Per_Download_Settings**: Configuration values that apply only to the current download session
- **Preset**: A predefined set of normalization parameters with a descriptive name
- **Custom_Parameters**: User-defined normalization values that override preset settings
- **Config_Module**: The centralized configuration management system (src/core/config.py)
- **Downloader_Module**: The core module that handles audio processing including volume normalization (src/core/downloader.py)
## Requirements
### Requirement 1: Modal Dialog Appearance and Accessibility
**User Story:** As a user, I want a clear and intuitive modal dialog to configure volume normalization settings, so that I can easily adjust audio loudness before downloading.
#### Acceptance Criteria
1. WHEN the user clicks a "Normalization Settings" button in the Download tab, THE Normalization_Modal SHALL appear as a centered dialog window
2. THE Normalization_Modal SHALL display a title "Volume Normalization Settings"
3. THE Normalization_Modal SHALL have a dark theme consistent with the existing DeleteModal styling (background-color: #212121, text color: #ffffff)
4. THE Normalization_Modal SHALL be modal (blocking interaction with the main window until closed)
5. THE Normalization_Modal SHALL have a fixed size of 500x400 pixels
6. THE Normalization_Modal SHALL be centered relative to the parent Download tab window
7. THE Normalization_Modal SHALL include a close button (X) in the top-right corner that cancels changes
8. THE Normalization_Modal SHALL include "Cancel" and "Apply" buttons at the bottom
9. WHEN the user clicks "Cancel", THE Normalization_Modal SHALL close without applying changes
10. WHEN the user clicks "Apply", THE Normalization_Modal SHALL save settings and close
### Requirement 2: Preset Selection Interface
**User Story:** As a user, I want to select from predefined normalization presets, so that I can quickly choose appropriate settings without understanding technical parameters.
#### Acceptance Criteria
1. THE Normalization_Modal SHALL display a "Presets" section with radio buttons for preset selection
2. THE Normalization_Modal SHALL provide the following presets:
- "Quiet" (I=-18, TP=-1.5, LRA=11) - for content that should remain quiet
- "Normal" (I=-14, TP=-1.5, LRA=11) - the current default, suitable for most music
- "Loud" (I=-10, TP=-1.5, LRA=11) - for content that should be louder
- "Very Loud" (I=-6, TP=-1.5, LRA=11) - for maximum loudness
- "Custom" - allows user-defined parameters
3. WHEN a preset is selected, THE Normalization_Modal SHALL update the parameter input fields to display the preset values
4. WHEN a preset is selected, THE Normalization_Modal SHALL disable the parameter input fields (make them read-only)
5. WHEN the "Custom" preset is selected, THE Normalization_Modal SHALL enable the parameter input fields for editing
6. THE Normalization_Modal SHALL display the currently active preset as pre-selected when the modal opens
### Requirement 3: Custom Parameter Configuration
**User Story:** As an advanced user, I want to configure custom loudnorm parameters, so that I can fine-tune normalization to my specific needs.
#### Acceptance Criteria
1. THE Normalization_Modal SHALL display three input fields for custom parameters:
- Integrated_Loudness (I): numeric input with range -23 to 0 LUFS
- True_Peak (TP): numeric input with range -5 to 0 dBFS
- Loudness_Range (LRA): numeric input with range 1 to 20 LU
2. WHEN the user selects the "Custom" preset, THE parameter input fields SHALL become editable
3. WHEN the user modifies a parameter value, THE Normalization_Modal SHALL validate the input is within the allowed range
4. IF a parameter value is outside the allowed range, THE Normalization_Modal SHALL display an error message and prevent applying settings
5. THE Normalization_Modal SHALL display the current custom parameter values when opened
6. WHEN a preset is selected (other than "Custom"), THE parameter input fields SHALL update to show preset values but remain read-only
### Requirement 4: Global Settings Persistence
**User Story:** As a user, I want my normalization settings to persist across application sessions, so that I don't need to reconfigure them each time I use the application.
#### Acceptance Criteria
1. WHEN the user clicks "Apply" in the Normalization_Modal, THE selected preset and custom parameters SHALL be saved to persistent storage
2. THE settings SHALL be stored in the Config_Module using a configuration file or dictionary
3. WHEN the application starts, THE Normalization_Modal SHALL load the previously saved settings
4. WHEN the application starts, THE Download tab SHALL use the saved normalization settings for all downloads
5. IF no settings have been previously saved, THE Normalization_Modal SHALL default to the "Normal" preset (I=-14, TP=-1.5, LRA=11)
6. THE settings storage location SHALL be documented in code comments
### Requirement 5: Settings Application to Downloads
**User Story:** As a user, I want the configured normalization settings to be applied to all downloaded audio files, so that my audio has consistent loudness.
#### Acceptance Criteria
1. WHEN a download is initiated, THE Downloader_Module SHALL use the current normalization settings from the Config_Module
2. WHEN the Downloader_Module normalizes audio, THE normalize_volume function SHALL apply the configured Integrated_Loudness, True_Peak, and Loudness_Range parameters
3. THE normalize_volume function SHALL construct the FFmpeg loudnorm filter string using the format: `loudnorm=I={I}:TP={TP}:LRA={LRA}`
4. WHEN normalization is applied, THE audio file SHALL be processed with the configured parameters before being saved
5. IF normalization fails, THE download process SHALL continue and log the failure (existing behavior maintained)
### Requirement 6: Modal Trigger and Visibility
**User Story:** As a user, I want easy access to normalization settings from the Download tab, so that I can adjust them before starting downloads.
#### Acceptance Criteria
1. THE Download tab SHALL display a "Normalization Settings" button in the control panel area
2. THE button SHALL be positioned near other download controls (Start, Stop, Browse buttons)
3. WHEN the user clicks the "Normalization Settings" button, THE Normalization_Modal SHALL open
4. THE button SHALL be enabled at all times (both when downloads are running and when idle)
5. WHEN the Normalization_Modal is open, THE user SHALL be able to view the current settings
6. WHEN the Normalization_Modal is open, THE user SHALL be able to change settings without affecting the current download
### Requirement 7: Settings Display and Feedback
**User Story:** As a user, I want to see the current normalization settings applied, so that I understand what loudness level is being used.
#### Acceptance Criteria
1. THE Normalization_Modal SHALL display a summary of the currently active preset name
2. THE Normalization_Modal SHALL display the current parameter values (I, TP, LRA) in a readable format
3. WHEN settings are successfully applied, THE Normalization_Modal SHALL display a confirmation message
4. THE Download tab SHALL display an indicator showing which preset is currently active (e.g., "Preset: Normal")
5. WHEN the user hovers over the preset indicator, THE application SHALL display a tooltip showing the current parameter values
### Requirement 8: Error Handling and Validation
**User Story:** As a user, I want clear error messages when I enter invalid settings, so that I can correct them and apply valid configuration.
#### Acceptance Criteria
1. IF the user enters a non-numeric value in a parameter field, THE Normalization_Modal SHALL display an error message: "Parameter must be a number"
2. IF the user enters a value outside the allowed range, THE Normalization_Modal SHALL display an error message: "Parameter {name} must be between {min} and {max}"
3. IF validation fails, THE "Apply" button SHALL remain disabled until valid values are entered
4. WHEN the user corrects an invalid value, THE error message SHALL disappear and the "Apply" button SHALL become enabled
5. IF the Config_Module fails to save settings, THE Normalization_Modal SHALL display an error message: "Failed to save settings. Please try again."
### Requirement 9: UI Consistency and Styling
**User Story:** As a user, I want the normalization modal to match the application's visual design, so that it feels like an integrated part of the application.
#### Acceptance Criteria
1. THE Normalization_Modal styling SHALL match the DeleteModal styling (dark theme with #212121 background)
2. THE buttons SHALL use the same styling as other application buttons (blue for primary actions, gray for secondary)
3. THE input fields SHALL use consistent styling with other QLineEdit and QSpinBox components in the application
4. THE font sizes and weights SHALL be consistent with other modals (11pt for labels, 12pt for buttons)
5. THE modal SHALL use the same color scheme: #ffffff for primary text, #cccccc for secondary text, #888888 for tertiary text
### Requirement 10: Preset Parameter Validation
**User Story:** As a developer, I want to ensure all preset parameters are valid according to FFmpeg loudnorm specifications, so that the application doesn't crash or produce invalid audio processing commands.
#### Acceptance Criteria
1. THE Normalization_Modal SHALL validate that all preset parameters conform to FFmpeg loudnorm filter specifications
2. FOR ALL presets, THE Integrated_Loudness (I) parameter SHALL be between -23 and 0 LUFS
3. FOR ALL presets, THE True_Peak (TP) parameter SHALL be between -5 and 0 dBFS
4. FOR ALL presets, THE Loudness_Range (LRA) parameter SHALL be between 1 and 20 LU
5. WHEN the Downloader_Module receives normalization parameters, THE parameters SHALL be validated before constructing the FFmpeg command
6. IF parameters are invalid, THE normalize_volume function SHALL log an error and skip normalization for that file
### Requirement 11: Configuration Storage Format
**User Story:** As a developer, I want a clear and maintainable configuration storage format, so that settings can be easily read, modified, and debugged.
#### Acceptance Criteria
1. THE Config_Module SHALL store normalization settings in a structured format (JSON or Python dictionary)
2. THE stored configuration SHALL include: current_preset (string), custom_parameters (dict with I, TP, LRA keys)
3. THE configuration storage location SHALL be documented in the Config_Module with comments
4. THE configuration SHALL be human-readable and editable if stored in a file
5. IF the configuration file is corrupted or missing, THE application SHALL use default settings and log a warning
### Requirement 12: Backward Compatibility
**User Story:** As a user, I want the application to work correctly even if I'm upgrading from a version without normalization settings, so that I don't experience errors or data loss.
#### Acceptance Criteria
1. IF no saved normalization settings exist, THE application SHALL default to the "Normal" preset (I=-14, TP=-1.5, LRA=11)
2. WHEN the application starts for the first time, THE Normalization_Modal SHALL display the "Normal" preset as selected
3. THE existing normalize_volume function behavior SHALL remain unchanged if no settings are configured
4. IF the Config_Module cannot load settings, THE application SHALL log a warning and use default settings
5. WHEN the user applies settings for the first time, THE application SHALL create the necessary configuration storage
@@ -0,0 +1,392 @@
# Implementation Plan: Volume Normalization Modal
## Overview
This implementation plan breaks down the Volume Normalization Modal feature into discrete, actionable coding tasks organized by phase. Each task builds incrementally on previous work, with property-based tests validating correctness properties throughout. The implementation follows the existing codebase patterns (PyQt6 modals, Config module structure) and maintains backward compatibility with the current normalization behavior.
---
## Tasks
### Phase 1: Core Infrastructure - NormalizationSettings Class
- [x] 1.1 Create NormalizationSettings class in Config module
- Add `NormalizationSettings` class to `src/core/config.py`
- Define `PRESETS` dictionary with four presets: Quiet, Normal, Loud, Very Loud
- Define `PARAM_RANGES` dictionary with validation ranges for I, TP, LRA parameters
- Implement `get_preset_params(preset_name: str) -> dict` method
- Implement `validate_parameters(params: dict) -> tuple[bool, str]` method
- _Requirements: 2.2, 10.1, 10.2, 10.3, 10.4_
- [ ]* 1.2 Write property test for preset definitions
- **Property 14: All Presets Valid**
- **Validates: Requirements 10.1, 10.2, 10.3, 10.4**
- Test that all preset parameters conform to FFmpeg loudnorm specifications
- Verify I is between -23 and 0, TP is between -5 and 0, LRA is between 1 and 20
- [x] 1.3 Implement settings persistence in Config module
- Add `load_settings() -> dict` class method to load from storage
- Add `save_settings(preset: str, custom_params: dict = None) -> bool` class method
- Add `get_default_settings() -> dict` class method returning Normal preset
- Implement JSON-based storage in application config directory
- Handle missing/corrupted config files gracefully with defaults
- _Requirements: 4.1, 4.2, 4.3, 11.1, 11.2, 11.3, 11.4, 11.5, 12.1, 12.4_
- [ ]* 1.4 Write property test for settings persistence
- **Property 8: Settings Persist Across Sessions**
- **Validates: Requirements 4.3, 11.2**
- Generate random valid settings, save them, simulate restart, verify loading
- Test that saved structure contains required keys: current_preset, custom_parameters
- [ ]* 1.5 Write property test for configuration structure
- **Property 15: Configuration Structure Correct**
- **Validates: Requirements 11.2**
- Verify saved configuration includes current_preset (string) and custom_parameters (dict)
- Test with various preset and parameter combinations
- [ ] 1.6 Checkpoint - Ensure all tests pass
- Ensure all Phase 1 tests pass, ask the user if questions arise.
---
### Phase 2: Modal Component - NormalizationModal Class
- [x] 2.1 Create NormalizationModal class structure
- Create `src/ui/components/normalization_modal_qt.py`
- Implement `NormalizationModal(QDialog)` class
- Define `settings_applied` pyqtSignal(dict) for emitting settings changes
- Implement `__init__(parent, config)` constructor
- Set modal properties: fixed size 500x400, dark theme (#212121), centered
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 9.1, 9.2, 9.3, 9.4, 9.5_
- [x] 2.2 Implement preset selection UI
- Create "Presets" section with QGroupBox
- Add radio buttons for: Quiet, Normal, Loud, Very Loud, Custom
- Implement `_on_preset_selected(preset_name: str)` slot
- Connect radio button signals to preset selection handler
- Display preset descriptions in UI
- _Requirements: 2.1, 2.2, 2.3, 2.6_
- [ ]* 2.3 Write property test for preset selection
- **Property 1: Preset Selection Updates Parameters**
- **Validates: Requirements 2.3**
- Generate random preset selections, verify parameter fields update correctly
- Test that selected preset values match expected preset parameters
- [x] 2.4 Implement custom parameter input fields
- Create three input fields: I (Integrated Loudness), TP (True Peak), LRA (Loudness Range)
- Use QDoubleSpinBox or QLineEdit with validators
- Display parameter labels with units (LUFS, dBFS, LU)
- Implement `_on_parameter_changed(param_name: str, value: str)` slot
- Connect input field signals to parameter change handler
- _Requirements: 3.1, 3.2, 3.5_
- [x] 2.5 Implement parameter validation and error display
- Implement `_validate_parameters() -> tuple[bool, str]` method
- Check for non-numeric input, display "Parameter must be a number" error
- Check for out-of-range values, display range error message
- Create error display area (red-tinted label) above buttons
- Update Apply button enabled state based on validation
- _Requirements: 3.3, 3.4, 8.1, 8.2, 8.3, 8.4_
- [ ]* 2.6 Write property test for parameter validation
- **Property 5: Parameter Validation Enforces Ranges**
- **Validates: Requirements 3.3, 3.4, 8.2, 8.3**
- Generate random parameter values (in and out of range)
- Verify validation result matches expected range constraints
- Test boundary values: -23, 0 for I; -5, 0 for TP; 1, 20 for LRA
- [x] 2.7 Implement preset/custom field interaction
- Implement `_enable_custom_editing(enabled: bool)` method
- When non-Custom preset selected: disable input fields, show preset values
- When Custom preset selected: enable input fields for editing
- _Requirements: 2.4, 2.5, 3.2_
- [ ]* 2.8 Write property test for preset/custom interaction
- **Property 2: Non-Custom Presets Disable Editing**
- **Property 3: Custom Preset Enables Editing**
- **Validates: Requirements 2.4, 2.5**
- Test that non-Custom presets disable input fields
- Test that Custom preset enables input fields
- [x] 2.9 Implement settings loading on modal open
- Implement `_load_current_settings()` method
- Load settings from Config module on modal initialization
- Display current preset as pre-selected
- Populate parameter fields with current values
- _Requirements: 2.6, 4.4_
- [ ]* 2.10 Write property test for modal initialization
- **Property 4: Modal Loads Current Settings**
- **Validates: Requirements 2.6**
- Save settings to Config, open modal, verify current preset is pre-selected
- Test with various preset and parameter combinations
- [x] 2.11 Implement apply and cancel button logic
- Implement `_on_apply()` method: validate, save to Config, emit signal, close
- Implement `_on_cancel()` method: close without saving
- Implement `_on_close()` method for X button: same as cancel
- Add Apply button (blue #2196F3) and Cancel button (gray #757575)
- _Requirements: 1.9, 1.10, 4.1_
- [ ]* 2.12 Write property test for apply/cancel behavior
- **Property 6: Cancel Discards Changes**
- **Property 7: Apply Saves Settings**
- **Validates: Requirements 1.9, 1.10, 4.1**
- Test that Cancel closes without saving changes
- Test that Apply saves valid settings and emits signal
- [x] 2.13 Implement settings display summary
- Add label showing current preset name
- Add label showing current parameter values in readable format
- Update display when settings are loaded or changed
- _Requirements: 7.1, 7.2_
- [ ] 2.14 Checkpoint - Ensure all modal tests pass
- Ensure all Phase 2 tests pass, ask the user if questions arise.
---
### Phase 3: Integration with DownloadTab
- [x] 3.1 Add Normalization Settings button to DownloadTab
- Modify `src/ui/tabs/download_tab_qt.py`
- Add "Normalization Settings" button to control panel
- Position button near other download controls (Start, Stop, Browse)
- Button should be enabled at all times
- _Requirements: 6.1, 6.2, 6.4_
- [x] 3.2 Implement modal opening from DownloadTab
- Implement `_open_normalization_modal()` method in DownloadTab
- Create NormalizationModal instance with Config reference
- Connect button click to modal opening
- _Requirements: 6.3_
- [ ]* 3.3 Write property test for modal opening
- **Property 11: Modal Opens on Button Click**
- **Validates: Requirements 6.3**
- Click button, verify modal appears and displays current settings
- [x] 3.4 Implement preset indicator in DownloadTab
- Add preset indicator label showing current active preset
- Display format: "Preset: {preset_name} (I={I}, TP={TP}, LRA={LRA})"
- Update indicator when settings are applied
- _Requirements: 7.4_
- [ ] 3.5 Implement preset indicator tooltip
- Add tooltip to preset indicator showing full parameter details
- Tooltip displays when user hovers over indicator
- _Requirements: 7.5_
- [ ]* 3.6 Write property test for preset indicator
- **Property 12: Preset Indicator Shows Current Preset**
- **Validates: Requirements 7.4**
- Change settings, verify indicator updates to show new preset
- [x] 3.7 Connect modal signals to DownloadTab
- Implement `_on_normalization_settings_applied(settings: dict)` slot
- Connect modal's `settings_applied` signal to this slot
- Update preset indicator when settings change
- _Requirements: 6.5, 6.6_
- [x] 3.8 Implement settings button always enabled
- Ensure button remains enabled during downloads
- Ensure button remains enabled when idle
- _Requirements: 6.4_
- [ ]* 3.9 Write property test for button availability
- **Property 10: Settings Button Always Enabled**
- **Validates: Requirements 6.4**
- Verify button is enabled in all application states
- [ ] 3.10 Checkpoint - Ensure all integration tests pass
- Ensure all Phase 3 tests pass, ask the user if questions arise.
---
### Phase 4: Downloader Module Updates
- [x] 4.1 Modify normalize_volume to use Config settings
- Update `src/core/downloader.py` normalize_volume method
- Load current settings from NormalizationSettings.load_settings()
- Extract custom_parameters from loaded settings
- _Requirements: 5.1, 5.2_
- [x] 4.2 Add parameter validation before FFmpeg command
- Call NormalizationSettings.validate_parameters() before processing
- Log error and skip normalization if validation fails
- _Requirements: 10.5_
- [x] 4.3 Update FFmpeg command construction
- Construct loudnorm filter string using format: `loudnorm=I={I}:TP={TP}:LRA={LRA}`
- Use parameters from Config instead of hardcoded values
- _Requirements: 5.3_
- [x] 4.4 Add logging for parameter usage
- Log which preset/parameters are being applied
- Log validation failures with error details
- Log FFmpeg command construction
- _Requirements: 5.1_
- [ ]* 4.5 Write property test for downloader settings usage
- **Property 9: Downloader Uses Configured Settings**
- **Validates: Requirements 5.1, 5.2, 5.3**
- Configure settings, initiate download, verify FFmpeg command uses correct parameters
- Test with various preset and custom parameter combinations
- [x] 4.6 Ensure backward compatibility
- Verify default settings match current hardcoded behavior (Normal preset)
- Test that downloads work without any settings configured
- _Requirements: 12.1, 12.3_
- [x] 4.7 Implement graceful failure handling
- If normalization fails, download continues without normalization
- Log failure details for debugging
- _Requirements: 5.5_
- [ ]* 4.8 Write property test for default settings
- **Property 13: Default Settings on First Run**
- **Validates: Requirements 4.5, 12.1, 12.2**
- Verify default settings are Normal preset with I=-14, TP=-1.5, LRA=11
- [ ] 4.9 Checkpoint - Ensure all downloader tests pass
- Ensure all Phase 4 tests pass, ask the user if questions arise.
---
### Phase 5: Comprehensive Testing and Validation
- [ ] 5.1 Write unit tests for NormalizationSettings class
- Test preset parameter retrieval for all presets
- Test parameter validation with valid and invalid values
- Test boundary values for all parameters
- Test settings loading and saving
- Test default settings initialization
- _Requirements: 10.1, 10.2, 10.3, 10.4, 11.1, 11.2, 12.1_
- [ ] 5.2 Write unit tests for NormalizationModal class
- Test modal initialization and UI layout
- Test preset selection and field updates
- Test custom parameter input and validation
- Test apply/cancel button behavior
- Test error message display
- Test settings loading on open
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 1.10, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 3.1, 3.2, 3.3, 3.4, 3.5, 8.1, 8.2, 8.3, 8.4_
- [ ] 5.3 Write unit tests for DownloadTab integration
- Test button creation and positioning
- Test modal opening from button click
- Test preset indicator display and updates
- Test tooltip display
- Test settings application signal handling
- _Requirements: 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 7.4, 7.5_
- [ ] 5.4 Write unit tests for Downloader integration
- Test normalize_volume uses Config settings
- Test parameter validation before FFmpeg command
- Test FFmpeg command construction with parameters
- Test logging of parameter usage
- Test graceful failure handling
- _Requirements: 5.1, 5.2, 5.3, 5.5, 10.5_
- [ ] 5.5 Write edge case tests
- Test with boundary parameter values (min/max for each parameter)
- Test with missing config file (should use defaults)
- Test with corrupted config file (should use defaults and log warning)
- Test rapid preset switching
- Test modal open/close cycles
- Test settings changes during active downloads
- _Requirements: 6.6, 11.5, 12.4_
- [ ] 5.6 Write error scenario tests
- Test non-numeric input in parameter fields
- Test out-of-range parameter values
- Test config save failures
- Test config load failures
- Test FFmpeg command construction with invalid parameters
- _Requirements: 8.1, 8.2, 8.3, 8.4, 8.5, 11.5_
- [ ] 5.7 Write integration tests for end-to-end flow
- Test complete flow: open modal → select preset → apply → verify settings used in download
- Test complete flow: open modal → select custom → enter parameters → apply → verify settings used
- Test complete flow: change settings → verify indicator updates → verify download uses new settings
- _Requirements: 1.1, 1.10, 4.1, 5.1, 5.2, 5.3, 6.3, 6.5, 7.4_
- [ ] 5.8 Verify all property-based tests pass
- Run all 20 property-based tests defined in design document
- Ensure each property test validates its corresponding requirement
- Verify test coverage for all correctness properties
- _Requirements: All_
- [ ] 5.9 Final checkpoint - Ensure all tests pass
- Ensure all unit tests pass
- Ensure all property-based tests pass
- Ensure all integration tests pass
- Ensure all edge case tests pass
- Ensure all error scenario tests pass
- Ask the user if questions arise.
---
## Implementation Notes
### Code Organization
- **NormalizationSettings**: Add to `src/core/config.py` as a new class
- **NormalizationModal**: Create new file `src/ui/components/normalization_modal_qt.py`
- **DownloadTab modifications**: Update `src/ui/tabs/download_tab_qt.py`
- **Downloader modifications**: Update `src/core/downloader.py`
### Testing Framework
- Use Python's `unittest` or `pytest` for unit tests
- Use `hypothesis` library for property-based tests
- Tag each property test with: `Feature: volume-normalization-modal, Property {number}: {property_text}`
- Minimum 100 iterations per property test
### Styling Consistency
- Match DeleteModal styling: #212121 background, #ffffff text
- Apply button: #2196F3 (blue)
- Cancel button: #757575 (gray)
- Input fields: dark theme with white text
- Font: 11pt for labels, 12pt for buttons
### Backward Compatibility
- Default to "Normal" preset (I=-14, TP=-1.5, LRA=11) if no settings exist
- Existing normalize_volume behavior preserved
- No breaking changes to public APIs
- Graceful degradation if settings unavailable
### Configuration Storage
- Store in application config directory (platform-specific)
- Windows: `%APPDATA%/dl_yt/config.json`
- macOS: `~/Library/Application Support/dl_yt/config.json`
- Linux: `~/.config/dl_yt/config.json`
- JSON format with keys: `current_preset`, `custom_parameters`
### Error Handling
- Log all errors with context
- Display user-friendly error messages in modal
- Continue downloads even if normalization fails
- Use defaults if config loading fails
---
## Acceptance Criteria Summary
All tasks must satisfy the following acceptance criteria:
1. Code follows existing codebase patterns and conventions
2. All unit tests pass with 90%+ coverage
3. All property-based tests pass (minimum 100 iterations each)
4. All integration tests pass
5. No breaking changes to existing functionality
6. Settings persist correctly across application sessions
7. Modal UI matches design specifications
8. Error handling is robust and user-friendly
9. Logging is comprehensive for debugging
10. Documentation is clear and maintainable