# 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