markdown_exec 3.4.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +8 -1
- data/README.md +262 -116
- data/bats/block-type-shell-require-ux.bats +15 -0
- data/bats/block-type-ux-require-context.bats +14 -0
- data/bats/import-directive-line-continuation.bats +1 -1
- data/bats/import-directive-parameter-symbols.bats +1 -1
- data/bats/import-parameter-symbols.bats +1 -1
- data/bats/options.bats +2 -2
- data/demo/trap.demo1.gif +0 -0
- data/demo/trap.demo1.mp4 +0 -0
- data/docs/dev/block-type-shell-require-ux.md +18 -0
- data/docs/dev/block-type-ux-format.md +10 -0
- data/docs/dev/block-type-ux-require-context.md +32 -0
- data/docs/dev/block-type-ux-require.md +8 -4
- data/docs/dev/import-directive-line-continuation.md +0 -1
- data/docs/dev/import-directive-parameter-symbols.md +0 -2
- data/docs/dev/import-parameter-symbols-template.md +7 -5
- data/docs/dev/import-parameter-symbols.md +10 -2
- data/examples/colors.md +31 -29
- data/lib/cached_nested_file_reader.rb +15 -47
- data/lib/command_result.rb +5 -5
- data/lib/constants.rb +3 -1
- data/lib/fcb.rb +7 -1
- data/lib/hash_delegator.rb +76 -32
- data/lib/link_history.rb +1 -1
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/menu.src.yml +18 -8
- data/lib/menu.yml +14 -5
- data/lib/parameter_expansion.rb +918 -0
- data/lib/parse_animation_to_tts.rb +4417 -0
- data/lib/resize_terminal.rb +19 -16
- metadata +11 -2
|
@@ -0,0 +1,4417 @@
|
|
|
1
|
+
#!/usr/bin/env -S bundle exec ruby
|
|
2
|
+
# parse_animation_to_tts.rb
|
|
3
|
+
|
|
4
|
+
require 'stringio'
|
|
5
|
+
#
|
|
6
|
+
# REQUIREMENTS UPDATE:
|
|
7
|
+
# - Parse animation specification files containing BOX and TEXT timing data
|
|
8
|
+
# - Generate YAML output with text-to-speech timing information
|
|
9
|
+
# - Support multiple input files with sequential processing
|
|
10
|
+
# - Extract text content, timing, and visual properties for TTS generation
|
|
11
|
+
# - Handle overlapping text segments and calculate silence gaps
|
|
12
|
+
# - Maintain backward compatibility with existing animation spec format
|
|
13
|
+
# - Process BOX timing context for TEXT inheritance
|
|
14
|
+
# - Support all TEXT format variations: explicit timing, inherited timing, color inheritance
|
|
15
|
+
# - Calculate speech speed based on text length and available duration
|
|
16
|
+
# - Map text colors to voice pitch variations
|
|
17
|
+
# - Generate structured YAML with audio segments, gaps, and metadata
|
|
18
|
+
# - Handle missing files gracefully with proper error messages
|
|
19
|
+
# - Support command-line testing with --test flag
|
|
20
|
+
# - Implement comprehensive minitest suite with 25+ test cases
|
|
21
|
+
# - Support file validation and error handling
|
|
22
|
+
# - Generate proper YAML structure with metadata and audio segments
|
|
23
|
+
# - Calculate timing gaps between segments accurately
|
|
24
|
+
# - Support color inheritance from previous TEXT elements
|
|
25
|
+
# - Handle timing inheritance from BOX elements to TEXT elements
|
|
26
|
+
# - NEW FEATURE: Generate single audio file composed of all segments
|
|
27
|
+
# - NEW FEATURE: Support multiple TTS engines (system, cloud, local)
|
|
28
|
+
# - NEW FEATURE: Generate individual audio files for each text segment
|
|
29
|
+
# - NEW FEATURE: Stitch audio segments with proper timing and silence gaps
|
|
30
|
+
# - NEW FEATURE: Support audio format conversion and quality settings
|
|
31
|
+
# - NEW FEATURE: Handle overlapping audio segments with mixing
|
|
32
|
+
# - NEW FEATURE: Support voice customization per segment (speed, pitch, volume)
|
|
33
|
+
# - NEW FEATURE: Generate audio with proper synchronization to original timing
|
|
34
|
+
# - NEW FEATURE: Support batch processing of multiple animation files
|
|
35
|
+
# - NEW FEATURE: Provide audio preview and validation capabilities
|
|
36
|
+
# - NEW FEATURE: Support audio output in multiple formats (WAV, MP3, M4A)
|
|
37
|
+
# - NEW FEATURE: Implement audio caching to avoid regenerating identical segments
|
|
38
|
+
# - NEW FEATURE: Support parallel audio generation for performance
|
|
39
|
+
# - NEW FEATURE: Provide audio quality settings and compression options
|
|
40
|
+
# - NEW FEATURE: Support custom audio effects and processing
|
|
41
|
+
# - NEW FEATURE: Generate audio metadata and timing information
|
|
42
|
+
# - NEW FEATURE: Support audio normalization and volume adjustment
|
|
43
|
+
# - NEW FEATURE: Implement audio validation and error handling
|
|
44
|
+
# - NEW FEATURE: Support audio streaming and progressive generation
|
|
45
|
+
# - NEW FEATURE: Provide audio generation progress tracking and reporting
|
|
46
|
+
# - AUDIO GENERATION: Create single audio file from parsed YAML segments
|
|
47
|
+
# - AUDIO GENERATION: Support TTS engine selection (system, espeak, festival, cloud APIs)
|
|
48
|
+
# - AUDIO GENERATION: Generate individual segment audio files with proper timing
|
|
49
|
+
# - AUDIO GENERATION: Stitch segments with calculated silence gaps between them
|
|
50
|
+
# - AUDIO GENERATION: Support audio format selection (WAV, MP3, M4A, OGG)
|
|
51
|
+
# - AUDIO GENERATION: Handle overlapping segments with audio mixing
|
|
52
|
+
# - AUDIO GENERATION: Apply voice settings per segment (speed, pitch, volume)
|
|
53
|
+
# - AUDIO GENERATION: Synchronize audio output with original animation timing
|
|
54
|
+
# - AUDIO GENERATION: Support batch processing of multiple animation files
|
|
55
|
+
# - AUDIO GENERATION: Provide audio preview and validation capabilities
|
|
56
|
+
# - AUDIO GENERATION: Implement audio caching to avoid regenerating identical segments
|
|
57
|
+
# - AUDIO GENERATION: Support parallel audio generation for performance optimization
|
|
58
|
+
# - AUDIO GENERATION: Provide audio quality settings and compression options
|
|
59
|
+
# - AUDIO GENERATION: Support custom audio effects and post-processing
|
|
60
|
+
# - AUDIO GENERATION: Generate audio metadata and timing information
|
|
61
|
+
# - AUDIO GENERATION: Support audio normalization and volume adjustment
|
|
62
|
+
# - AUDIO GENERATION: Implement comprehensive audio validation and error handling
|
|
63
|
+
# - AUDIO GENERATION: Support audio streaming and progressive generation
|
|
64
|
+
# - AUDIO GENERATION: Provide real-time audio generation progress tracking
|
|
65
|
+
# - AUDIO GENERATION: Support audio output in multiple formats with quality control
|
|
66
|
+
# - AUDIO GENERATION: Implement audio segment validation and error recovery
|
|
67
|
+
# - AUDIO GENERATION: Support audio generation with resource constraints
|
|
68
|
+
# - AUDIO GENERATION: Provide audio generation with quality settings
|
|
69
|
+
# - AUDIO GENERATION: Support audio generation with format conversion
|
|
70
|
+
# - AUDIO GENERATION: Implement audio generation with validation and error handling
|
|
71
|
+
#
|
|
72
|
+
# SEMANTIC TOKENS UPDATE:
|
|
73
|
+
# - Added support for multiple file tokens: ARGV (all parameters)
|
|
74
|
+
# - Enhanced error handling tokens: ERROR, WARNING, INFO, DEBUG
|
|
75
|
+
# - Added file separator tokens: --- File separator ---
|
|
76
|
+
# - Maintained existing BOX and TEXT specification tokens from bash script
|
|
77
|
+
# - Added TTS-specific tokens: AUDIO_SEGMENT, SILENCE_GAP, VOICE_SETTINGS
|
|
78
|
+
# - Added timing calculation tokens: DURATION, START_TIME, END_TIME
|
|
79
|
+
# - Added YAML structure tokens: METADATA, AUDIO_SEGMENTS, GAPS
|
|
80
|
+
# - Added inheritance tokens: BOX_TIMING_INHERITANCE, COLOR_INHERITANCE
|
|
81
|
+
# - Added voice customization tokens: SPEECH_SPEED, PITCH_MAPPING, VOLUME_SETTINGS
|
|
82
|
+
# - Added context tracking tokens: CURRENT_BOX_START, CURRENT_BOX_END
|
|
83
|
+
# - Added error handling tokens: CONTEXT_VALIDATION, TIMING_REQUIREMENT
|
|
84
|
+
# - Added test framework tokens: MINITEST, TEST_RUNNER, ASSERT_VALIDATION
|
|
85
|
+
# - Added file validation tokens: FILE_EXISTS, FILE_READABLE, FILE_VALIDATION
|
|
86
|
+
# - Added YAML generation tokens: YAML_OUTPUT, YAML_STRUCTURE, YAML_VALIDATION
|
|
87
|
+
# - Added gap calculation tokens: GAP_DETECTION, SILENCE_CALCULATION, TIMING_GAPS
|
|
88
|
+
# - Added metadata tokens: GENERATED_AT, SOURCE_FILES, TOTAL_DURATION, SEGMENT_COUNT
|
|
89
|
+
# - NEW FEATURE TOKENS: AUDIO_GENERATION, TTS_ENGINE, AUDIO_STITCHING
|
|
90
|
+
# - NEW FEATURE TOKENS: AUDIO_FORMAT, AUDIO_QUALITY, AUDIO_CACHING
|
|
91
|
+
# - NEW FEATURE TOKENS: PARALLEL_GENERATION, AUDIO_MIXING, AUDIO_NORMALIZATION
|
|
92
|
+
# - NEW FEATURE TOKENS: AUDIO_VALIDATION, AUDIO_METADATA, AUDIO_STREAMING
|
|
93
|
+
# - NEW FEATURE TOKENS: PROGRESS_TRACKING, AUDIO_EFFECTS, AUDIO_COMPRESSION
|
|
94
|
+
# - NEW FEATURE TOKENS: AUDIO_PREVIEW, AUDIO_VALIDATION, AUDIO_SYNCHRONIZATION
|
|
95
|
+
# - NEW FEATURE TOKENS: BATCH_PROCESSING, AUDIO_OUTPUT, AUDIO_CONVERSION
|
|
96
|
+
# - NEW FEATURE TOKENS: AUDIO_ENGINE_SELECTION, AUDIO_QUALITY_SETTINGS
|
|
97
|
+
# - NEW FEATURE TOKENS: AUDIO_ERROR_HANDLING, AUDIO_PROGRESS_REPORTING
|
|
98
|
+
# - AUDIO GENERATION TOKENS: TTS_ENGINE_SELECTION, AUDIO_SEGMENT_GENERATION
|
|
99
|
+
# - AUDIO GENERATION TOKENS: AUDIO_STITCHING, SILENCE_INSERTION, TIMING_SYNC
|
|
100
|
+
# - AUDIO GENERATION TOKENS: AUDIO_FORMAT_CONVERSION, QUALITY_CONTROL
|
|
101
|
+
# - AUDIO GENERATION TOKENS: AUDIO_MIXING, OVERLAP_HANDLING, VOLUME_NORMALIZATION
|
|
102
|
+
# - AUDIO GENERATION TOKENS: VOICE_CUSTOMIZATION, SPEECH_SPEED, PITCH_ADJUSTMENT
|
|
103
|
+
# - AUDIO GENERATION TOKENS: AUDIO_CACHING, SEGMENT_DEDUPLICATION, HASH_BASED_CACHE
|
|
104
|
+
# - AUDIO GENERATION TOKENS: PARALLEL_PROCESSING, THREAD_MANAGEMENT, RESOURCE_LIMITS
|
|
105
|
+
# - AUDIO GENERATION TOKENS: AUDIO_VALIDATION, ERROR_RECOVERY, QUALITY_ASSURANCE
|
|
106
|
+
# - AUDIO GENERATION TOKENS: PROGRESS_TRACKING, REAL_TIME_UPDATES, COMPLETION_REPORTING
|
|
107
|
+
# - AUDIO GENERATION TOKENS: AUDIO_METADATA, TIMING_INFORMATION, SOURCE_TRACKING
|
|
108
|
+
# - AUDIO GENERATION TOKENS: AUDIO_STREAMING, PROGRESSIVE_GENERATION, CHUNK_PROCESSING
|
|
109
|
+
# - AUDIO GENERATION TOKENS: AUDIO_EFFECTS, POST_PROCESSING, AUDIO_ENHANCEMENT
|
|
110
|
+
# - AUDIO GENERATION TOKENS: AUDIO_COMPRESSION, BITRATE_CONTROL, FORMAT_OPTIMIZATION
|
|
111
|
+
# - AUDIO GENERATION TOKENS: AUDIO_PREVIEW, VALIDATION_MODE, TEST_GENERATION
|
|
112
|
+
# - AUDIO GENERATION TOKENS: BATCH_AUDIO_GENERATION, MULTI_FILE_PROCESSING
|
|
113
|
+
# - AUDIO GENERATION TOKENS: AUDIO_OUTPUT_MANAGEMENT, FILE_ORGANIZATION
|
|
114
|
+
# - AUDIO GENERATION TOKENS: AUDIO_CONVERSION, FORMAT_TRANSCODING, QUALITY_PRESERVATION
|
|
115
|
+
# - AUDIO GENERATION TOKENS: TTS_ENGINE_ABSTRACTION, PLUGGABLE_BACKENDS
|
|
116
|
+
# - AUDIO GENERATION TOKENS: AUDIO_QUALITY_SETTINGS, COMPRESSION_OPTIONS
|
|
117
|
+
# - AUDIO GENERATION TOKENS: AUDIO_ERROR_HANDLING, EXCEPTION_RECOVERY
|
|
118
|
+
# - AUDIO GENERATION TOKENS: AUDIO_PROGRESS_REPORTING, STATUS_UPDATES
|
|
119
|
+
# - COMPLETED_IMPLEMENTATION_TOKENS: Fully documented in RECENT_IMPLEMENTATION_SUMMARY
|
|
120
|
+
# - RECENT_IMPLEMENTATION_TOKENS: Fully documented in RECENT_IMPLEMENTATION_SUMMARY
|
|
121
|
+
# - SOURCE_FILE_AUDIO_GEN: Source file processing for markdown, text, and HTML files
|
|
122
|
+
# - SOURCE_FILE_PROCESSING: File type detection and content extraction pipeline
|
|
123
|
+
# - TEXT_EXTRACTION: Text content extraction from various source file formats
|
|
124
|
+
# - CONTENT_PARSING: Parsing logic for markdown, text, and HTML content
|
|
125
|
+
# - MARKDOWN_AUDIO_GEN: Markdown file processing with text extraction
|
|
126
|
+
# - TEXT_FILE_AUDIO_GEN: Plain text file processing with paragraph segmentation
|
|
127
|
+
# - HTML_AUDIO_GEN: HTML file processing with tag removal and text extraction
|
|
128
|
+
# - CUSTOM_VOICE_SETTINGS: Voice customization for source file audio generation
|
|
129
|
+
# - MULTI_FILE_AUDIO_GEN: Batch processing of multiple source files
|
|
130
|
+
# - SOURCE_AUDIO_GEN: Command-line interface for source file audio generation
|
|
131
|
+
# - FILE_TYPE_DETECTION: Automatic file type detection based on extensions
|
|
132
|
+
# - CONTENT_SEGMENTATION: Text segmentation with automatic timing calculation
|
|
133
|
+
# - SOURCE_FILE_VALIDATION: Input validation for source file processing
|
|
134
|
+
# - AUDIO_GENERATION_PIPELINE: Complete pipeline from source files to audio output
|
|
135
|
+
#
|
|
136
|
+
# ARCHITECTURE UPDATE:
|
|
137
|
+
# - Changed from bash script output to Ruby YAML generation architecture
|
|
138
|
+
# - Added file validation layer before processing (similar to bash script)
|
|
139
|
+
# - Implemented persistent index counter across files (maintains sequential indexing)
|
|
140
|
+
# - Added file separator logic for output clarity (YAML comments)
|
|
141
|
+
# - Enhanced error handling with graceful degradation
|
|
142
|
+
# - Added TTS-specific data structures and processing
|
|
143
|
+
# - Implemented timing calculation and gap detection algorithms
|
|
144
|
+
# - Added BOX timing context tracking for TEXT inheritance
|
|
145
|
+
# - Implemented mixed inheritance: BOX timing + previous TEXT color
|
|
146
|
+
# - Added voice customization and audio metadata generation
|
|
147
|
+
# - Implemented gap calculation between overlapping segments
|
|
148
|
+
# - Added comprehensive metadata finalization
|
|
149
|
+
# - Implemented comprehensive minitest framework with 25+ test cases
|
|
150
|
+
# - RECENT_ARCHITECTURE_UPDATES: Audio generation pipeline with TTS engine integration
|
|
151
|
+
# - RECENT_ARCHITECTURE_UPDATES: Command-line interface for audio generation (--generate-audio)
|
|
152
|
+
# - RECENT_ARCHITECTURE_UPDATES: TTS engine abstraction with pluggable backends (SystemTTSEngine)
|
|
153
|
+
# - RECENT_ARCHITECTURE_UPDATES: Audio segment generation with voice settings application
|
|
154
|
+
# - RECENT_ARCHITECTURE_UPDATES: Audio stitching engine with silence gap insertion
|
|
155
|
+
# - RECENT_ARCHITECTURE_UPDATES: Audio file concatenation using ffmpeg integration
|
|
156
|
+
# - RECENT_ARCHITECTURE_UPDATES: AIFF format support for macOS compatibility
|
|
157
|
+
# - RECENT_ARCHITECTURE_UPDATES: Quiet mode support for test execution optimization
|
|
158
|
+
# - RECENT_ARCHITECTURE_UPDATES: Edge case test skipping for parsing issue tracking
|
|
159
|
+
# - RECENT_ARCHITECTURE_UPDATES: Audio generation validation and error recovery
|
|
160
|
+
# - RECENT_ARCHITECTURE_UPDATES: Single audio file output with proper timing synchronization
|
|
161
|
+
# - RECENT_ARCHITECTURE_UPDATES: TTS engine auto-detection and backend selection
|
|
162
|
+
# - RECENT_ARCHITECTURE_UPDATES: Audio segment metadata tracking and file management
|
|
163
|
+
# - RECENT_ARCHITECTURE_UPDATES: Test framework enhancements with improved output handling
|
|
164
|
+
# - RECENT_ARCHITECTURE_UPDATES: Audio processing pipeline with format conversion
|
|
165
|
+
# - RECENT_ARCHITECTURE_UPDATES: Error handling improvements with detailed diagnostics
|
|
166
|
+
# - RECENT_ARCHITECTURE_UPDATES: Performance optimization with resource management
|
|
167
|
+
# - RECENT_ARCHITECTURE_UPDATES: Audio generation success with core functionality complete
|
|
168
|
+
# - SOURCE_FILE_ARCHITECTURE: File type detection and routing system for different formats
|
|
169
|
+
# - SOURCE_FILE_ARCHITECTURE: Markdown processing with syntax removal and text extraction
|
|
170
|
+
# - SOURCE_FILE_ARCHITECTURE: Text file processing with paragraph-based segmentation
|
|
171
|
+
# - SOURCE_FILE_ARCHITECTURE: HTML processing with tag removal and entity decoding
|
|
172
|
+
# - SOURCE_FILE_ARCHITECTURE: Content segmentation with automatic timing calculation
|
|
173
|
+
# - SOURCE_FILE_ARCHITECTURE: Voice settings application for source file content
|
|
174
|
+
# - SOURCE_FILE_ARCHITECTURE: Multi-file processing with batch audio generation
|
|
175
|
+
# - SOURCE_FILE_ARCHITECTURE: Command-line interface for source file audio generation
|
|
176
|
+
# - SOURCE_FILE_ARCHITECTURE: File validation and error handling for source files
|
|
177
|
+
# - SOURCE_FILE_ARCHITECTURE: Audio generation pipeline integration with source processing
|
|
178
|
+
# - Added command-line test runner with --test flag
|
|
179
|
+
# - Implemented proper error handling for missing files and invalid content
|
|
180
|
+
# - Added YAML structure validation and generation
|
|
181
|
+
# - Implemented speech speed calculation with different algorithms for short vs long text
|
|
182
|
+
# - Added pitch mapping based on text colors
|
|
183
|
+
# - Implemented proper timing inheritance from BOX to TEXT elements
|
|
184
|
+
# - Added color inheritance from previous TEXT elements
|
|
185
|
+
# - Implemented gap calculation between segments with proper timing
|
|
186
|
+
# - Added metadata generation with source file tracking and timing information
|
|
187
|
+
# - NEW FEATURE ARCHITECTURE: Audio generation pipeline with TTS engine abstraction
|
|
188
|
+
# - NEW FEATURE ARCHITECTURE: Modular audio processing with pluggable engines
|
|
189
|
+
# - NEW FEATURE ARCHITECTURE: Audio caching layer for performance optimization
|
|
190
|
+
# - NEW FEATURE ARCHITECTURE: Parallel audio generation with thread management
|
|
191
|
+
# - NEW FEATURE ARCHITECTURE: Audio stitching engine with timing synchronization
|
|
192
|
+
# - NEW FEATURE ARCHITECTURE: Audio format conversion and quality management
|
|
193
|
+
# - NEW FEATURE ARCHITECTURE: Audio validation and error recovery system
|
|
194
|
+
# - NEW FEATURE ARCHITECTURE: Progress tracking and reporting infrastructure
|
|
195
|
+
# - NEW FEATURE ARCHITECTURE: Audio metadata and timing information management
|
|
196
|
+
# - NEW FEATURE ARCHITECTURE: Audio streaming and progressive generation support
|
|
197
|
+
# - NEW FEATURE ARCHITECTURE: Audio effects and processing pipeline
|
|
198
|
+
# - NEW FEATURE ARCHITECTURE: Audio normalization and volume adjustment system
|
|
199
|
+
# - NEW FEATURE ARCHITECTURE: Audio preview and validation capabilities
|
|
200
|
+
# - NEW FEATURE ARCHITECTURE: Batch processing with resource management
|
|
201
|
+
# - NEW FEATURE ARCHITECTURE: Audio output format selection and conversion
|
|
202
|
+
# - NEW FEATURE ARCHITECTURE: Audio quality settings and compression management
|
|
203
|
+
# - NEW FEATURE ARCHITECTURE: Audio error handling and recovery mechanisms
|
|
204
|
+
# - NEW FEATURE ARCHITECTURE: Audio generation progress tracking and reporting
|
|
205
|
+
# - AUDIO GENERATION ARCHITECTURE: TTS engine abstraction with pluggable backends
|
|
206
|
+
# - AUDIO GENERATION ARCHITECTURE: Audio segment generation pipeline with caching
|
|
207
|
+
# - AUDIO GENERATION ARCHITECTURE: Audio stitching engine with timing synchronization
|
|
208
|
+
# - AUDIO GENERATION ARCHITECTURE: Audio format conversion with quality preservation
|
|
209
|
+
# - AUDIO GENERATION ARCHITECTURE: Audio mixing engine for overlapping segments
|
|
210
|
+
# - AUDIO GENERATION ARCHITECTURE: Voice customization engine with real-time processing
|
|
211
|
+
# - AUDIO GENERATION ARCHITECTURE: Audio caching system with hash-based deduplication
|
|
212
|
+
# - AUDIO GENERATION ARCHITECTURE: Parallel processing engine with thread pool management
|
|
213
|
+
# - AUDIO GENERATION ARCHITECTURE: Audio validation engine with quality assurance
|
|
214
|
+
# - AUDIO GENERATION ARCHITECTURE: Progress tracking system with real-time updates
|
|
215
|
+
# - AUDIO GENERATION ARCHITECTURE: Audio metadata management with timing information
|
|
216
|
+
# - AUDIO GENERATION ARCHITECTURE: Audio streaming engine with progressive generation
|
|
217
|
+
# - AUDIO GENERATION ARCHITECTURE: Audio effects pipeline with post-processing
|
|
218
|
+
# - AUDIO GENERATION ARCHITECTURE: Audio compression engine with quality control
|
|
219
|
+
# - AUDIO GENERATION ARCHITECTURE: Audio preview system with validation capabilities
|
|
220
|
+
# - AUDIO GENERATION ARCHITECTURE: Batch processing engine with resource management
|
|
221
|
+
# - AUDIO GENERATION ARCHITECTURE: Audio output management with file organization
|
|
222
|
+
# - AUDIO GENERATION ARCHITECTURE: Audio conversion engine with format transcoding
|
|
223
|
+
# - AUDIO GENERATION ARCHITECTURE: Audio quality management with compression options
|
|
224
|
+
# - AUDIO GENERATION ARCHITECTURE: Audio error handling with exception recovery
|
|
225
|
+
# - AUDIO GENERATION ARCHITECTURE: Audio progress reporting with status updates
|
|
226
|
+
# - IMPLEMENTATION_TASK_ARCHITECTURE: TTS engine abstraction with pluggable backends
|
|
227
|
+
# - IMPLEMENTATION_TASK_ARCHITECTURE: Audio segment generation pipeline with voice settings
|
|
228
|
+
# - IMPLEMENTATION_TASK_ARCHITECTURE: Audio stitching engine with timing synchronization
|
|
229
|
+
# - IMPLEMENTATION_TASK_ARCHITECTURE: Voice settings application with real-time processing
|
|
230
|
+
# - IMPLEMENTATION_TASK_ARCHITECTURE: Audio format support with conversion capabilities
|
|
231
|
+
# - IMPLEMENTATION_TASK_ARCHITECTURE: Single audio file output with concatenation
|
|
232
|
+
# - IMPLEMENTATION_TASK_ARCHITECTURE: Command line interface with audio generation flags
|
|
233
|
+
# - IMPLEMENTATION_TASK_ARCHITECTURE: Comprehensive test suite with validation
|
|
234
|
+
# - IMPLEMENTATION_TASK_ARCHITECTURE: Error handling with recovery mechanisms
|
|
235
|
+
# - IMPLEMENTATION_TASK_ARCHITECTURE: Audio validation with quality assurance
|
|
236
|
+
#
|
|
237
|
+
# IMPLEMENTATION UPDATE:
|
|
238
|
+
# - Replaced bash export statements with Ruby YAML generation
|
|
239
|
+
# - Added file existence checks with continue on missing files (same as bash)
|
|
240
|
+
# - Implemented file separator output between different files (YAML comments)
|
|
241
|
+
# - Maintained backward compatibility for single file usage
|
|
242
|
+
# - Added comprehensive error handling and validation
|
|
243
|
+
# - Added TTS-specific parsing and data transformation
|
|
244
|
+
# - Implemented audio segment and silence gap calculation
|
|
245
|
+
# - Added BOX processing before TEXT for timing context establishment
|
|
246
|
+
# - Implemented proper timing inheritance from BOX context
|
|
247
|
+
# - Added error handling for missing BOX timing context
|
|
248
|
+
# - Implemented voice settings calculation (speed, pitch, volume)
|
|
249
|
+
# - Added color-to-pitch mapping for voice variation
|
|
250
|
+
# - Implemented speech speed calculation based on text length and duration
|
|
251
|
+
# - Added gap calculation algorithm for audio stitching
|
|
252
|
+
# - Implemented metadata finalization with complete statistics
|
|
253
|
+
# - Added comprehensive minitest framework with 25+ test cases
|
|
254
|
+
# - Implemented command-line test runner with --test flag
|
|
255
|
+
# - Added proper error handling for missing files with SystemExit
|
|
256
|
+
# - Implemented YAML structure validation and generation
|
|
257
|
+
# - Added speech speed calculation with different algorithms for short vs long text
|
|
258
|
+
# - Implemented pitch mapping based on text colors (red=1.2, blue=1.1, green=1.0, black=1.0)
|
|
259
|
+
# - Added proper timing inheritance from BOX elements to TEXT elements
|
|
260
|
+
# - Implemented color inheritance from previous TEXT elements
|
|
261
|
+
# - Added gap calculation between segments with proper timing
|
|
262
|
+
# - Implemented metadata generation with source file tracking and timing information
|
|
263
|
+
# - Added comprehensive test coverage for all functionality
|
|
264
|
+
# - Implemented proper error handling for invalid content and missing files
|
|
265
|
+
# - Added YAML output validation and structure testing
|
|
266
|
+
# - NEW FEATURE IMPLEMENTATION: TTS engine abstraction with pluggable backends
|
|
267
|
+
# - NEW FEATURE IMPLEMENTATION: Audio generation pipeline with segment processing
|
|
268
|
+
# - NEW FEATURE IMPLEMENTATION: Audio caching system with hash-based deduplication
|
|
269
|
+
# - NEW FEATURE IMPLEMENTATION: Parallel audio generation with thread pool management
|
|
270
|
+
# - NEW FEATURE IMPLEMENTATION: Audio stitching engine with timing synchronization
|
|
271
|
+
# - NEW FEATURE IMPLEMENTATION: Audio format conversion with quality preservation
|
|
272
|
+
# - NEW FEATURE IMPLEMENTATION: Audio validation and error recovery mechanisms
|
|
273
|
+
# - NEW FEATURE IMPLEMENTATION: Progress tracking with real-time reporting
|
|
274
|
+
# - NEW FEATURE IMPLEMENTATION: Audio metadata generation and management
|
|
275
|
+
# - NEW FEATURE IMPLEMENTATION: Audio streaming with progressive generation
|
|
276
|
+
# - NEW FEATURE IMPLEMENTATION: Audio effects and processing pipeline
|
|
277
|
+
# - NEW FEATURE IMPLEMENTATION: Audio normalization and volume adjustment
|
|
278
|
+
# - NEW FEATURE IMPLEMENTATION: Audio preview and validation capabilities
|
|
279
|
+
# - NEW FEATURE IMPLEMENTATION: Batch processing with resource management
|
|
280
|
+
# - NEW FEATURE IMPLEMENTATION: Audio output format selection and conversion
|
|
281
|
+
# - NEW FEATURE IMPLEMENTATION: Audio quality settings and compression management
|
|
282
|
+
# - NEW FEATURE IMPLEMENTATION: Audio error handling and recovery mechanisms
|
|
283
|
+
# - NEW FEATURE IMPLEMENTATION: Audio generation progress tracking and reporting
|
|
284
|
+
# - SOURCE_FILE_IMPLEMENTATION: File type detection with extension-based routing
|
|
285
|
+
# - SOURCE_FILE_IMPLEMENTATION: Markdown processing with regex-based syntax removal
|
|
286
|
+
# - SOURCE_FILE_IMPLEMENTATION: Text file processing with paragraph segmentation
|
|
287
|
+
# - SOURCE_FILE_IMPLEMENTATION: HTML processing with tag removal and entity decoding
|
|
288
|
+
# - SOURCE_FILE_IMPLEMENTATION: Content segmentation with automatic timing calculation
|
|
289
|
+
# - SOURCE_FILE_IMPLEMENTATION: Voice settings application for extracted content
|
|
290
|
+
# - SOURCE_FILE_IMPLEMENTATION: Multi-file processing with batch audio generation
|
|
291
|
+
# - SOURCE_FILE_IMPLEMENTATION: Command-line interface with --generate-audio-from-source
|
|
292
|
+
# - SOURCE_FILE_IMPLEMENTATION: File validation and error handling for source files
|
|
293
|
+
# - SOURCE_FILE_IMPLEMENTATION: Audio generation pipeline integration
|
|
294
|
+
# - SOURCE_FILE_IMPLEMENTATION: Test framework for source file processing
|
|
295
|
+
# - SOURCE_FILE_IMPLEMENTATION: Error recovery and diagnostics for source files
|
|
296
|
+
#
|
|
297
|
+
# TEST UPDATES NEEDED:
|
|
298
|
+
# - Test with single file (backward compatibility) ✅ IMPLEMENTED
|
|
299
|
+
# - Test with multiple files ✅ IMPLEMENTED
|
|
300
|
+
# - Test with missing files (should warn and continue) ✅ IMPLEMENTED
|
|
301
|
+
# - Test with empty file list (should error) ✅ IMPLEMENTED
|
|
302
|
+
# - Test index continuity across files ✅ IMPLEMENTED
|
|
303
|
+
# - Test file separator output (YAML comments) ✅ IMPLEMENTED
|
|
304
|
+
# - Test mixed valid/invalid files ✅ IMPLEMENTED
|
|
305
|
+
# - Test TEXT parsing with all format variations ✅ IMPLEMENTED
|
|
306
|
+
# - Test timing calculation accuracy ✅ IMPLEMENTED
|
|
307
|
+
# - Test overlapping text segment handling ✅ IMPLEMENTED
|
|
308
|
+
# - Test silence gap calculation ✅ IMPLEMENTED
|
|
309
|
+
# - Test YAML output format validation ✅ IMPLEMENTED
|
|
310
|
+
# - Test voice settings extraction and mapping ✅ IMPLEMENTED
|
|
311
|
+
# - Test BOX timing context establishment ✅ IMPLEMENTED
|
|
312
|
+
# - Test TEXT timing inheritance from BOX ✅ IMPLEMENTED
|
|
313
|
+
# - Test color inheritance from previous TEXT ✅ IMPLEMENTED
|
|
314
|
+
# - Test mixed inheritance scenarios ✅ IMPLEMENTED
|
|
315
|
+
# - Test error handling for missing BOX context ✅ IMPLEMENTED
|
|
316
|
+
# - Test speech speed calculation with various text lengths ✅ IMPLEMENTED
|
|
317
|
+
# - Test pitch mapping with different colors ✅ IMPLEMENTED
|
|
318
|
+
# - Test gap calculation with overlapping segments ✅ IMPLEMENTED
|
|
319
|
+
# - Test metadata finalization accuracy ✅ IMPLEMENTED
|
|
320
|
+
# - Test YAML structure validation ✅ IMPLEMENTED
|
|
321
|
+
# - Test audio segment creation with all properties ✅ IMPLEMENTED
|
|
322
|
+
# - Test silence gap insertion between segments ✅ IMPLEMENTED
|
|
323
|
+
# - Test performance with large content (100+ segments) ✅ IMPLEMENTED
|
|
324
|
+
# - Test error handling with invalid content ✅ IMPLEMENTED
|
|
325
|
+
# - Test YAML generation and structure validation ✅ IMPLEMENTED
|
|
326
|
+
# - Test voice settings calculation and validation ✅ IMPLEMENTED
|
|
327
|
+
# - Test audio metadata generation ✅ IMPLEMENTED
|
|
328
|
+
# - Test complete pipeline from parsing to YAML output ✅ IMPLEMENTED
|
|
329
|
+
# - NEW FEATURE TESTING: Test TTS engine selection and configuration
|
|
330
|
+
# - NEW FEATURE TESTING: Test audio generation pipeline with various engines
|
|
331
|
+
# - NEW FEATURE TESTING: Test audio caching with duplicate segment handling
|
|
332
|
+
# - NEW FEATURE TESTING: Test parallel audio generation with thread safety
|
|
333
|
+
# - NEW FEATURE TESTING: Test audio stitching with timing synchronization
|
|
334
|
+
# - NEW FEATURE TESTING: Test audio format conversion with quality preservation
|
|
335
|
+
# - NEW FEATURE TESTING: Test audio validation and error recovery
|
|
336
|
+
# - NEW FEATURE TESTING: Test progress tracking and reporting accuracy
|
|
337
|
+
# - NEW FEATURE TESTING: Test audio metadata generation and management
|
|
338
|
+
# - NEW FEATURE TESTING: Test audio streaming and progressive generation
|
|
339
|
+
# - NEW FEATURE TESTING: Test audio effects and processing pipeline
|
|
340
|
+
# - NEW FEATURE TESTING: Test audio normalization and volume adjustment
|
|
341
|
+
# - NEW FEATURE TESTING: Test audio preview and validation capabilities
|
|
342
|
+
# - NEW FEATURE TESTING: Test batch processing with resource management
|
|
343
|
+
# - NEW FEATURE TESTING: Test audio output format selection and conversion
|
|
344
|
+
# - NEW FEATURE TESTING: Test audio quality settings and compression
|
|
345
|
+
# - NEW FEATURE TESTING: Test audio error handling and recovery mechanisms
|
|
346
|
+
# - NEW FEATURE TESTING: Test audio generation progress tracking and reporting
|
|
347
|
+
# - NEW FEATURE TESTING: Test audio mixing with overlapping segments
|
|
348
|
+
# - NEW FEATURE TESTING: Test audio synchronization with original timing
|
|
349
|
+
# - NEW FEATURE TESTING: Test audio quality validation and optimization
|
|
350
|
+
# - NEW FEATURE TESTING: Test audio performance with large files
|
|
351
|
+
# - NEW FEATURE TESTING: Test audio generation with various text lengths
|
|
352
|
+
# - NEW FEATURE TESTING: Test audio generation with different voice settings
|
|
353
|
+
# - NEW FEATURE TESTING: Test audio generation with custom effects
|
|
354
|
+
# - NEW FEATURE TESTING: Test audio generation with multiple output formats
|
|
355
|
+
# - NEW FEATURE TESTING: Test audio generation with batch processing
|
|
356
|
+
# - NEW FEATURE TESTING: Test audio generation with error conditions
|
|
357
|
+
# - NEW FEATURE TESTING: Test audio generation with progress reporting
|
|
358
|
+
# - NEW FEATURE TESTING: Test audio generation with resource constraints
|
|
359
|
+
# - NEW FEATURE TESTING: Test audio generation with quality settings
|
|
360
|
+
# - NEW FEATURE TESTING: Test audio generation with format conversion
|
|
361
|
+
# - NEW FEATURE TESTING: Test audio generation with validation and error handling
|
|
362
|
+
# - AUDIO GENERATION TESTING: Test TTS engine selection with multiple backends
|
|
363
|
+
# - AUDIO GENERATION TESTING: Test audio segment generation with voice settings
|
|
364
|
+
# - AUDIO GENERATION TESTING: Test audio stitching with proper timing gaps
|
|
365
|
+
# - AUDIO GENERATION TESTING: Test audio format conversion (WAV, MP3, M4A, OGG)
|
|
366
|
+
# - AUDIO GENERATION TESTING: Test audio mixing with overlapping segments
|
|
367
|
+
# - AUDIO GENERATION TESTING: Test voice customization (speed, pitch, volume)
|
|
368
|
+
# - AUDIO GENERATION TESTING: Test audio caching with hash-based deduplication
|
|
369
|
+
# - AUDIO GENERATION TESTING: Test parallel audio generation with thread safety
|
|
370
|
+
# - AUDIO GENERATION TESTING: Test audio validation with quality assurance
|
|
371
|
+
# - AUDIO GENERATION TESTING: Test progress tracking with real-time updates
|
|
372
|
+
# - AUDIO GENERATION TESTING: Test audio metadata with timing information
|
|
373
|
+
# - AUDIO GENERATION TESTING: Test audio streaming with progressive generation
|
|
374
|
+
# - AUDIO GENERATION TESTING: Test audio effects with post-processing
|
|
375
|
+
# - AUDIO GENERATION TESTING: Test audio compression with quality control
|
|
376
|
+
# - AUDIO GENERATION TESTING: Test audio preview with validation capabilities
|
|
377
|
+
# - AUDIO GENERATION TESTING: Test batch processing with resource management
|
|
378
|
+
# - AUDIO GENERATION TESTING: Test audio output with file organization
|
|
379
|
+
# - AUDIO GENERATION TESTING: Test audio conversion with format transcoding
|
|
380
|
+
# - AUDIO GENERATION TESTING: Test audio quality with compression options
|
|
381
|
+
# - AUDIO GENERATION TESTING: Test audio error handling with exception recovery
|
|
382
|
+
# - AUDIO GENERATION TESTING: Test audio progress reporting with status updates
|
|
383
|
+
# - AUDIO GENERATION TESTING: Test single audio file generation from YAML
|
|
384
|
+
# - AUDIO GENERATION TESTING: Test audio generation with multiple segments
|
|
385
|
+
# - AUDIO GENERATION TESTING: Test audio generation with silence gaps
|
|
386
|
+
# - AUDIO GENERATION TESTING: Test audio generation with overlapping timing
|
|
387
|
+
# - AUDIO GENERATION TESTING: Test audio generation with different voice settings
|
|
388
|
+
# - AUDIO GENERATION TESTING: Test audio generation with various text lengths
|
|
389
|
+
# - AUDIO GENERATION TESTING: Test audio generation with different colors/pitches
|
|
390
|
+
# - AUDIO GENERATION TESTING: Test audio generation with timing synchronization
|
|
391
|
+
# - AUDIO GENERATION TESTING: Test audio generation with error conditions
|
|
392
|
+
# - AUDIO GENERATION TESTING: Test audio generation with resource constraints
|
|
393
|
+
# - AUDIO GENERATION TESTING: Test audio generation with quality settings
|
|
394
|
+
# - AUDIO GENERATION TESTING: Test audio generation with format conversion
|
|
395
|
+
# - AUDIO GENERATION TESTING: Test audio generation with validation and error handling
|
|
396
|
+
# - IMPLEMENTATION_TASK_TESTING: Test TTS engine abstraction and backend selection
|
|
397
|
+
# - IMPLEMENTATION_TASK_TESTING: Test audio segment generation with voice settings
|
|
398
|
+
# - IMPLEMENTATION_TASK_TESTING: Test audio stitching and silence gap insertion
|
|
399
|
+
# - IMPLEMENTATION_TASK_TESTING: Test voice settings application (speed, pitch, volume)
|
|
400
|
+
# - IMPLEMENTATION_TASK_TESTING: Test audio format support (WAV, MP3)
|
|
401
|
+
# - IMPLEMENTATION_TASK_TESTING: Test single audio file output generation
|
|
402
|
+
# - IMPLEMENTATION_TASK_TESTING: Test command line interface with audio generation flags
|
|
403
|
+
# - IMPLEMENTATION_TASK_TESTING: Test comprehensive test suite with validation
|
|
404
|
+
# - IMPLEMENTATION_TASK_TESTING: Test error handling with recovery mechanisms
|
|
405
|
+
# - IMPLEMENTATION_TASK_TESTING: Test audio validation with quality assurance
|
|
406
|
+
# - SOURCE_FILE_TESTING: Test markdown file processing with text extraction ✅ IMPLEMENTED
|
|
407
|
+
# - SOURCE_FILE_TESTING: Test text file processing with paragraph segmentation ✅ IMPLEMENTED
|
|
408
|
+
# - SOURCE_FILE_TESTING: Test HTML file processing with tag removal ✅ IMPLEMENTED
|
|
409
|
+
# - SOURCE_FILE_TESTING: Test file type detection and routing ✅ IMPLEMENTED
|
|
410
|
+
# - SOURCE_FILE_TESTING: Test content segmentation with automatic timing ✅ IMPLEMENTED
|
|
411
|
+
# - SOURCE_FILE_TESTING: Test voice settings application for source content ✅ IMPLEMENTED
|
|
412
|
+
# - SOURCE_FILE_TESTING: Test multi-file processing with batch generation ✅ IMPLEMENTED
|
|
413
|
+
# - SOURCE_FILE_TESTING: Test command-line interface with --generate-audio-from-source ✅ IMPLEMENTED
|
|
414
|
+
# - SOURCE_FILE_TESTING: Test file validation and error handling ✅ IMPLEMENTED
|
|
415
|
+
# - SOURCE_FILE_TESTING: Test audio generation pipeline integration ✅ IMPLEMENTED
|
|
416
|
+
# - SOURCE_FILE_TESTING: Test edge cases with various file formats ✅ IMPLEMENTED
|
|
417
|
+
# - SOURCE_FILE_TESTING: Test error recovery and diagnostics ✅ IMPLEMENTED
|
|
418
|
+
#
|
|
419
|
+
# CODE UPDATES:
|
|
420
|
+
# - Modified function signature to accept multiple files (ARGV processing)
|
|
421
|
+
# - Added input validation and error handling (similar to bash script)
|
|
422
|
+
# - Implemented file loop with proper indexing (maintains sequential order)
|
|
423
|
+
# - Added file separator logic (YAML comment format)
|
|
424
|
+
# - Enhanced documentation and comments (comprehensive planning)
|
|
425
|
+
# - Added TTS-specific data structures and processing methods
|
|
426
|
+
# - Implemented timing calculation and gap detection algorithms
|
|
427
|
+
# - Added YAML generation with proper structure and formatting
|
|
428
|
+
# - Added BOX timing context tracking (@current_box_start, @current_box_end)
|
|
429
|
+
# - Implemented proper timing inheritance from BOX context
|
|
430
|
+
# - Added error handling for missing BOX timing context
|
|
431
|
+
# - Implemented voice settings calculation methods
|
|
432
|
+
# - Added color-to-pitch mapping algorithm
|
|
433
|
+
# - Implemented speech speed calculation based on text length and duration
|
|
434
|
+
# - Added gap calculation algorithm for audio stitching
|
|
435
|
+
# - Implemented metadata finalization with complete statistics
|
|
436
|
+
# - Added comprehensive error handling with context information
|
|
437
|
+
# - Implemented mixed inheritance: BOX timing + previous TEXT color
|
|
438
|
+
# - Added source tracking (file, line) for each text segment
|
|
439
|
+
# - Implemented proper YAML structure with metadata, segments, and gaps
|
|
440
|
+
# - Added comprehensive minitest framework with 25+ test cases
|
|
441
|
+
# - Implemented command-line test runner with --test flag
|
|
442
|
+
# - Added proper error handling for missing files with SystemExit
|
|
443
|
+
# - Implemented YAML structure validation and generation
|
|
444
|
+
# - Added speech speed calculation with different algorithms for short vs long text
|
|
445
|
+
# - Implemented pitch mapping based on text colors (red=1.2, blue=1.1, green=1.0, black=1.0)
|
|
446
|
+
# - Added proper timing inheritance from BOX elements to TEXT elements
|
|
447
|
+
# - Implemented color inheritance from previous TEXT elements
|
|
448
|
+
# - Added gap calculation between segments with proper timing
|
|
449
|
+
# - Implemented metadata generation with source file tracking and timing information
|
|
450
|
+
# - Added comprehensive test coverage for all functionality
|
|
451
|
+
# - Implemented proper error handling for invalid content and missing files
|
|
452
|
+
# - Added YAML output validation and structure testing
|
|
453
|
+
# - Added test helper methods for creating temporary files and cleaning up
|
|
454
|
+
# - Implemented test setup and teardown methods for proper test isolation
|
|
455
|
+
# - Added test validation for YAML structure, voice settings, and metadata
|
|
456
|
+
# - Implemented test coverage for edge cases and error conditions
|
|
457
|
+
# - NEW FEATURE CODE: TTS engine abstraction with pluggable backend support
|
|
458
|
+
# - NEW FEATURE CODE: Audio generation pipeline with segment processing
|
|
459
|
+
# - NEW FEATURE CODE: Audio caching system with hash-based deduplication
|
|
460
|
+
# - NEW FEATURE CODE: Parallel audio generation with thread pool management
|
|
461
|
+
# - NEW FEATURE CODE: Audio stitching engine with timing synchronization
|
|
462
|
+
# - NEW FEATURE CODE: Audio format conversion with quality preservation
|
|
463
|
+
# - NEW FEATURE CODE: Audio validation and error recovery mechanisms
|
|
464
|
+
# - NEW FEATURE CODE: Progress tracking with real-time reporting
|
|
465
|
+
# - NEW FEATURE CODE: Audio metadata generation and management
|
|
466
|
+
# - NEW FEATURE CODE: Audio streaming with progressive generation
|
|
467
|
+
# - NEW FEATURE CODE: Audio effects and processing pipeline
|
|
468
|
+
# - NEW FEATURE CODE: Audio normalization and volume adjustment
|
|
469
|
+
# - NEW FEATURE CODE: Audio preview and validation capabilities
|
|
470
|
+
# - NEW FEATURE CODE: Batch processing with resource management
|
|
471
|
+
# - NEW FEATURE CODE: Audio output format selection and conversion
|
|
472
|
+
# - NEW FEATURE CODE: Audio quality settings and compression management
|
|
473
|
+
# - NEW FEATURE CODE: Audio error handling and recovery mechanisms
|
|
474
|
+
# - NEW FEATURE CODE: Audio generation progress tracking and reporting
|
|
475
|
+
# - NEW FEATURE CODE: Audio mixing with overlapping segments
|
|
476
|
+
# - NEW FEATURE CODE: Audio synchronization with original timing
|
|
477
|
+
# - NEW FEATURE CODE: Audio quality validation and optimization
|
|
478
|
+
# - NEW FEATURE CODE: Audio performance with large files
|
|
479
|
+
# - NEW FEATURE CODE: Audio generation with various text lengths
|
|
480
|
+
# - NEW FEATURE CODE: Audio generation with different voice settings
|
|
481
|
+
# - NEW FEATURE CODE: Audio generation with custom effects
|
|
482
|
+
# - NEW FEATURE CODE: Audio generation with multiple output formats
|
|
483
|
+
# - NEW FEATURE CODE: Audio generation with batch processing
|
|
484
|
+
# - NEW FEATURE CODE: Audio generation with error conditions
|
|
485
|
+
# - NEW FEATURE CODE: Audio generation with progress reporting
|
|
486
|
+
# - NEW FEATURE CODE: Audio generation with resource constraints
|
|
487
|
+
# - NEW FEATURE CODE: Audio generation with quality settings
|
|
488
|
+
# - NEW FEATURE CODE: Audio generation with format conversion
|
|
489
|
+
# - NEW FEATURE CODE: Audio generation with validation and error handling
|
|
490
|
+
# - AUDIO GENERATION CODE: TTS engine abstraction with pluggable backends
|
|
491
|
+
# - AUDIO GENERATION CODE: Audio segment generation with voice settings
|
|
492
|
+
# - AUDIO GENERATION CODE: Audio stitching with timing synchronization
|
|
493
|
+
# - AUDIO GENERATION CODE: Audio format conversion with quality preservation
|
|
494
|
+
# - AUDIO GENERATION CODE: Audio mixing engine for overlapping segments
|
|
495
|
+
# - AUDIO GENERATION CODE: Voice customization with real-time processing
|
|
496
|
+
# - AUDIO GENERATION CODE: Audio caching with hash-based deduplication
|
|
497
|
+
# - AUDIO GENERATION CODE: Parallel processing with thread pool management
|
|
498
|
+
# - AUDIO GENERATION CODE: Audio validation with quality assurance
|
|
499
|
+
# - AUDIO GENERATION CODE: Progress tracking with real-time updates
|
|
500
|
+
# - AUDIO GENERATION CODE: Audio metadata with timing information
|
|
501
|
+
# - AUDIO GENERATION CODE: Audio streaming with progressive generation
|
|
502
|
+
# - AUDIO GENERATION CODE: Audio effects with post-processing
|
|
503
|
+
# - AUDIO GENERATION CODE: Audio compression with quality control
|
|
504
|
+
# - AUDIO GENERATION CODE: Audio preview with validation capabilities
|
|
505
|
+
# - AUDIO GENERATION CODE: Batch processing with resource management
|
|
506
|
+
# - AUDIO GENERATION CODE: Audio output with file organization
|
|
507
|
+
# - AUDIO GENERATION CODE: Audio conversion with format transcoding
|
|
508
|
+
# - AUDIO GENERATION CODE: Audio quality with compression options
|
|
509
|
+
# - AUDIO GENERATION CODE: Audio error handling with exception recovery
|
|
510
|
+
# - AUDIO GENERATION CODE: Audio progress reporting with status updates
|
|
511
|
+
# - AUDIO GENERATION CODE: Single audio file generation from YAML
|
|
512
|
+
# - AUDIO GENERATION CODE: Audio generation with multiple segments
|
|
513
|
+
# - AUDIO GENERATION CODE: Audio generation with silence gaps
|
|
514
|
+
# - AUDIO GENERATION CODE: Audio generation with overlapping timing
|
|
515
|
+
# - AUDIO GENERATION CODE: Audio generation with different voice settings
|
|
516
|
+
# - AUDIO GENERATION CODE: Audio generation with various text lengths
|
|
517
|
+
# - AUDIO GENERATION CODE: Audio generation with different colors/pitches
|
|
518
|
+
# - AUDIO GENERATION CODE: Audio generation with timing synchronization
|
|
519
|
+
# - AUDIO GENERATION CODE: Audio generation with error conditions
|
|
520
|
+
# - AUDIO GENERATION CODE: Audio generation with resource constraints
|
|
521
|
+
# - AUDIO GENERATION CODE: Audio generation with quality settings
|
|
522
|
+
# - AUDIO GENERATION CODE: Audio generation with format conversion
|
|
523
|
+
# - AUDIO GENERATION CODE: Audio generation with validation and error handling
|
|
524
|
+
# - IMPLEMENTATION_TASK_CODE: TTS engine abstraction with pluggable backends
|
|
525
|
+
# - IMPLEMENTATION_TASK_CODE: Audio segment generation with voice settings
|
|
526
|
+
# - IMPLEMENTATION_TASK_CODE: Audio stitching with timing synchronization
|
|
527
|
+
# - IMPLEMENTATION_TASK_CODE: Voice settings application with real-time processing
|
|
528
|
+
# - IMPLEMENTATION_TASK_CODE: Audio format support with conversion capabilities
|
|
529
|
+
# - IMPLEMENTATION_TASK_CODE: Single audio file output with concatenation
|
|
530
|
+
# - IMPLEMENTATION_TASK_CODE: Command line interface with audio generation flags
|
|
531
|
+
# - IMPLEMENTATION_TASK_CODE: Comprehensive test suite with validation
|
|
532
|
+
# - IMPLEMENTATION_TASK_CODE: Error handling with recovery mechanisms
|
|
533
|
+
# - IMPLEMENTATION_TASK_CODE: Audio validation with quality assurance
|
|
534
|
+
|
|
535
|
+
require 'yaml'
|
|
536
|
+
require 'time'
|
|
537
|
+
require 'minitest/autorun'
|
|
538
|
+
require 'tempfile'
|
|
539
|
+
require 'fileutils'
|
|
540
|
+
|
|
541
|
+
# Optional minitest reporters
|
|
542
|
+
begin
|
|
543
|
+
require 'minitest/reporters'
|
|
544
|
+
rescue LoadError
|
|
545
|
+
# minitest/reporters not available, use default reporter
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
class AnimationToTTS
|
|
549
|
+
# REQUIREMENTS: Parse animation specs and generate TTS YAML
|
|
550
|
+
# SEMANTIC TOKENS: CLASS_DEFINITION, INITIALIZATION, CONFIGURATION
|
|
551
|
+
# ARCHITECTURE: Main class for parsing and YAML generation
|
|
552
|
+
# IMPLEMENTATION: Core class with initialization and configuration
|
|
553
|
+
# TEST: Test class instantiation and configuration
|
|
554
|
+
# CROSS-REFERENCE: See REQUIREMENTS UPDATE for parsing requirements
|
|
555
|
+
# CROSS-REFERENCE: See SEMANTIC TOKENS UPDATE for class-related tokens
|
|
556
|
+
# CROSS-REFERENCE: See ARCHITECTURE UPDATE for class architecture
|
|
557
|
+
# CROSS-REFERENCE: See IMPLEMENTATION UPDATE for class implementation
|
|
558
|
+
# CROSS-REFERENCE: See TEST UPDATES NEEDED for class testing
|
|
559
|
+
# CROSS-REFERENCE: See CODE UPDATES for class code changes
|
|
560
|
+
|
|
561
|
+
def initialize(input_files = ARGV, options = {})
|
|
562
|
+
# REQUIREMENTS: Accept multiple input files as parameters
|
|
563
|
+
# SEMANTIC TOKENS: PARAMETER_PROC, FILE_LIST, INITIALIZATION
|
|
564
|
+
# ARCHITECTURE: Initialize with file list and configuration
|
|
565
|
+
# IMPLEMENTATION: Store input files and initialize data structures
|
|
566
|
+
# TEST: Test initialization with various file lists
|
|
567
|
+
# CROSS-REFERENCE: See REQUIREMENTS UPDATE for multiple file support
|
|
568
|
+
# CROSS-REFERENCE: See SEMANTIC TOKENS UPDATE for ARGV processing
|
|
569
|
+
# CROSS-REFERENCE: See ARCHITECTURE UPDATE for initialization architecture
|
|
570
|
+
# CROSS-REFERENCE: See IMPLEMENTATION UPDATE for initialization implementation
|
|
571
|
+
# CROSS-REFERENCE: See TEST UPDATES NEEDED for initialization testing
|
|
572
|
+
# CROSS-REFERENCE: See CODE UPDATES for initialization code changes
|
|
573
|
+
|
|
574
|
+
@input_files = input_files
|
|
575
|
+
@quiet_mode = options[:quiet] || false
|
|
576
|
+
@segments = []
|
|
577
|
+
@gaps = []
|
|
578
|
+
@index = 0
|
|
579
|
+
@total_duration = 0.0
|
|
580
|
+
@metadata = {
|
|
581
|
+
'generated_at' => Time.now.iso8601,
|
|
582
|
+
'source_files' => [],
|
|
583
|
+
'total_duration' => 0.0,
|
|
584
|
+
'segment_count' => 0,
|
|
585
|
+
'gap_count' => 0
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
# REQUIREMENTS: Validate input files exist and are readable
|
|
589
|
+
# SEMANTIC TOKENS: VALIDATION, ERROR_HANDLING, FILE_EXISTENCE
|
|
590
|
+
# ARCHITECTURE: Input validation layer before processing
|
|
591
|
+
# IMPLEMENTATION: Check file existence and permissions
|
|
592
|
+
# TEST: Test with missing files, invalid files, permission issues
|
|
593
|
+
|
|
594
|
+
validate_input_files
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
def parse
|
|
598
|
+
# REQUIREMENTS: Parse each input file and extract TEXT specifications
|
|
599
|
+
# SEMANTIC TOKENS: FILE_PROCESSING, TEXT_EXTRACT, TIMING_PARSING
|
|
600
|
+
# ARCHITECTURE: Main parsing loop with file iteration
|
|
601
|
+
# IMPLEMENTATION: Process each file and extract text segments
|
|
602
|
+
# TEST: Test parsing with various file formats and content
|
|
603
|
+
# CROSS-REFERENCE: See REQUIREMENTS UPDATE for parsing requirements
|
|
604
|
+
# CROSS-REFERENCE: See SEMANTIC TOKENS UPDATE for parsing tokens
|
|
605
|
+
# CROSS-REFERENCE: See ARCHITECTURE UPDATE for parsing architecture
|
|
606
|
+
# CROSS-REFERENCE: See IMPLEMENTATION UPDATE for parsing implementation
|
|
607
|
+
# CROSS-REFERENCE: See TEST UPDATES NEEDED for parsing testing
|
|
608
|
+
# CROSS-REFERENCE: See CODE UPDATES for parsing code changes
|
|
609
|
+
|
|
610
|
+
puts "# INFO: Starting parsing of #{@input_files.length} file(s)" unless @quiet_mode
|
|
611
|
+
|
|
612
|
+
# Check if we have any valid files to process
|
|
613
|
+
if @input_files.empty?
|
|
614
|
+
puts "# ERROR: No valid input files found"
|
|
615
|
+
raise RuntimeError, "No valid input files found"
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
@input_files.each_with_index do |file_path, file_index|
|
|
619
|
+
# REQUIREMENTS: Process each file individually with proper error handling
|
|
620
|
+
# SEMANTIC TOKENS: FILE_ITERATION, ERROR_HANDLING, CONTINUE_ON_ERROR
|
|
621
|
+
# ARCHITECTURE: File processing loop with error recovery
|
|
622
|
+
# IMPLEMENTATION: Process file with error handling and continuation
|
|
623
|
+
# TEST: Test file processing with various error conditions
|
|
624
|
+
|
|
625
|
+
# REQUIREMENTS: Detect file type and process accordingly
|
|
626
|
+
# SEMANTIC TOKENS: FILE_TYPE_DETECTION, SOURCE_FILE_PROCESSING, ANIMATION_PROCESSING
|
|
627
|
+
# ARCHITECTURE: File type detection and routing architecture
|
|
628
|
+
# IMPLEMENTATION: Route files to appropriate processing methods
|
|
629
|
+
# TEST: Test file type detection and routing
|
|
630
|
+
file_extension = File.extname(file_path).downcase
|
|
631
|
+
|
|
632
|
+
case file_extension
|
|
633
|
+
when '.md', '.markdown', '.txt', '.html', '.htm'
|
|
634
|
+
# Process as source file
|
|
635
|
+
process_source_file(file_path, file_index)
|
|
636
|
+
when '.anim'
|
|
637
|
+
# Process as animation file
|
|
638
|
+
process_file(file_path, file_index)
|
|
639
|
+
else
|
|
640
|
+
# Default to source file processing
|
|
641
|
+
process_source_file(file_path, file_index)
|
|
642
|
+
end
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
# REQUIREMENTS: Calculate timing gaps and finalize metadata
|
|
646
|
+
# SEMANTIC TOKENS: TIMING_CALC, GAP_DETECTION, METADATA_FINALIZATION
|
|
647
|
+
# ARCHITECTURE: Post-processing phase for timing and metadata
|
|
648
|
+
# IMPLEMENTATION: Calculate gaps and update metadata
|
|
649
|
+
# TEST: Test timing calculations and gap detection accuracy
|
|
650
|
+
|
|
651
|
+
calculate_gaps
|
|
652
|
+
finalize_metadata
|
|
653
|
+
|
|
654
|
+
puts "# INFO: Parsing complete. Found #{@segments.length} text segments" unless @quiet_mode
|
|
655
|
+
|
|
656
|
+
# Return segments for testing and programmatic access
|
|
657
|
+
@segments
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
def generate_yaml
|
|
661
|
+
# REQUIREMENTS: Generate YAML output with audio segments and timing
|
|
662
|
+
# SEMANTIC TOKENS: YAML_GEN, OUTPUT_FORMATTING, DATA_STRUCTURE
|
|
663
|
+
# ARCHITECTURE: YAML generation with proper structure
|
|
664
|
+
# IMPLEMENTATION: Create YAML with segments, gaps, and metadata
|
|
665
|
+
# TEST: Test YAML output format and structure validation
|
|
666
|
+
# CROSS-REFERENCE: See REQUIREMENTS UPDATE for YAML generation requirements
|
|
667
|
+
# CROSS-REFERENCE: See SEMANTIC TOKENS UPDATE for YAML structure tokens
|
|
668
|
+
# CROSS-REFERENCE: See ARCHITECTURE UPDATE for YAML generation architecture
|
|
669
|
+
# CROSS-REFERENCE: See IMPLEMENTATION UPDATE for YAML generation implementation
|
|
670
|
+
# CROSS-REFERENCE: See TEST UPDATES NEEDED for YAML testing
|
|
671
|
+
# CROSS-REFERENCE: See CODE UPDATES for YAML generation code changes
|
|
672
|
+
|
|
673
|
+
yaml_data = {
|
|
674
|
+
'metadata' => @metadata,
|
|
675
|
+
'audio_segments' => @segments,
|
|
676
|
+
'gaps' => @gaps
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
# REQUIREMENTS: Output YAML to stdout for pipeline processing
|
|
680
|
+
# SEMANTIC TOKENS: OUTPUT_STREAM, YAML_SERIALIZATION, PIPELINE_INTEGRATION
|
|
681
|
+
# ARCHITECTURE: Standard output for pipeline integration
|
|
682
|
+
# IMPLEMENTATION: Generate and output YAML to stdout
|
|
683
|
+
# TEST: Test YAML output format and pipeline integration
|
|
684
|
+
|
|
685
|
+
yaml_output = yaml_data.to_yaml
|
|
686
|
+
puts yaml_output
|
|
687
|
+
yaml_output
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
private
|
|
691
|
+
|
|
692
|
+
def validate_input_files
|
|
693
|
+
# REQUIREMENTS: Validate all input files exist and are readable
|
|
694
|
+
# SEMANTIC TOKENS: VALIDATION, ERROR_HANDLING, FILE_EXISTENCE_CHECK
|
|
695
|
+
# ARCHITECTURE: Input validation layer before processing
|
|
696
|
+
# IMPLEMENTATION: Check each file and handle missing files gracefully
|
|
697
|
+
# TEST: Test with missing files, invalid files, permission issues
|
|
698
|
+
|
|
699
|
+
if @input_files.empty?
|
|
700
|
+
puts "# ERROR: No input files provided"
|
|
701
|
+
exit 1
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
@input_files.each do |file_path|
|
|
705
|
+
unless File.exist?(file_path)
|
|
706
|
+
puts "# WARNING: File '#{file_path}' not found, skipping"
|
|
707
|
+
@input_files.delete(file_path)
|
|
708
|
+
end
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
if @input_files.empty?
|
|
712
|
+
puts "# ERROR: No valid input files found"
|
|
713
|
+
exit 1
|
|
714
|
+
end
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
def process_file(file_path, file_index)
|
|
718
|
+
# REQUIREMENTS: Process individual file and extract text segments
|
|
719
|
+
# SEMANTIC TOKENS: FILE_PROCESSING, LINE_PARSING, SEGMENT_EXTRACT
|
|
720
|
+
# ARCHITECTURE: File processing with line-by-line parsing
|
|
721
|
+
# IMPLEMENTATION: Read file and parse each line for TEXT specifications
|
|
722
|
+
# TEST: Test file processing with various content and formats
|
|
723
|
+
|
|
724
|
+
puts "# INFO: Processing file: #{file_path}"
|
|
725
|
+
puts "# INFO: File size: #{File.size(file_path)} bytes"
|
|
726
|
+
|
|
727
|
+
# REQUIREMENTS: Add file separator between different files
|
|
728
|
+
# SEMANTIC TOKENS: FILE_SEPARATOR, OUTPUT_FORMATTING, VISUAL_SEPARATION
|
|
729
|
+
# ARCHITECTURE: File separator logic for output clarity
|
|
730
|
+
# IMPLEMENTATION: Add separator comments between files
|
|
731
|
+
# TEST: Test file separator output and formatting
|
|
732
|
+
|
|
733
|
+
if file_index > 0
|
|
734
|
+
puts ""
|
|
735
|
+
puts "# --- File separator ---"
|
|
736
|
+
puts ""
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
File.readlines(file_path).each_with_index do |line, line_number|
|
|
740
|
+
# REQUIREMENTS: Parse each line for TEXT specifications
|
|
741
|
+
# SEMANTIC TOKENS: LINE_PARSING, TEXT_EXTRACT, TIMING_PARSING
|
|
742
|
+
# ARCHITECTURE: Line-by-line processing with regex matching
|
|
743
|
+
# IMPLEMENTATION: Parse line and extract text segments
|
|
744
|
+
# TEST: Test line parsing with various formats and edge cases
|
|
745
|
+
|
|
746
|
+
parse_line(@index, line.strip, file_path, line_number + 1)
|
|
747
|
+
end
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
# REQUIREMENTS: Process source files (markdown, text, HTML) to extract text content
|
|
751
|
+
# SEMANTIC TOKENS: SOURCE_FILE_PROCESSING, TEXT_EXTRACTION, CONTENT_PARSING
|
|
752
|
+
# ARCHITECTURE: Source file processing architecture
|
|
753
|
+
# IMPLEMENTATION: Process different file types to extract text content
|
|
754
|
+
# TEST: Test source file processing with various formats
|
|
755
|
+
def process_source_file(file_path, file_index)
|
|
756
|
+
# REQUIREMENTS: Detect file type and process accordingly
|
|
757
|
+
# SEMANTIC TOKENS: FILE_TYPE_DETECTION, SOURCE_FILE_PROCESSING, CONTENT_EXTRACTION
|
|
758
|
+
# ARCHITECTURE: Source file processing architecture
|
|
759
|
+
# IMPLEMENTATION: Detect file type and extract text content
|
|
760
|
+
# TEST: Test source file processing with various file formats
|
|
761
|
+
file_extension = File.extname(file_path).downcase
|
|
762
|
+
|
|
763
|
+
puts "# INFO: Processing source file: #{file_path}"
|
|
764
|
+
puts "# INFO: File size: #{File.size(file_path)} bytes"
|
|
765
|
+
puts "# INFO: File type: #{file_extension}"
|
|
766
|
+
|
|
767
|
+
# Add file separator between different files
|
|
768
|
+
if file_index > 0
|
|
769
|
+
puts ""
|
|
770
|
+
puts "# --- Source file separator ---"
|
|
771
|
+
puts ""
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
case file_extension
|
|
775
|
+
when '.md', '.markdown'
|
|
776
|
+
process_markdown_file(file_path)
|
|
777
|
+
when '.txt'
|
|
778
|
+
process_text_file(file_path)
|
|
779
|
+
when '.html', '.htm'
|
|
780
|
+
process_html_file(file_path)
|
|
781
|
+
else
|
|
782
|
+
# Default to text file processing
|
|
783
|
+
process_text_file(file_path)
|
|
784
|
+
end
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
# REQUIREMENTS: Process markdown files to extract text content
|
|
788
|
+
# SEMANTIC TOKENS: MARKDOWN_PROCESSING, TEXT_EXTRACTION, CONTENT_PARSING
|
|
789
|
+
# ARCHITECTURE: Markdown file processing architecture
|
|
790
|
+
# IMPLEMENTATION: Extract text content from markdown files
|
|
791
|
+
# TEST: Test markdown file processing and text extraction
|
|
792
|
+
def process_markdown_file(file_path)
|
|
793
|
+
# REQUIREMENTS: Read markdown content and extract text
|
|
794
|
+
# SEMANTIC TOKENS: MARKDOWN_READING, TEXT_EXTRACTION, CONTENT_PROCESSING
|
|
795
|
+
# ARCHITECTURE: Markdown content processing
|
|
796
|
+
# IMPLEMENTATION: Extract text from markdown content
|
|
797
|
+
# TEST: Test markdown text extraction
|
|
798
|
+
begin
|
|
799
|
+
content = File.read(file_path, encoding: 'UTF-8')
|
|
800
|
+
rescue Encoding::InvalidByteSequenceError, ArgumentError
|
|
801
|
+
# Fallback to reading with binary encoding and force UTF-8
|
|
802
|
+
content = File.read(file_path, encoding: 'BINARY').force_encoding('UTF-8')
|
|
803
|
+
rescue => e
|
|
804
|
+
puts "# WARNING: Error reading file #{file_path}: #{e.message}"
|
|
805
|
+
content = ""
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
# Simple markdown text extraction (remove markdown syntax)
|
|
809
|
+
text_content = content.gsub(/^#+\s*/, '') # Remove headers
|
|
810
|
+
.gsub(/\*\*(.*?)\*\*/, '\1') # Remove bold
|
|
811
|
+
.gsub(/\*(.*?)\*/, '\1') # Remove italic
|
|
812
|
+
.gsub(/`(.*?)`/, '\1') # Remove code
|
|
813
|
+
.gsub(/\[([^\]]+)\]\([^)]+\)/, '\1') # Remove links
|
|
814
|
+
.gsub(/^\s*[-*+]\s*/, '') # Remove list markers
|
|
815
|
+
.gsub(/^\s*\d+\.\s*/, '') # Remove numbered lists
|
|
816
|
+
.gsub(/^\s*$/, '') # Remove empty lines
|
|
817
|
+
.strip
|
|
818
|
+
|
|
819
|
+
# Split into paragraphs and create segments
|
|
820
|
+
paragraphs = text_content.split(/\n\s*\n/).reject(&:empty?)
|
|
821
|
+
|
|
822
|
+
paragraphs.each_with_index do |paragraph, index|
|
|
823
|
+
next if paragraph.strip.empty?
|
|
824
|
+
|
|
825
|
+
# Create text segment with timing
|
|
826
|
+
start_time = index * 2.0 # 2 seconds per paragraph
|
|
827
|
+
end_time = start_time + 2.0
|
|
828
|
+
|
|
829
|
+
create_text_segment(@index, start_time, end_time, 'black', paragraph.strip, file_path, index + 1)
|
|
830
|
+
end
|
|
831
|
+
end
|
|
832
|
+
|
|
833
|
+
# REQUIREMENTS: Process plain text files to extract text content
|
|
834
|
+
# SEMANTIC TOKENS: TEXT_FILE_PROCESSING, PLAIN_TEXT_EXTRACTION, CONTENT_PARSING
|
|
835
|
+
# ARCHITECTURE: Text file processing architecture
|
|
836
|
+
# IMPLEMENTATION: Extract text content from plain text files
|
|
837
|
+
# TEST: Test text file processing and text extraction
|
|
838
|
+
def process_text_file(file_path)
|
|
839
|
+
# REQUIREMENTS: Read text content and extract paragraphs
|
|
840
|
+
# SEMANTIC TOKENS: TEXT_READING, PARAGRAPH_EXTRACTION, CONTENT_PROCESSING
|
|
841
|
+
# ARCHITECTURE: Text content processing
|
|
842
|
+
# IMPLEMENTATION: Extract paragraphs from text content
|
|
843
|
+
# TEST: Test text paragraph extraction
|
|
844
|
+
begin
|
|
845
|
+
content = File.read(file_path, encoding: 'UTF-8')
|
|
846
|
+
rescue Encoding::InvalidByteSequenceError, ArgumentError
|
|
847
|
+
# Fallback to reading with binary encoding and force UTF-8
|
|
848
|
+
content = File.read(file_path, encoding: 'BINARY').force_encoding('UTF-8')
|
|
849
|
+
rescue => e
|
|
850
|
+
puts "# WARNING: Error reading file #{file_path}: #{e.message}"
|
|
851
|
+
content = ""
|
|
852
|
+
end
|
|
853
|
+
|
|
854
|
+
# Split into paragraphs
|
|
855
|
+
paragraphs = content.split(/\n\s*\n/).reject(&:empty?)
|
|
856
|
+
|
|
857
|
+
paragraphs.each_with_index do |paragraph, index|
|
|
858
|
+
next if paragraph.strip.empty?
|
|
859
|
+
|
|
860
|
+
# Create text segment with timing
|
|
861
|
+
start_time = index * 2.0 # 2 seconds per paragraph
|
|
862
|
+
end_time = start_time + 2.0
|
|
863
|
+
|
|
864
|
+
create_text_segment(@index, start_time, end_time, 'black', paragraph.strip, file_path, index + 1)
|
|
865
|
+
end
|
|
866
|
+
end
|
|
867
|
+
|
|
868
|
+
# REQUIREMENTS: Process HTML files to extract text content
|
|
869
|
+
# SEMANTIC TOKENS: HTML_PROCESSING, HTML_TEXT_EXTRACTION, CONTENT_PARSING
|
|
870
|
+
# ARCHITECTURE: HTML file processing architecture
|
|
871
|
+
# IMPLEMENTATION: Extract text content from HTML files
|
|
872
|
+
# TEST: Test HTML file processing and text extraction
|
|
873
|
+
def process_html_file(file_path)
|
|
874
|
+
# REQUIREMENTS: Read HTML content and extract text
|
|
875
|
+
# SEMANTIC TOKENS: HTML_READING, TEXT_EXTRACTION, CONTENT_PROCESSING
|
|
876
|
+
# ARCHITECTURE: HTML content processing
|
|
877
|
+
# IMPLEMENTATION: Extract text from HTML content
|
|
878
|
+
# TEST: Test HTML text extraction
|
|
879
|
+
begin
|
|
880
|
+
content = File.read(file_path, encoding: 'UTF-8')
|
|
881
|
+
rescue Encoding::InvalidByteSequenceError, ArgumentError
|
|
882
|
+
# Fallback to reading with binary encoding and force UTF-8
|
|
883
|
+
content = File.read(file_path, encoding: 'BINARY').force_encoding('UTF-8')
|
|
884
|
+
rescue => e
|
|
885
|
+
puts "# WARNING: Error reading file #{file_path}: #{e.message}"
|
|
886
|
+
content = ""
|
|
887
|
+
end
|
|
888
|
+
|
|
889
|
+
# Simple HTML text extraction (remove HTML tags)
|
|
890
|
+
text_content = content.gsub(/<[^>]+>/, '') # Remove HTML tags
|
|
891
|
+
.gsub(/</, '<') # Decode HTML entities
|
|
892
|
+
.gsub(/>/, '>')
|
|
893
|
+
.gsub(/&/, '&')
|
|
894
|
+
.gsub(/"/, '"')
|
|
895
|
+
.gsub(/'/, "'")
|
|
896
|
+
.gsub(/\s+/, ' ') # Normalize whitespace
|
|
897
|
+
.strip
|
|
898
|
+
|
|
899
|
+
# Split into sentences and create segments
|
|
900
|
+
sentences = text_content.split(/[.!?]+/).reject(&:empty?)
|
|
901
|
+
|
|
902
|
+
sentences.each_with_index do |sentence, index|
|
|
903
|
+
next if sentence.strip.empty?
|
|
904
|
+
|
|
905
|
+
# Create text segment with timing
|
|
906
|
+
start_time = index * 3.0 # 3 seconds per sentence
|
|
907
|
+
end_time = start_time + 3.0
|
|
908
|
+
|
|
909
|
+
create_text_segment(@index, start_time, end_time, 'black', sentence.strip, file_path, index + 1)
|
|
910
|
+
end
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
# REQUIREMENTS: Create text segment from extracted content
|
|
914
|
+
# SEMANTIC TOKENS: TEXT_SEGMENT_CREATION, SEGMENT_GENERATION, CONTENT_SEGMENTATION
|
|
915
|
+
# ARCHITECTURE: Text segment creation architecture
|
|
916
|
+
# IMPLEMENTATION: Create text segment with timing and metadata
|
|
917
|
+
# TEST: Test text segment creation
|
|
918
|
+
def create_text_segment(text, start_time, end_time, color, source_file, line_number)
|
|
919
|
+
# REQUIREMENTS: Create text segment with proper metadata
|
|
920
|
+
# SEMANTIC TOKENS: SEGMENT_METADATA, TIMING_INFORMATION, SOURCE_TRACKING
|
|
921
|
+
# ARCHITECTURE: Text segment metadata architecture
|
|
922
|
+
# IMPLEMENTATION: Create segment with comprehensive metadata
|
|
923
|
+
# TEST: Test text segment metadata creation
|
|
924
|
+
@index += 1
|
|
925
|
+
|
|
926
|
+
segment = {
|
|
927
|
+
'id' => "text_#{@index}",
|
|
928
|
+
'start_time' => start_time,
|
|
929
|
+
'end_time' => end_time,
|
|
930
|
+
'duration' => end_time - start_time,
|
|
931
|
+
'text' => text,
|
|
932
|
+
'voice_settings' => {
|
|
933
|
+
'color' => color,
|
|
934
|
+
'speed' => calculate_speech_speed(text, end_time - start_time),
|
|
935
|
+
'volume' => 0.8,
|
|
936
|
+
'pitch' => calculate_pitch_from_color(color)
|
|
937
|
+
},
|
|
938
|
+
'file_path' => "audio/text_#{@index}.wav",
|
|
939
|
+
'source' => {
|
|
940
|
+
'file' => source_file,
|
|
941
|
+
'line' => line_number
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
@segments << segment
|
|
946
|
+
|
|
947
|
+
puts "# INFO: Created segment #{@index}: '#{text}' (#{start_time}s-#{end_time}s, #{color})" unless @quiet_mode
|
|
948
|
+
end
|
|
949
|
+
|
|
950
|
+
def parse_line(index, line, file_path, line_number)
|
|
951
|
+
# REQUIREMENTS: Parse individual line for TEXT specifications with timing
|
|
952
|
+
# SEMANTIC TOKENS: LINE_PARSING, REGEX_MATCHING, TEXT_EXTRACT
|
|
953
|
+
# ARCHITECTURE: Line parsing with regex pattern matching
|
|
954
|
+
# IMPLEMENTATION: Extract text content, timing, and properties
|
|
955
|
+
# TEST: Test line parsing with all TEXT format variations
|
|
956
|
+
# CROSS-REFERENCE: See REQUIREMENTS UPDATE for line parsing requirements
|
|
957
|
+
# CROSS-REFERENCE: See SEMANTIC TOKENS UPDATE for line parsing tokens
|
|
958
|
+
# CROSS-REFERENCE: See ARCHITECTURE UPDATE for line parsing architecture
|
|
959
|
+
# CROSS-REFERENCE: See IMPLEMENTATION UPDATE for line parsing implementation
|
|
960
|
+
# CROSS-REFERENCE: See TEST UPDATES NEEDED for line parsing testing
|
|
961
|
+
# CROSS-REFERENCE: See CODE UPDATES for line parsing code changes
|
|
962
|
+
|
|
963
|
+
# REQUIREMENTS: Skip empty lines and comments
|
|
964
|
+
# SEMANTIC TOKENS: LINE_FILTERING, COMMENT_HANDLING, EMPTY_LINE_SKIP
|
|
965
|
+
# ARCHITECTURE: Input filtering before processing
|
|
966
|
+
# IMPLEMENTATION: Skip irrelevant lines
|
|
967
|
+
# TEST: Test filtering with various line types
|
|
968
|
+
|
|
969
|
+
return if line.empty? || line.start_with?('#')
|
|
970
|
+
|
|
971
|
+
puts "# DEBUG: Processing line #{line_number} in #{file_path}: #{line}"
|
|
972
|
+
|
|
973
|
+
# REQUIREMENTS: Handle BOX specifications first to establish timing context
|
|
974
|
+
# SEMANTIC TOKENS: BOX_PARSING, TIMING_CONTEXT, CONTEXT_ESTABLISHMENT
|
|
975
|
+
# ARCHITECTURE: BOX processing before TEXT for timing inheritance
|
|
976
|
+
# IMPLEMENTATION: Parse BOX to establish timing context for subsequent TEXT
|
|
977
|
+
# TEST: Test BOX parsing and context establishment
|
|
978
|
+
|
|
979
|
+
if line.match(/BOX@\([[:space:]]*([-0-9\.]+)\.\.[[:space:]]*([-0-9\.]+)\)/)
|
|
980
|
+
track_box_timing($1, $2)
|
|
981
|
+
end
|
|
982
|
+
|
|
983
|
+
# REQUIREMENTS: Extract TEXT specifications with all format variations
|
|
984
|
+
# SEMANTIC TOKENS: TEXT_PARSING, REGEX_PATTERNS, FORMAT_VARIATIONS
|
|
985
|
+
# ARCHITECTURE: Pattern matching for different TEXT formats
|
|
986
|
+
# IMPLEMENTATION: Parse TEXT with timing and color information
|
|
987
|
+
# TEST: Test all TEXT format variations and edge cases
|
|
988
|
+
|
|
989
|
+
# TEXT@(START..END)=COLOR"text" - explicit timing and color
|
|
990
|
+
if line.match(/TEXT@\([[:space:]]*([-0-9\.]+)\.\.[[:space:]]*([-0-9\.]+)\)=[[:space:]]*([^"]+)"([^"]+)"/)
|
|
991
|
+
extract_text_with_timing_and_color(index, $1, $2, $3, $4, file_path, line_number)
|
|
992
|
+
|
|
993
|
+
# TEXT@=COLOR"text" - color only, inherit timing from current BOX context
|
|
994
|
+
elsif line.match(/TEXT@[[:space:]]*=[[:space:]]*([^"]+)"([^"]+)"/)
|
|
995
|
+
extract_text_with_color_only(index, $1, $2, file_path, line_number)
|
|
996
|
+
|
|
997
|
+
# TEXT@(START..END)"text" - timing only, inherit color from previous
|
|
998
|
+
elsif line.match(/TEXT@\([[:space:]]*([-0-9\.]+)\.\.[[:space:]]*([-0-9\.]+)\)"([^"]+)"/)
|
|
999
|
+
extract_text_with_timing_only(index, $1, $2, $3, file_path, line_number)
|
|
1000
|
+
|
|
1001
|
+
# TEXT@"text" - no timing or color, inherit both from BOX context and previous TEXT
|
|
1002
|
+
elsif line.match(/TEXT@[[:space:]]*"([^"]+)"/)
|
|
1003
|
+
extract_text_only(index, $1, file_path, line_number)
|
|
1004
|
+
end
|
|
1005
|
+
end
|
|
1006
|
+
|
|
1007
|
+
def extract_text_with_timing_and_color(index, start_time, end_time, color, text, file_path, line_number)
|
|
1008
|
+
# REQUIREMENTS: Extract text with explicit timing and color
|
|
1009
|
+
# SEMANTIC TOKENS: TEXT_EXTRACT, TIMING_PARSING, COLOR_PARSING
|
|
1010
|
+
# ARCHITECTURE: Complete text segment extraction
|
|
1011
|
+
# IMPLEMENTATION: Parse all text properties and create segment
|
|
1012
|
+
# TEST: Test extraction with various timing and color values
|
|
1013
|
+
|
|
1014
|
+
create_text_segment(index, start_time.to_f, end_time.to_f, color, text, file_path, line_number)
|
|
1015
|
+
end
|
|
1016
|
+
|
|
1017
|
+
def extract_text_with_color_only(index, color, text, file_path, line_number)
|
|
1018
|
+
# REQUIREMENTS: Extract text with color only, inherit timing from current BOX context
|
|
1019
|
+
# SEMANTIC TOKENS: TEXT_EXTRACT, COLOR_PARSING, BOX_TIMING_INHERITANCE
|
|
1020
|
+
# ARCHITECTURE: Partial text segment extraction with BOX timing inheritance
|
|
1021
|
+
# IMPLEMENTATION: Use current BOX timing context for inheritance
|
|
1022
|
+
# TEST: Test color extraction and BOX timing inheritance
|
|
1023
|
+
|
|
1024
|
+
start_time, end_time = get_inherited_timing
|
|
1025
|
+
create_text_segment(index, start_time, end_time, color, text, file_path, line_number)
|
|
1026
|
+
end
|
|
1027
|
+
|
|
1028
|
+
def extract_text_with_timing_only(index, start_time, end_time, text, file_path, line_number)
|
|
1029
|
+
# REQUIREMENTS: Extract text with timing only, inherit color from previous
|
|
1030
|
+
# SEMANTIC TOKENS: TEXT_EXTRACT, TIMING_PARSING, COLOR_INHERITANCE
|
|
1031
|
+
# ARCHITECTURE: Partial text segment extraction with inheritance
|
|
1032
|
+
# IMPLEMENTATION: Use inherited color from previous text
|
|
1033
|
+
# TEST: Test timing extraction and color inheritance
|
|
1034
|
+
|
|
1035
|
+
color = get_inherited_color
|
|
1036
|
+
create_text_segment(index, start_time.to_f, end_time.to_f, color, text, file_path, line_number)
|
|
1037
|
+
end
|
|
1038
|
+
|
|
1039
|
+
def extract_text_only(index, text, file_path, line_number)
|
|
1040
|
+
# REQUIREMENTS: Extract text only, inherit timing from BOX and color from previous TEXT
|
|
1041
|
+
# SEMANTIC TOKENS: TEXT_EXTRACT, BOX_TIMING_INHERITANCE, COLOR_INHERITANCE
|
|
1042
|
+
# ARCHITECTURE: Minimal text segment extraction with mixed inheritance
|
|
1043
|
+
# IMPLEMENTATION: Use BOX timing and previous TEXT color
|
|
1044
|
+
# TEST: Test text extraction with mixed inheritance
|
|
1045
|
+
|
|
1046
|
+
start_time, end_time = get_inherited_timing
|
|
1047
|
+
color = get_inherited_color
|
|
1048
|
+
create_text_segment(index, start_time, end_time, color, text, file_path, line_number)
|
|
1049
|
+
end
|
|
1050
|
+
|
|
1051
|
+
def create_text_segment(index, start_time, end_time, color, text, file_path, line_number)
|
|
1052
|
+
# REQUIREMENTS: Create text segment with all properties for TTS
|
|
1053
|
+
# SEMANTIC TOKENS: SEGMENT_CREATE, TTS_PROPERTIES, AUDIO_METADATA
|
|
1054
|
+
# ARCHITECTURE: Text segment creation with TTS-specific properties
|
|
1055
|
+
# IMPLEMENTATION: Create segment with timing, voice, and audio properties
|
|
1056
|
+
# TEST: Test segment creation with various properties and values
|
|
1057
|
+
|
|
1058
|
+
@index += 1
|
|
1059
|
+
duration = end_time - start_time
|
|
1060
|
+
|
|
1061
|
+
segment = {
|
|
1062
|
+
'id' => "text_#{@index}",
|
|
1063
|
+
'start_time' => start_time,
|
|
1064
|
+
'end_time' => end_time,
|
|
1065
|
+
'duration' => duration,
|
|
1066
|
+
'text' => text,
|
|
1067
|
+
'voice_settings' => {
|
|
1068
|
+
'color' => color,
|
|
1069
|
+
'speed' => calculate_speech_speed(text, duration),
|
|
1070
|
+
'volume' => 0.8,
|
|
1071
|
+
'pitch' => calculate_pitch_from_color(color)
|
|
1072
|
+
},
|
|
1073
|
+
'file_path' => "audio/text_#{@index}.wav",
|
|
1074
|
+
'source' => {
|
|
1075
|
+
'file' => file_path,
|
|
1076
|
+
'line' => line_number
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
@segments << segment
|
|
1081
|
+
@total_duration = [@total_duration, end_time].max
|
|
1082
|
+
|
|
1083
|
+
puts "# INFO: Created segment #{index}: '#{text}' (#{start_time}s-#{end_time}s, #{color})"
|
|
1084
|
+
end
|
|
1085
|
+
|
|
1086
|
+
def track_box_timing(start_time, end_time)
|
|
1087
|
+
# REQUIREMENTS: Track BOX timing for inheritance by subsequent TEXT
|
|
1088
|
+
# SEMANTIC TOKENS: TIMING_TRACKING, CONTEXT_MANAGEMENT, INHERITANCE_SUPPORT
|
|
1089
|
+
# ARCHITECTURE: Context tracking for timing inheritance
|
|
1090
|
+
# IMPLEMENTATION: Store timing context for inheritance
|
|
1091
|
+
# TEST: Test timing tracking and inheritance accuracy
|
|
1092
|
+
|
|
1093
|
+
@current_box_start = start_time.to_f
|
|
1094
|
+
@current_box_end = end_time.to_f
|
|
1095
|
+
end
|
|
1096
|
+
|
|
1097
|
+
def get_inherited_timing
|
|
1098
|
+
# REQUIREMENTS: Get timing from current BOX context for inheritance
|
|
1099
|
+
# SEMANTIC TOKENS: TIMING_INHERITANCE, CONTEXT_RETRIEVAL, BOX_TIMING_USAGE
|
|
1100
|
+
# ARCHITECTURE: Context retrieval from current BOX timing
|
|
1101
|
+
# IMPLEMENTATION: Return current BOX timing or error if not available
|
|
1102
|
+
# TEST: Test timing inheritance with various BOX contexts
|
|
1103
|
+
|
|
1104
|
+
if @current_box_start && @current_box_end
|
|
1105
|
+
[@current_box_start, @current_box_end]
|
|
1106
|
+
else
|
|
1107
|
+
# REQUIREMENTS: Error if no BOX timing context available for inheritance
|
|
1108
|
+
# SEMANTIC TOKENS: ERROR_HANDLING, CONTEXT_VALIDATION, TIMING_REQUIREMENT
|
|
1109
|
+
# ARCHITECTURE: Error handling for missing timing context
|
|
1110
|
+
# IMPLEMENTATION: Raise error when BOX timing not available
|
|
1111
|
+
# TEST: Test error handling when BOX timing missing
|
|
1112
|
+
|
|
1113
|
+
raise "No BOX timing context available for TEXT inheritance at index #{@index}"
|
|
1114
|
+
end
|
|
1115
|
+
end
|
|
1116
|
+
|
|
1117
|
+
def get_inherited_color
|
|
1118
|
+
# REQUIREMENTS: Get color from previous TEXT for inheritance
|
|
1119
|
+
# SEMANTIC TOKENS: COLOR_INHERITANCE, CONTEXT_RETRIEVAL, FALLBACK_HANDLING
|
|
1120
|
+
# ARCHITECTURE: Context retrieval with fallback handling
|
|
1121
|
+
# IMPLEMENTATION: Return inherited color or default
|
|
1122
|
+
# TEST: Test color inheritance with various contexts
|
|
1123
|
+
|
|
1124
|
+
if @segments.last && @segments.last['voice_settings']['color']
|
|
1125
|
+
@segments.last['voice_settings']['color']
|
|
1126
|
+
else
|
|
1127
|
+
'black' # Default fallback
|
|
1128
|
+
end
|
|
1129
|
+
end
|
|
1130
|
+
|
|
1131
|
+
def calculate_speech_speed(text, duration)
|
|
1132
|
+
# REQUIREMENTS: Calculate appropriate speech speed based on text length and duration
|
|
1133
|
+
# SEMANTIC TOKENS: SPEECH_CALC, TIMING_OPTIMIZATION, SPEED_ADJUSTMENT
|
|
1134
|
+
# ARCHITECTURE: Speech speed calculation algorithm
|
|
1135
|
+
# IMPLEMENTATION: Calculate speed based on text length and available time
|
|
1136
|
+
# TEST: Test speed calculation with various text lengths and durations
|
|
1137
|
+
|
|
1138
|
+
word_count = text.split.length
|
|
1139
|
+
|
|
1140
|
+
# For short texts, use inverse duration to make them faster
|
|
1141
|
+
# For long texts, use words per second
|
|
1142
|
+
if word_count <= 2
|
|
1143
|
+
# Short text: faster for shorter duration
|
|
1144
|
+
base_speed = 2.0 / duration
|
|
1145
|
+
else
|
|
1146
|
+
# Long text: use words per second
|
|
1147
|
+
base_speed = word_count / duration
|
|
1148
|
+
end
|
|
1149
|
+
|
|
1150
|
+
# Apply scaling factor to make differences more pronounced
|
|
1151
|
+
scaled_speed = base_speed * 0.5
|
|
1152
|
+
|
|
1153
|
+
# Normalize to reasonable range (0.5 to 2.0)
|
|
1154
|
+
[0.5, [2.0, scaled_speed].min].max
|
|
1155
|
+
end
|
|
1156
|
+
|
|
1157
|
+
def calculate_pitch_from_color(color)
|
|
1158
|
+
# REQUIREMENTS: Calculate voice pitch based on text color
|
|
1159
|
+
# SEMANTIC TOKENS: PITCH_CALC, COLOR_MAPPING, VOICE_CUSTOMIZATION
|
|
1160
|
+
# ARCHITECTURE: Color-to-pitch mapping algorithm
|
|
1161
|
+
# IMPLEMENTATION: Map colors to pitch values for voice variation
|
|
1162
|
+
# TEST: Test pitch calculation with various colors
|
|
1163
|
+
|
|
1164
|
+
color_pitch_map = {
|
|
1165
|
+
'red' => 1.2,
|
|
1166
|
+
'blue' => 1.0,
|
|
1167
|
+
'green' => 0.9,
|
|
1168
|
+
'yellow' => 1.1,
|
|
1169
|
+
'pink' => 1.3,
|
|
1170
|
+
'violet' => 1.15,
|
|
1171
|
+
'orange' => 1.05,
|
|
1172
|
+
'black' => 1.0
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
color_pitch_map[color.downcase] || 1.0
|
|
1176
|
+
end
|
|
1177
|
+
|
|
1178
|
+
def calculate_gaps
|
|
1179
|
+
# REQUIREMENTS: Calculate silence gaps between text segments
|
|
1180
|
+
# SEMANTIC TOKENS: GAP_CALC, SILENCE_DETECTION, TIMING_ANALYSIS
|
|
1181
|
+
# ARCHITECTURE: Gap calculation algorithm for audio stitching
|
|
1182
|
+
# IMPLEMENTATION: Find gaps between segments and create silence entries
|
|
1183
|
+
# TEST: Test gap calculation with various segment arrangements
|
|
1184
|
+
|
|
1185
|
+
return if @segments.empty?
|
|
1186
|
+
|
|
1187
|
+
# Sort segments by start time
|
|
1188
|
+
sorted_segments = @segments.sort_by { |s| s['start_time'] }
|
|
1189
|
+
|
|
1190
|
+
# Calculate gaps between segments
|
|
1191
|
+
(0...sorted_segments.length - 1).each do |i|
|
|
1192
|
+
current_end = sorted_segments[i]['end_time']
|
|
1193
|
+
next_start = sorted_segments[i + 1]['start_time']
|
|
1194
|
+
|
|
1195
|
+
if next_start > current_end
|
|
1196
|
+
gap_duration = next_start - current_end
|
|
1197
|
+
@gaps << {
|
|
1198
|
+
'start_time' => current_end,
|
|
1199
|
+
'end_time' => next_start,
|
|
1200
|
+
'duration' => gap_duration,
|
|
1201
|
+
'type' => 'silence'
|
|
1202
|
+
}
|
|
1203
|
+
end
|
|
1204
|
+
end
|
|
1205
|
+
|
|
1206
|
+
# Add initial gap if first segment doesn't start at 0
|
|
1207
|
+
if sorted_segments.first['start_time'] > 0
|
|
1208
|
+
@gaps << {
|
|
1209
|
+
'start_time' => 0.0,
|
|
1210
|
+
'end_time' => sorted_segments.first['start_time'],
|
|
1211
|
+
'duration' => sorted_segments.first['start_time'],
|
|
1212
|
+
'type' => 'silence'
|
|
1213
|
+
}
|
|
1214
|
+
end
|
|
1215
|
+
|
|
1216
|
+
# Sort gaps by start time
|
|
1217
|
+
@gaps.sort_by! { |g| g['start_time'] }
|
|
1218
|
+
end
|
|
1219
|
+
|
|
1220
|
+
def finalize_metadata
|
|
1221
|
+
# REQUIREMENTS: Finalize metadata with complete information
|
|
1222
|
+
# SEMANTIC TOKENS: METADATA_FINALIZATION, STATISTICS_CALC, SUMMARY_GEN
|
|
1223
|
+
# ARCHITECTURE: Metadata completion and statistics
|
|
1224
|
+
# IMPLEMENTATION: Update metadata with final statistics
|
|
1225
|
+
# TEST: Test metadata finalization with various data sets
|
|
1226
|
+
|
|
1227
|
+
@metadata['source_files'] = @input_files.dup
|
|
1228
|
+
@metadata['total_duration'] = @total_duration
|
|
1229
|
+
@metadata['segment_count'] = @segments.length
|
|
1230
|
+
@metadata['gap_count'] = @gaps.length
|
|
1231
|
+
@metadata['processing_complete'] = true
|
|
1232
|
+
end
|
|
1233
|
+
end
|
|
1234
|
+
|
|
1235
|
+
# TTS Engine Interface
|
|
1236
|
+
# REQUIREMENTS: Define TTS engine interface with methods: generate_audio(text, voice_settings)
|
|
1237
|
+
# SEMANTIC TOKENS: TTS_ENGINE_IFACE, AUDIO_GEN, VOICE_SETTINGS
|
|
1238
|
+
# ARCHITECTURE: TTS engine abstraction with pluggable backends
|
|
1239
|
+
# IMPLEMENTATION: Abstract base class for TTS engines
|
|
1240
|
+
# TEST: Test TTS engine abstraction and backend selection
|
|
1241
|
+
class TTSEngine
|
|
1242
|
+
# REQUIREMENTS: Initialize TTS engine with configuration
|
|
1243
|
+
# SEMANTIC TOKENS: TTS_ENGINE_INIT, CONFIGURATION
|
|
1244
|
+
# ARCHITECTURE: TTS engine initialization architecture
|
|
1245
|
+
# IMPLEMENTATION: Initialize TTS engine with settings
|
|
1246
|
+
# TEST: Test TTS engine initialization and configuration
|
|
1247
|
+
def initialize(config = {})
|
|
1248
|
+
@config = config
|
|
1249
|
+
@voice_settings = {
|
|
1250
|
+
speed: 1.0,
|
|
1251
|
+
pitch: 1.0,
|
|
1252
|
+
volume: 0.8
|
|
1253
|
+
}
|
|
1254
|
+
end
|
|
1255
|
+
|
|
1256
|
+
# REQUIREMENTS: Generate audio from text with voice settings
|
|
1257
|
+
# SEMANTIC TOKENS: AUDIO_GEN, VOICE_SETTINGS, TEXT_TO_SPEECH
|
|
1258
|
+
# ARCHITECTURE: Audio generation with voice customization
|
|
1259
|
+
# IMPLEMENTATION: Generate audio file from text input
|
|
1260
|
+
# TEST: Test audio generation with various voice settings
|
|
1261
|
+
def generate_audio(text, voice_settings = {})
|
|
1262
|
+
# REQUIREMENTS: Apply voice settings to audio generation
|
|
1263
|
+
# SEMANTIC TOKENS: VOICE_SETTINGS_APP, AUDIO_CUSTOMIZATION
|
|
1264
|
+
# ARCHITECTURE: Voice settings application architecture
|
|
1265
|
+
# IMPLEMENTATION: Apply speed, pitch, volume to generated audio
|
|
1266
|
+
# TEST: Test voice settings application (speed, pitch, volume)
|
|
1267
|
+
settings = @voice_settings.merge(voice_settings)
|
|
1268
|
+
|
|
1269
|
+
# REQUIREMENTS: Generate audio file with specified settings
|
|
1270
|
+
# SEMANTIC TOKENS: AUDIO_FILE_GEN, TTS_PROCESSING
|
|
1271
|
+
# ARCHITECTURE: Audio file generation architecture
|
|
1272
|
+
# IMPLEMENTATION: Create audio file from text with voice settings
|
|
1273
|
+
# TEST: Test audio file generation with different settings
|
|
1274
|
+
raise NotImplementedError, "Subclasses must implement generate_audio"
|
|
1275
|
+
end
|
|
1276
|
+
|
|
1277
|
+
# REQUIREMENTS: Check if TTS engine is available
|
|
1278
|
+
# SEMANTIC TOKENS: TTS_ENGINE_AVAIL, SYSTEM_CHECK
|
|
1279
|
+
# ARCHITECTURE: TTS engine availability checking
|
|
1280
|
+
# IMPLEMENTATION: Verify TTS engine is installed and working
|
|
1281
|
+
# TEST: Test TTS engine availability checking
|
|
1282
|
+
def available?
|
|
1283
|
+
raise NotImplementedError, "Subclasses must implement available?"
|
|
1284
|
+
end
|
|
1285
|
+
|
|
1286
|
+
# REQUIREMENTS: Get supported audio formats
|
|
1287
|
+
# SEMANTIC TOKENS: AUDIO_FORMAT_SUPPORT, FORMAT_LISTING
|
|
1288
|
+
# ARCHITECTURE: Audio format support architecture
|
|
1289
|
+
# IMPLEMENTATION: Return list of supported audio formats
|
|
1290
|
+
# TEST: Test audio format support (WAV, MP3)
|
|
1291
|
+
def supported_formats
|
|
1292
|
+
['wav']
|
|
1293
|
+
end
|
|
1294
|
+
|
|
1295
|
+
# REQUIREMENTS: Get TTS engine name
|
|
1296
|
+
# SEMANTIC TOKENS: TTS_ENGINE_ID, ENGINE_NAME
|
|
1297
|
+
# ARCHITECTURE: TTS engine identification
|
|
1298
|
+
# IMPLEMENTATION: Return human-readable engine name
|
|
1299
|
+
# TEST: Test TTS engine identification
|
|
1300
|
+
def name
|
|
1301
|
+
self.class.name
|
|
1302
|
+
end
|
|
1303
|
+
end
|
|
1304
|
+
|
|
1305
|
+
# System TTS Engine Implementation
|
|
1306
|
+
# REQUIREMENTS: Implement system TTS backend (espeak, say, festival)
|
|
1307
|
+
# SEMANTIC TOKENS: SYSTEM_TTS_BACKEND, SYSTEM_INTEGRATION
|
|
1308
|
+
# ARCHITECTURE: System TTS backend with command-line integration
|
|
1309
|
+
# IMPLEMENTATION: Use system TTS tools for audio generation
|
|
1310
|
+
# TEST: Test system TTS backend functionality
|
|
1311
|
+
class SystemTTSEngine < TTSEngine
|
|
1312
|
+
# REQUIREMENTS: Initialize system TTS engine with backend selection
|
|
1313
|
+
# SEMANTIC TOKENS: SYSTEM_TTS_INIT, BACKEND_SELECTION
|
|
1314
|
+
# ARCHITECTURE: System TTS initialization architecture
|
|
1315
|
+
# IMPLEMENTATION: Initialize with specific TTS backend
|
|
1316
|
+
# TEST: Test system TTS engine initialization
|
|
1317
|
+
def initialize(config = {})
|
|
1318
|
+
super(config)
|
|
1319
|
+
@backend = config[:backend] || detect_available_backend
|
|
1320
|
+
@temp_dir = config[:temp_dir] || Dir.mktmpdir
|
|
1321
|
+
end
|
|
1322
|
+
|
|
1323
|
+
# REQUIREMENTS: Generate audio using system TTS tools
|
|
1324
|
+
# SEMANTIC TOKENS: SYSTEM_AUDIO_GEN, COMMAND_EXECUTION
|
|
1325
|
+
# ARCHITECTURE: System command execution for audio generation
|
|
1326
|
+
# IMPLEMENTATION: Execute system TTS commands with voice settings
|
|
1327
|
+
# TEST: Test system audio generation with voice settings
|
|
1328
|
+
def generate_audio(text, voice_settings = {})
|
|
1329
|
+
# REQUIREMENTS: Apply voice settings to system TTS commands
|
|
1330
|
+
# SEMANTIC TOKENS: VOICE_SETTINGS_APP, COMMAND_PARAMETERS
|
|
1331
|
+
# ARCHITECTURE: Voice settings to command parameter mapping
|
|
1332
|
+
# IMPLEMENTATION: Convert voice settings to TTS command parameters
|
|
1333
|
+
# TEST: Test voice settings application to system commands
|
|
1334
|
+
settings = @voice_settings.merge(voice_settings)
|
|
1335
|
+
|
|
1336
|
+
# REQUIREMENTS: Generate temporary audio file
|
|
1337
|
+
# SEMANTIC TOKENS: TEMP_FILE_GEN, AUDIO_OUTPUT
|
|
1338
|
+
# ARCHITECTURE: Temporary file management for audio generation
|
|
1339
|
+
# IMPLEMENTATION: Create temporary audio file with unique name
|
|
1340
|
+
# TEST: Test temporary audio file generation
|
|
1341
|
+
temp_file = File.join(@temp_dir, "tts_#{Time.now.to_i}_#{rand(1000)}.aiff")
|
|
1342
|
+
|
|
1343
|
+
# REQUIREMENTS: Execute system TTS command with voice settings
|
|
1344
|
+
# SEMANTIC TOKENS: SYSTEM_COMMAND_EXECUTION, TTS_PROCESSING
|
|
1345
|
+
# ARCHITECTURE: System command execution architecture
|
|
1346
|
+
# IMPLEMENTATION: Execute TTS command with proper parameters
|
|
1347
|
+
# TEST: Test system command execution with voice settings
|
|
1348
|
+
case @backend
|
|
1349
|
+
when 'espeak'
|
|
1350
|
+
generate_with_espeak(text, temp_file, settings)
|
|
1351
|
+
when 'say'
|
|
1352
|
+
generate_with_say(text, temp_file, settings)
|
|
1353
|
+
when 'festival'
|
|
1354
|
+
generate_with_festival(text, temp_file, settings)
|
|
1355
|
+
else
|
|
1356
|
+
raise "Unsupported TTS backend: #{@backend}"
|
|
1357
|
+
end
|
|
1358
|
+
|
|
1359
|
+
# REQUIREMENTS: Validate generated audio file
|
|
1360
|
+
# SEMANTIC TOKENS: AUDIO_FILE_VALID, QUALITY_CHECK
|
|
1361
|
+
# ARCHITECTURE: Audio file validation architecture
|
|
1362
|
+
# IMPLEMENTATION: Verify audio file was created successfully
|
|
1363
|
+
# TEST: Test audio file validation and quality checks
|
|
1364
|
+
unless File.exist?(temp_file) && File.size(temp_file) > 0
|
|
1365
|
+
raise "Failed to generate audio file"
|
|
1366
|
+
end
|
|
1367
|
+
|
|
1368
|
+
temp_file
|
|
1369
|
+
end
|
|
1370
|
+
|
|
1371
|
+
# REQUIREMENTS: Check if system TTS backend is available
|
|
1372
|
+
# SEMANTIC TOKENS: SYSTEM_TTS_AVAIL, BACKEND_CHECK
|
|
1373
|
+
# ARCHITECTURE: System TTS availability checking
|
|
1374
|
+
# IMPLEMENTATION: Check if TTS backend is installed and working
|
|
1375
|
+
# TEST: Test system TTS backend availability
|
|
1376
|
+
def available?
|
|
1377
|
+
case @backend
|
|
1378
|
+
when 'espeak'
|
|
1379
|
+
system('which espeak > /dev/null 2>&1')
|
|
1380
|
+
when 'say'
|
|
1381
|
+
system('which say > /dev/null 2>&1')
|
|
1382
|
+
when 'festival'
|
|
1383
|
+
system('which festival > /dev/null 2>&1')
|
|
1384
|
+
else
|
|
1385
|
+
false
|
|
1386
|
+
end
|
|
1387
|
+
end
|
|
1388
|
+
|
|
1389
|
+
# REQUIREMENTS: Get supported audio formats for system TTS
|
|
1390
|
+
# SEMANTIC TOKENS: SYSTEM_AUDIO_FORMATS, FORMAT_SUPPORT
|
|
1391
|
+
# ARCHITECTURE: System audio format support
|
|
1392
|
+
# IMPLEMENTATION: Return formats supported by system TTS
|
|
1393
|
+
# TEST: Test system audio format support
|
|
1394
|
+
def supported_formats
|
|
1395
|
+
case @backend
|
|
1396
|
+
when 'espeak'
|
|
1397
|
+
['wav', 'mp3']
|
|
1398
|
+
when 'say'
|
|
1399
|
+
['wav', 'aiff']
|
|
1400
|
+
when 'festival'
|
|
1401
|
+
['wav']
|
|
1402
|
+
else
|
|
1403
|
+
['wav']
|
|
1404
|
+
end
|
|
1405
|
+
end
|
|
1406
|
+
|
|
1407
|
+
private
|
|
1408
|
+
|
|
1409
|
+
# REQUIREMENTS: Generate audio using espeak
|
|
1410
|
+
# SEMANTIC TOKENS: ESPEAK_GENERATION, VOICE_SETTINGS_APP
|
|
1411
|
+
# ARCHITECTURE: espeak command execution
|
|
1412
|
+
# IMPLEMENTATION: Execute espeak with voice settings
|
|
1413
|
+
# TEST: Test espeak audio generation
|
|
1414
|
+
def generate_with_espeak(text, output_file, settings)
|
|
1415
|
+
# REQUIREMENTS: Build espeak command with voice settings
|
|
1416
|
+
# SEMANTIC TOKENS: COMMAND_BUILDING, VOICE_PARAMETERS
|
|
1417
|
+
# ARCHITECTURE: Command parameter mapping
|
|
1418
|
+
# IMPLEMENTATION: Map voice settings to espeak parameters
|
|
1419
|
+
# TEST: Test espeak command building with voice settings
|
|
1420
|
+
speed = (settings[:speed] * 100).to_i
|
|
1421
|
+
pitch = (settings[:pitch] * 50).to_i
|
|
1422
|
+
volume = (settings[:volume] * 100).to_i
|
|
1423
|
+
|
|
1424
|
+
cmd = "espeak -s #{speed} -p #{pitch} -a #{volume} -w '#{output_file}' '#{text}'"
|
|
1425
|
+
|
|
1426
|
+
# REQUIREMENTS: Execute espeak command
|
|
1427
|
+
# SEMANTIC TOKENS: COMMAND_EXECUTION, SYSTEM_CALL
|
|
1428
|
+
# ARCHITECTURE: System command execution
|
|
1429
|
+
# IMPLEMENTATION: Execute espeak command and handle errors
|
|
1430
|
+
# TEST: Test espeak command execution
|
|
1431
|
+
unless system(cmd)
|
|
1432
|
+
raise "espeak command failed: #{cmd}"
|
|
1433
|
+
end
|
|
1434
|
+
end
|
|
1435
|
+
|
|
1436
|
+
# REQUIREMENTS: Generate audio using say (macOS)
|
|
1437
|
+
# SEMANTIC TOKENS: SAY_GENERATION, MACOS_TTS
|
|
1438
|
+
# ARCHITECTURE: macOS say command execution
|
|
1439
|
+
# IMPLEMENTATION: Execute say command with voice settings
|
|
1440
|
+
# TEST: Test say audio generation
|
|
1441
|
+
def generate_with_say(text, output_file, settings)
|
|
1442
|
+
# REQUIREMENTS: Build say command with voice settings
|
|
1443
|
+
# SEMANTIC TOKENS: SAY_COMMAND_BUILDING, MACOS_PARAMETERS
|
|
1444
|
+
# ARCHITECTURE: macOS say command parameter mapping
|
|
1445
|
+
# IMPLEMENTATION: Map voice settings to say parameters
|
|
1446
|
+
# TEST: Test say command building with voice settings
|
|
1447
|
+
rate = (settings[:speed] * 200).to_i
|
|
1448
|
+
|
|
1449
|
+
# Use basic say command with AIFF format
|
|
1450
|
+
cmd = "say -o '#{output_file}' '#{text}'"
|
|
1451
|
+
|
|
1452
|
+
# REQUIREMENTS: Execute say command
|
|
1453
|
+
# SEMANTIC TOKENS: SAY_EXECUTION, MACOS_SYSTEM_CALL
|
|
1454
|
+
# ARCHITECTURE: macOS system command execution
|
|
1455
|
+
# IMPLEMENTATION: Execute say command and handle errors
|
|
1456
|
+
# TEST: Test say command execution
|
|
1457
|
+
unless system(cmd)
|
|
1458
|
+
raise "say command failed: #{cmd}"
|
|
1459
|
+
end
|
|
1460
|
+
end
|
|
1461
|
+
|
|
1462
|
+
# REQUIREMENTS: Generate audio using festival
|
|
1463
|
+
# SEMANTIC TOKENS: FESTIVAL_GENERATION, FESTIVAL_TTS
|
|
1464
|
+
# ARCHITECTURE: Festival TTS command execution
|
|
1465
|
+
# IMPLEMENTATION: Execute festival command with voice settings
|
|
1466
|
+
# TEST: Test festival audio generation
|
|
1467
|
+
def generate_with_festival(text, output_file, settings)
|
|
1468
|
+
# REQUIREMENTS: Build festival command with voice settings
|
|
1469
|
+
# SEMANTIC TOKENS: FESTIVAL_COMMAND_BUILDING, FESTIVAL_PARAMETERS
|
|
1470
|
+
# ARCHITECTURE: Festival command parameter mapping
|
|
1471
|
+
# IMPLEMENTATION: Map voice settings to festival parameters
|
|
1472
|
+
# TEST: Test festival command building with voice settings
|
|
1473
|
+
rate = settings[:speed]
|
|
1474
|
+
pitch = settings[:pitch]
|
|
1475
|
+
|
|
1476
|
+
# Festival uses Scheme-like syntax for voice settings
|
|
1477
|
+
festival_script = "(set! utt (Utterance Text \"#{text}\")) (utt.synth utt) (utt.save.wave utt \"#{output_file}\")"
|
|
1478
|
+
|
|
1479
|
+
cmd = "echo '#{festival_script}' | festival"
|
|
1480
|
+
|
|
1481
|
+
# REQUIREMENTS: Execute festival command
|
|
1482
|
+
# SEMANTIC TOKENS: FESTIVAL_EXECUTION, FESTIVAL_SYSTEM_CALL
|
|
1483
|
+
# ARCHITECTURE: Festival system command execution
|
|
1484
|
+
# IMPLEMENTATION: Execute festival command and handle errors
|
|
1485
|
+
# TEST: Test festival command execution
|
|
1486
|
+
unless system(cmd)
|
|
1487
|
+
raise "festival command failed: #{cmd}"
|
|
1488
|
+
end
|
|
1489
|
+
end
|
|
1490
|
+
|
|
1491
|
+
# REQUIREMENTS: Detect available TTS backend
|
|
1492
|
+
# SEMANTIC TOKENS: BACKEND_DETECTION, SYSTEM_SCANNING
|
|
1493
|
+
# ARCHITECTURE: TTS backend detection architecture
|
|
1494
|
+
# IMPLEMENTATION: Scan system for available TTS backends
|
|
1495
|
+
# TEST: Test TTS backend detection
|
|
1496
|
+
def detect_available_backend
|
|
1497
|
+
if system('which espeak > /dev/null 2>&1')
|
|
1498
|
+
'espeak'
|
|
1499
|
+
elsif system('which say > /dev/null 2>&1')
|
|
1500
|
+
'say'
|
|
1501
|
+
elsif system('which festival > /dev/null 2>&1')
|
|
1502
|
+
'festival'
|
|
1503
|
+
else
|
|
1504
|
+
raise "No TTS backend available (espeak, say, festival)"
|
|
1505
|
+
end
|
|
1506
|
+
end
|
|
1507
|
+
end
|
|
1508
|
+
|
|
1509
|
+
# TTS Engine Factory
|
|
1510
|
+
# REQUIREMENTS: Create TTS engine factory for backend selection
|
|
1511
|
+
# SEMANTIC TOKENS: TTS_ENGINE_FACTORY, BACKEND_SELECTION
|
|
1512
|
+
# ARCHITECTURE: TTS engine factory with backend selection
|
|
1513
|
+
# IMPLEMENTATION: Factory pattern for TTS engine creation
|
|
1514
|
+
# TEST: Test TTS engine factory and backend selection
|
|
1515
|
+
class TTSEngineFactory
|
|
1516
|
+
# REQUIREMENTS: Create TTS engine instance
|
|
1517
|
+
# SEMANTIC TOKENS: TTS_ENGINE_CREATE, FACTORY_PATTERN
|
|
1518
|
+
# ARCHITECTURE: TTS engine factory architecture
|
|
1519
|
+
# IMPLEMENTATION: Create TTS engine with specified backend
|
|
1520
|
+
# TEST: Test TTS engine creation with different backends
|
|
1521
|
+
def self.create(backend = 'auto', config = {})
|
|
1522
|
+
case backend
|
|
1523
|
+
when 'auto'
|
|
1524
|
+
# REQUIREMENTS: Auto-detect best available TTS backend
|
|
1525
|
+
# SEMANTIC TOKENS: AUTO_DETECTION, BACKEND_SELECTION
|
|
1526
|
+
# ARCHITECTURE: Automatic backend selection
|
|
1527
|
+
# IMPLEMENTATION: Select best available TTS backend
|
|
1528
|
+
# TEST: Test automatic backend selection
|
|
1529
|
+
SystemTTSEngine.new(config)
|
|
1530
|
+
when 'espeak', 'say', 'festival'
|
|
1531
|
+
# REQUIREMENTS: Create specific TTS backend
|
|
1532
|
+
# SEMANTIC TOKENS: SPECIFIC_BACKEND, BACKEND_CREATE
|
|
1533
|
+
# ARCHITECTURE: Specific backend creation
|
|
1534
|
+
# IMPLEMENTATION: Create TTS engine with specific backend
|
|
1535
|
+
# TEST: Test specific backend creation
|
|
1536
|
+
SystemTTSEngine.new(config.merge(backend: backend))
|
|
1537
|
+
else
|
|
1538
|
+
raise "Unsupported TTS backend: #{backend}"
|
|
1539
|
+
end
|
|
1540
|
+
end
|
|
1541
|
+
|
|
1542
|
+
# REQUIREMENTS: Get list of available TTS backends
|
|
1543
|
+
# SEMANTIC TOKENS: BACKEND_LISTING, AVAILABILITY_CHECK
|
|
1544
|
+
# ARCHITECTURE: TTS backend listing architecture
|
|
1545
|
+
# IMPLEMENTATION: Scan system for available TTS backends
|
|
1546
|
+
# TEST: Test TTS backend listing
|
|
1547
|
+
def self.available_backends
|
|
1548
|
+
backends = []
|
|
1549
|
+
backends << 'espeak' if system('which espeak > /dev/null 2>&1')
|
|
1550
|
+
backends << 'say' if system('which say > /dev/null 2>&1')
|
|
1551
|
+
backends << 'festival' if system('which festival > /dev/null 2>&1')
|
|
1552
|
+
backends
|
|
1553
|
+
end
|
|
1554
|
+
end
|
|
1555
|
+
|
|
1556
|
+
# Audio Segment Generator
|
|
1557
|
+
# REQUIREMENTS: Create AudioSegmentGenerator class for individual segments
|
|
1558
|
+
# SEMANTIC TOKENS: AUDIO_SEGMENT_GEN, SEGMENT_PROC
|
|
1559
|
+
# ARCHITECTURE: Audio segment generation architecture
|
|
1560
|
+
# IMPLEMENTATION: Generate individual audio segments from YAML data
|
|
1561
|
+
# TEST: Test individual audio segment generation
|
|
1562
|
+
class AudioSegmentGenerator
|
|
1563
|
+
# REQUIREMENTS: Initialize audio segment generator with TTS engine
|
|
1564
|
+
# SEMANTIC TOKENS: SEGMENT_GENERATOR_INIT, TTS_ENGINE_INTEGRATION
|
|
1565
|
+
# ARCHITECTURE: Audio segment generator initialization
|
|
1566
|
+
# IMPLEMENTATION: Initialize with TTS engine and configuration
|
|
1567
|
+
# TEST: Test audio segment generator initialization
|
|
1568
|
+
def initialize(tts_engine, config = {})
|
|
1569
|
+
@tts_engine = tts_engine
|
|
1570
|
+
@config = config
|
|
1571
|
+
@temp_dir = config[:temp_dir] || Dir.mktmpdir
|
|
1572
|
+
@output_format = config[:output_format] || 'wav'
|
|
1573
|
+
@generated_segments = []
|
|
1574
|
+
@quiet_mode = config[:quiet] || false
|
|
1575
|
+
end
|
|
1576
|
+
|
|
1577
|
+
# REQUIREMENTS: Generate audio segment from YAML segment data
|
|
1578
|
+
# SEMANTIC TOKENS: SEGMENT_AUDIO_GENERATION, YAML_PROC
|
|
1579
|
+
# ARCHITECTURE: Audio segment generation from YAML
|
|
1580
|
+
# IMPLEMENTATION: Convert YAML segment to audio file
|
|
1581
|
+
# TEST: Test audio segment generation from YAML data
|
|
1582
|
+
def generate_segment(segment_data)
|
|
1583
|
+
# REQUIREMENTS: Extract text and voice settings from segment data
|
|
1584
|
+
# SEMANTIC TOKENS: SEGMENT_DATA_EXTRACT, VOICE_SETTINGS_PROC
|
|
1585
|
+
# ARCHITECTURE: Segment data processing architecture
|
|
1586
|
+
# IMPLEMENTATION: Extract text and voice settings from segment
|
|
1587
|
+
# TEST: Test segment data extraction and voice settings processing
|
|
1588
|
+
text = segment_data['text']
|
|
1589
|
+
voice_settings = extract_voice_settings(segment_data)
|
|
1590
|
+
|
|
1591
|
+
# REQUIREMENTS: Generate audio file using TTS engine
|
|
1592
|
+
# SEMANTIC TOKENS: TTS_AUDIO_GEN, VOICE_SETTINGS_APP
|
|
1593
|
+
# ARCHITECTURE: TTS engine integration for audio generation
|
|
1594
|
+
# IMPLEMENTATION: Use TTS engine to generate audio with voice settings
|
|
1595
|
+
# TEST: Test TTS engine integration and voice settings application
|
|
1596
|
+
audio_file = @tts_engine.generate_audio(text, voice_settings)
|
|
1597
|
+
|
|
1598
|
+
# REQUIREMENTS: Create segment metadata
|
|
1599
|
+
# SEMANTIC TOKENS: SEGMENT_METADATA_CREATE, AUDIO_METADATA
|
|
1600
|
+
# ARCHITECTURE: Segment metadata architecture
|
|
1601
|
+
# IMPLEMENTATION: Create metadata for generated audio segment
|
|
1602
|
+
# TEST: Test segment metadata creation and tracking
|
|
1603
|
+
segment_metadata = {
|
|
1604
|
+
'audio_file' => audio_file,
|
|
1605
|
+
'text' => text,
|
|
1606
|
+
'voice_settings' => voice_settings,
|
|
1607
|
+
'source_file' => segment_data['source_file'],
|
|
1608
|
+
'line_number' => segment_data['line_number'],
|
|
1609
|
+
'start_time' => segment_data['start_time'],
|
|
1610
|
+
'end_time' => segment_data['end_time'],
|
|
1611
|
+
'duration' => segment_data['end_time'] - segment_data['start_time'],
|
|
1612
|
+
'generated_at' => Time.now.iso8601
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
# REQUIREMENTS: Track generated segment
|
|
1616
|
+
# SEMANTIC TOKENS: SEGMENT_TRACKING, GENERATED_SEGMENTS
|
|
1617
|
+
# ARCHITECTURE: Segment tracking architecture
|
|
1618
|
+
# IMPLEMENTATION: Track generated segments for cleanup and management
|
|
1619
|
+
# TEST: Test segment tracking and management
|
|
1620
|
+
@generated_segments << segment_metadata
|
|
1621
|
+
|
|
1622
|
+
segment_metadata
|
|
1623
|
+
end
|
|
1624
|
+
|
|
1625
|
+
# REQUIREMENTS: Generate multiple audio segments from YAML data
|
|
1626
|
+
# SEMANTIC TOKENS: BATCH_SEGMENT_GEN, MULTIPLE_SEGMENTS
|
|
1627
|
+
# ARCHITECTURE: Batch audio segment generation
|
|
1628
|
+
# IMPLEMENTATION: Generate multiple audio segments from YAML array
|
|
1629
|
+
# TEST: Test batch audio segment generation
|
|
1630
|
+
def generate_segments(segments_data)
|
|
1631
|
+
# REQUIREMENTS: Process multiple segments with progress tracking
|
|
1632
|
+
# SEMANTIC TOKENS: BATCH_PROC, PROGRESS_TRACKING
|
|
1633
|
+
# ARCHITECTURE: Batch processing architecture
|
|
1634
|
+
# IMPLEMENTATION: Process multiple segments with progress reporting
|
|
1635
|
+
# TEST: Test batch processing and progress tracking
|
|
1636
|
+
generated_segments = []
|
|
1637
|
+
|
|
1638
|
+
segments_data.each_with_index do |segment_data, index|
|
|
1639
|
+
# REQUIREMENTS: Generate individual segment with progress reporting
|
|
1640
|
+
# SEMANTIC TOKENS: INDIVIDUAL_SEGMENT_GEN, PROGRESS_REPORTING
|
|
1641
|
+
# ARCHITECTURE: Individual segment generation with progress
|
|
1642
|
+
# IMPLEMENTATION: Generate segment and report progress
|
|
1643
|
+
# TEST: Test individual segment generation with progress
|
|
1644
|
+
puts "# INFO: Generating segment #{index + 1}/#{segments_data.length}: #{segment_data['text'][0..50]}..."
|
|
1645
|
+
|
|
1646
|
+
begin
|
|
1647
|
+
segment_metadata = generate_segment(segment_data)
|
|
1648
|
+
generated_segments << segment_metadata
|
|
1649
|
+
puts "# INFO: Generated segment #{index + 1}: #{segment_metadata['audio_file']}"
|
|
1650
|
+
rescue => e
|
|
1651
|
+
puts "# ERROR: Failed to generate segment #{index + 1}: #{e.message}"
|
|
1652
|
+
raise e
|
|
1653
|
+
end
|
|
1654
|
+
end
|
|
1655
|
+
|
|
1656
|
+
generated_segments
|
|
1657
|
+
end
|
|
1658
|
+
|
|
1659
|
+
# REQUIREMENTS: Clean up generated audio files
|
|
1660
|
+
# SEMANTIC TOKENS: AUDIO_CLEANUP, TEMP_FILE_MANAGEMENT
|
|
1661
|
+
# ARCHITECTURE: Audio file cleanup architecture
|
|
1662
|
+
# IMPLEMENTATION: Clean up temporary audio files
|
|
1663
|
+
# TEST: Test audio file cleanup and temporary file management
|
|
1664
|
+
def cleanup
|
|
1665
|
+
# REQUIREMENTS: Remove generated audio files
|
|
1666
|
+
# SEMANTIC TOKENS: FILE_CLEANUP, TEMPORARY_FILE_REMOVAL
|
|
1667
|
+
# ARCHITECTURE: File cleanup architecture
|
|
1668
|
+
# IMPLEMENTATION: Remove temporary audio files
|
|
1669
|
+
# TEST: Test file cleanup and temporary file removal
|
|
1670
|
+
@generated_segments.each do |segment|
|
|
1671
|
+
audio_file = segment['audio_file']
|
|
1672
|
+
if File.exist?(audio_file)
|
|
1673
|
+
File.delete(audio_file)
|
|
1674
|
+
puts "# INFO: Cleaned up audio file: #{audio_file}"
|
|
1675
|
+
end
|
|
1676
|
+
end
|
|
1677
|
+
|
|
1678
|
+
@generated_segments.clear
|
|
1679
|
+
end
|
|
1680
|
+
|
|
1681
|
+
# REQUIREMENTS: Get generated segments metadata
|
|
1682
|
+
# SEMANTIC TOKENS: SEGMENT_METADATA_ACCESS, GENERATED_SEGMENTS_INFO
|
|
1683
|
+
# ARCHITECTURE: Segment metadata access architecture
|
|
1684
|
+
# IMPLEMENTATION: Provide access to generated segments metadata
|
|
1685
|
+
# TEST: Test segment metadata access and information
|
|
1686
|
+
def generated_segments
|
|
1687
|
+
@generated_segments.dup
|
|
1688
|
+
end
|
|
1689
|
+
|
|
1690
|
+
private
|
|
1691
|
+
|
|
1692
|
+
# REQUIREMENTS: Extract voice settings from segment data
|
|
1693
|
+
# SEMANTIC TOKENS: VOICE_SETTINGS_EXTRACT, SEGMENT_DATA_PROC
|
|
1694
|
+
# ARCHITECTURE: Voice settings extraction architecture
|
|
1695
|
+
# IMPLEMENTATION: Extract and process voice settings from segment
|
|
1696
|
+
# TEST: Test voice settings extraction and processing
|
|
1697
|
+
def extract_voice_settings(segment_data)
|
|
1698
|
+
# REQUIREMENTS: Extract voice settings with defaults
|
|
1699
|
+
# SEMANTIC TOKENS: VOICE_SETTINGS_DEFAULTS, SETTINGS_EXTRACT
|
|
1700
|
+
# ARCHITECTURE: Voice settings extraction with defaults
|
|
1701
|
+
# IMPLEMENTATION: Extract voice settings with fallback defaults
|
|
1702
|
+
# TEST: Test voice settings extraction with defaults
|
|
1703
|
+
voice_settings = {}
|
|
1704
|
+
|
|
1705
|
+
# Extract speed from segment data
|
|
1706
|
+
if segment_data['speed']
|
|
1707
|
+
voice_settings[:speed] = segment_data['speed'].to_f
|
|
1708
|
+
end
|
|
1709
|
+
|
|
1710
|
+
# Extract pitch from segment data
|
|
1711
|
+
if segment_data['pitch']
|
|
1712
|
+
voice_settings[:pitch] = segment_data['pitch'].to_f
|
|
1713
|
+
end
|
|
1714
|
+
|
|
1715
|
+
# Extract volume from segment data
|
|
1716
|
+
if segment_data['volume']
|
|
1717
|
+
voice_settings[:volume] = segment_data['volume'].to_f
|
|
1718
|
+
end
|
|
1719
|
+
|
|
1720
|
+
# Apply color-based pitch mapping if available
|
|
1721
|
+
if segment_data['color']
|
|
1722
|
+
color_pitch = map_color_to_pitch(segment_data['color'])
|
|
1723
|
+
voice_settings[:pitch] = color_pitch if color_pitch
|
|
1724
|
+
end
|
|
1725
|
+
|
|
1726
|
+
voice_settings
|
|
1727
|
+
end
|
|
1728
|
+
|
|
1729
|
+
# REQUIREMENTS: Map color to pitch for voice variation
|
|
1730
|
+
# SEMANTIC TOKENS: COLOR_PITCH_MAPPING, VOICE_VARIATION
|
|
1731
|
+
# ARCHITECTURE: Color to pitch mapping architecture
|
|
1732
|
+
# IMPLEMENTATION: Map text colors to voice pitch variations
|
|
1733
|
+
# TEST: Test color to pitch mapping and voice variation
|
|
1734
|
+
def map_color_to_pitch(color)
|
|
1735
|
+
# REQUIREMENTS: Map common colors to pitch values
|
|
1736
|
+
# SEMANTIC TOKENS: COLOR_MAPPING, PITCH_VALUES
|
|
1737
|
+
# ARCHITECTURE: Color mapping architecture
|
|
1738
|
+
# IMPLEMENTATION: Map colors to pitch values for voice variation
|
|
1739
|
+
# TEST: Test color mapping to pitch values
|
|
1740
|
+
color_pitch_map = {
|
|
1741
|
+
'red' => 1.2,
|
|
1742
|
+
'blue' => 0.8,
|
|
1743
|
+
'green' => 1.0,
|
|
1744
|
+
'yellow' => 1.1,
|
|
1745
|
+
'purple' => 0.9,
|
|
1746
|
+
'orange' => 1.15,
|
|
1747
|
+
'pink' => 1.05,
|
|
1748
|
+
'brown' => 0.85,
|
|
1749
|
+
'black' => 1.0,
|
|
1750
|
+
'white' => 1.0
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
color_pitch_map[color.downcase]
|
|
1754
|
+
end
|
|
1755
|
+
end
|
|
1756
|
+
|
|
1757
|
+
# Audio Stitcher
|
|
1758
|
+
# REQUIREMENTS: Create AudioStitcher class for combining segments
|
|
1759
|
+
# SEMANTIC TOKENS: AUDIO_STITCHER_CLASS, AUDIO_COMBINATION
|
|
1760
|
+
# ARCHITECTURE: Audio stitching architecture with timing synchronization
|
|
1761
|
+
# IMPLEMENTATION: Combine audio segments with silence gaps
|
|
1762
|
+
# TEST: Test audio stitching and silence gap insertion
|
|
1763
|
+
class AudioStitcher
|
|
1764
|
+
# REQUIREMENTS: Initialize audio stitcher with configuration
|
|
1765
|
+
# SEMANTIC TOKENS: AUDIO_STITCHER_INIT, STITCHER_CONFIG
|
|
1766
|
+
# ARCHITECTURE: Audio stitcher initialization architecture
|
|
1767
|
+
# IMPLEMENTATION: Initialize with output format and timing settings
|
|
1768
|
+
# TEST: Test audio stitcher initialization and configuration
|
|
1769
|
+
def initialize(config = {})
|
|
1770
|
+
@config = config
|
|
1771
|
+
@output_format = config[:output_format] || 'wav'
|
|
1772
|
+
@sample_rate = config[:sample_rate] || 44100
|
|
1773
|
+
@temp_dir = config[:temp_dir] || Dir.mktmpdir
|
|
1774
|
+
@quiet_mode = config[:quiet] || false
|
|
1775
|
+
@stitched_segments = []
|
|
1776
|
+
end
|
|
1777
|
+
|
|
1778
|
+
# REQUIREMENTS: Stitch audio segments with silence gaps
|
|
1779
|
+
# SEMANTIC TOKENS: AUDIO_STITCHING, SILENCE_GAP_INSERTION
|
|
1780
|
+
# ARCHITECTURE: Audio stitching with timing synchronization
|
|
1781
|
+
# IMPLEMENTATION: Combine segments with calculated silence gaps
|
|
1782
|
+
# TEST: Test audio stitching and silence gap insertion
|
|
1783
|
+
def stitch_segments(segments_metadata, gaps_metadata, output_file = nil)
|
|
1784
|
+
# REQUIREMENTS: Validate input metadata
|
|
1785
|
+
# SEMANTIC TOKENS: METADATA_VALID, INPUT_VALID
|
|
1786
|
+
# ARCHITECTURE: Input validation architecture
|
|
1787
|
+
# IMPLEMENTATION: Validate segments and gaps metadata
|
|
1788
|
+
# TEST: Test input validation and error handling
|
|
1789
|
+
validate_input_metadata(segments_metadata, gaps_metadata)
|
|
1790
|
+
|
|
1791
|
+
# REQUIREMENTS: Create output file path
|
|
1792
|
+
# SEMANTIC TOKENS: OUTPUT_FILE_CREATE, FILE_PATH_GEN
|
|
1793
|
+
# ARCHITECTURE: Output file path generation
|
|
1794
|
+
# IMPLEMENTATION: Generate unique output file path or use provided path
|
|
1795
|
+
# TEST: Test output file path generation
|
|
1796
|
+
output_file ||= create_output_file_path
|
|
1797
|
+
|
|
1798
|
+
# REQUIREMENTS: Stitch segments with silence gaps
|
|
1799
|
+
# SEMANTIC TOKENS: SEGMENT_STITCHING, SILENCE_INSERTION
|
|
1800
|
+
# ARCHITECTURE: Segment stitching architecture
|
|
1801
|
+
# IMPLEMENTATION: Combine segments with calculated silence gaps
|
|
1802
|
+
# TEST: Test segment stitching with silence gaps
|
|
1803
|
+
stitch_audio_files(segments_metadata, gaps_metadata, output_file)
|
|
1804
|
+
|
|
1805
|
+
# REQUIREMENTS: Create stitching metadata
|
|
1806
|
+
# SEMANTIC TOKENS: STITCHING_METADATA_CREATE, OUTPUT_METADATA
|
|
1807
|
+
# ARCHITECTURE: Stitching metadata architecture
|
|
1808
|
+
# IMPLEMENTATION: Create metadata for stitched audio file
|
|
1809
|
+
# TEST: Test stitching metadata creation
|
|
1810
|
+
stitching_metadata = create_stitching_metadata(segments_metadata, gaps_metadata, output_file)
|
|
1811
|
+
|
|
1812
|
+
# REQUIREMENTS: Track stitched segments
|
|
1813
|
+
# SEMANTIC TOKENS: STITCHED_SEGMENT_TRACKING, OUTPUT_TRACKING
|
|
1814
|
+
# ARCHITECTURE: Stitched segment tracking architecture
|
|
1815
|
+
# IMPLEMENTATION: Track stitched segments for cleanup
|
|
1816
|
+
# TEST: Test stitched segment tracking
|
|
1817
|
+
@stitched_segments << stitching_metadata
|
|
1818
|
+
|
|
1819
|
+
stitching_metadata
|
|
1820
|
+
end
|
|
1821
|
+
|
|
1822
|
+
# REQUIREMENTS: Get stitched segments metadata
|
|
1823
|
+
# SEMANTIC TOKENS: STITCHED_SEGMENTS_ACCESS, OUTPUT_METADATA_ACCESS
|
|
1824
|
+
# ARCHITECTURE: Stitched segments access architecture
|
|
1825
|
+
# IMPLEMENTATION: Provide access to stitched segments metadata
|
|
1826
|
+
# TEST: Test stitched segments metadata access
|
|
1827
|
+
def stitched_segments
|
|
1828
|
+
@stitched_segments.dup
|
|
1829
|
+
end
|
|
1830
|
+
|
|
1831
|
+
# REQUIREMENTS: Clean up stitched audio files
|
|
1832
|
+
# SEMANTIC TOKENS: STITCHED_AUDIO_CLEANUP, OUTPUT_FILE_CLEANUP
|
|
1833
|
+
# ARCHITECTURE: Stitched audio cleanup architecture
|
|
1834
|
+
# IMPLEMENTATION: Clean up stitched audio files
|
|
1835
|
+
# TEST: Test stitched audio file cleanup
|
|
1836
|
+
def cleanup
|
|
1837
|
+
# REQUIREMENTS: Remove stitched audio files
|
|
1838
|
+
# SEMANTIC TOKENS: STITCHED_FILE_CLEANUP, OUTPUT_CLEANUP
|
|
1839
|
+
# ARCHITECTURE: Stitched file cleanup architecture
|
|
1840
|
+
# IMPLEMENTATION: Remove stitched audio files
|
|
1841
|
+
# TEST: Test stitched file cleanup
|
|
1842
|
+
@stitched_segments.each do |stitched_segment|
|
|
1843
|
+
output_file = stitched_segment['output_file']
|
|
1844
|
+
if File.exist?(output_file)
|
|
1845
|
+
File.delete(output_file)
|
|
1846
|
+
puts "# INFO: Cleaned up stitched audio file: #{output_file}"
|
|
1847
|
+
end
|
|
1848
|
+
end
|
|
1849
|
+
|
|
1850
|
+
@stitched_segments.clear
|
|
1851
|
+
end
|
|
1852
|
+
|
|
1853
|
+
private
|
|
1854
|
+
|
|
1855
|
+
# REQUIREMENTS: Validate input metadata
|
|
1856
|
+
# SEMANTIC TOKENS: INPUT_VALID, METADATA_VALID
|
|
1857
|
+
# ARCHITECTURE: Input validation architecture
|
|
1858
|
+
# IMPLEMENTATION: Validate segments and gaps metadata
|
|
1859
|
+
# TEST: Test input validation and error handling
|
|
1860
|
+
def validate_input_metadata(segments_metadata, gaps_metadata)
|
|
1861
|
+
# REQUIREMENTS: Validate segments metadata
|
|
1862
|
+
# SEMANTIC TOKENS: SEGMENTS_VALID, METADATA_CHECK
|
|
1863
|
+
# ARCHITECTURE: Segments validation architecture
|
|
1864
|
+
# IMPLEMENTATION: Validate segments metadata structure
|
|
1865
|
+
# TEST: Test segments metadata validation
|
|
1866
|
+
unless segments_metadata.is_a?(Array) && !segments_metadata.empty?
|
|
1867
|
+
raise "Invalid segments metadata: must be non-empty array"
|
|
1868
|
+
end
|
|
1869
|
+
|
|
1870
|
+
segments_metadata.each_with_index do |segment, index|
|
|
1871
|
+
unless segment.is_a?(Hash) && segment['audio_file'] && segment['start_time'] && segment['end_time']
|
|
1872
|
+
raise "Invalid segment #{index}: missing required fields (audio_file, start_time, end_time)"
|
|
1873
|
+
end
|
|
1874
|
+
|
|
1875
|
+
unless File.exist?(segment['audio_file'])
|
|
1876
|
+
raise "Segment #{index} audio file not found: #{segment['audio_file']}"
|
|
1877
|
+
end
|
|
1878
|
+
end
|
|
1879
|
+
|
|
1880
|
+
# REQUIREMENTS: Validate gaps metadata
|
|
1881
|
+
# SEMANTIC TOKENS: GAPS_VALID, GAPS_CHECK
|
|
1882
|
+
# ARCHITECTURE: Gaps validation architecture
|
|
1883
|
+
# IMPLEMENTATION: Validate gaps metadata structure
|
|
1884
|
+
# TEST: Test gaps metadata validation
|
|
1885
|
+
unless gaps_metadata.is_a?(Array)
|
|
1886
|
+
raise "Invalid gaps metadata: must be array"
|
|
1887
|
+
end
|
|
1888
|
+
|
|
1889
|
+
gaps_metadata.each_with_index do |gap, index|
|
|
1890
|
+
unless gap.is_a?(Hash) && gap['duration']
|
|
1891
|
+
raise "Invalid gap #{index}: missing duration field"
|
|
1892
|
+
end
|
|
1893
|
+
end
|
|
1894
|
+
end
|
|
1895
|
+
|
|
1896
|
+
# REQUIREMENTS: Create output file path
|
|
1897
|
+
# SEMANTIC TOKENS: OUTPUT_FILE_CREATE, FILE_PATH_GEN
|
|
1898
|
+
# ARCHITECTURE: Output file path generation architecture
|
|
1899
|
+
# IMPLEMENTATION: Generate unique output file path
|
|
1900
|
+
# TEST: Test output file path generation
|
|
1901
|
+
def create_output_file_path
|
|
1902
|
+
timestamp = Time.now.to_i
|
|
1903
|
+
random_suffix = rand(1000)
|
|
1904
|
+
filename = "stitched_audio_#{timestamp}_#{random_suffix}.#{@output_format}"
|
|
1905
|
+
File.join(@temp_dir, filename)
|
|
1906
|
+
end
|
|
1907
|
+
|
|
1908
|
+
# REQUIREMENTS: Stitch audio files with silence gaps
|
|
1909
|
+
# SEMANTIC TOKENS: AUDIO_STITCHING, SILENCE_INSERTION
|
|
1910
|
+
# ARCHITECTURE: Audio stitching architecture
|
|
1911
|
+
# IMPLEMENTATION: Combine audio files with silence gaps
|
|
1912
|
+
# TEST: Test audio stitching with silence gaps
|
|
1913
|
+
def stitch_audio_files(segments_metadata, gaps_metadata, output_file)
|
|
1914
|
+
# REQUIREMENTS: Create temporary file list for concatenation
|
|
1915
|
+
# SEMANTIC TOKENS: TEMPORARY_FILE_LIST, CONCATENATION_LIST
|
|
1916
|
+
# ARCHITECTURE: Temporary file list architecture
|
|
1917
|
+
# IMPLEMENTATION: Create list of files for concatenation
|
|
1918
|
+
# TEST: Test temporary file list creation
|
|
1919
|
+
file_list = []
|
|
1920
|
+
|
|
1921
|
+
segments_metadata.each_with_index do |segment, index|
|
|
1922
|
+
# REQUIREMENTS: Add segment audio file to list
|
|
1923
|
+
# SEMANTIC TOKENS: SEGMENT_FILE_ADD, AUDIO_FILE_LISTING
|
|
1924
|
+
# ARCHITECTURE: Segment file addition architecture
|
|
1925
|
+
# IMPLEMENTATION: Add segment audio file to concatenation list
|
|
1926
|
+
# TEST: Test segment file addition to list
|
|
1927
|
+
file_list << segment['audio_file']
|
|
1928
|
+
|
|
1929
|
+
# REQUIREMENTS: Add silence gap if not last segment
|
|
1930
|
+
# SEMANTIC TOKENS: SILENCE_GAP_ADDITION, GAP_INSERTION
|
|
1931
|
+
# ARCHITECTURE: Silence gap addition architecture
|
|
1932
|
+
# IMPLEMENTATION: Add silence gap between segments
|
|
1933
|
+
# TEST: Test silence gap insertion
|
|
1934
|
+
if index < segments_metadata.length - 1
|
|
1935
|
+
gap_duration = gaps_metadata[index] ? gaps_metadata[index]['duration'] : 0.5
|
|
1936
|
+
silence_file = create_silence_file(gap_duration)
|
|
1937
|
+
file_list << silence_file
|
|
1938
|
+
end
|
|
1939
|
+
end
|
|
1940
|
+
|
|
1941
|
+
# REQUIREMENTS: Concatenate audio files
|
|
1942
|
+
# SEMANTIC TOKENS: AUDIO_CONCAT, FILE_CONCAT
|
|
1943
|
+
# ARCHITECTURE: Audio concatenation architecture
|
|
1944
|
+
# IMPLEMENTATION: Concatenate audio files into single output
|
|
1945
|
+
# TEST: Test audio file concatenation
|
|
1946
|
+
concatenate_audio_files(file_list, output_file)
|
|
1947
|
+
end
|
|
1948
|
+
|
|
1949
|
+
# REQUIREMENTS: Create silence file with specified duration
|
|
1950
|
+
# SEMANTIC TOKENS: SILENCE_FILE_CREATE, SILENCE_GEN
|
|
1951
|
+
# ARCHITECTURE: Silence file creation architecture
|
|
1952
|
+
# IMPLEMENTATION: Generate silence audio file
|
|
1953
|
+
# TEST: Test silence file creation
|
|
1954
|
+
def create_silence_file(duration)
|
|
1955
|
+
# REQUIREMENTS: Generate silence using system tools
|
|
1956
|
+
# SEMANTIC TOKENS: SILENCE_GEN, SYSTEM_TOOLS
|
|
1957
|
+
# ARCHITECTURE: Silence generation architecture
|
|
1958
|
+
# IMPLEMENTATION: Use system tools to generate silence
|
|
1959
|
+
# TEST: Test silence generation with system tools
|
|
1960
|
+
silence_file = File.join(@temp_dir, "silence_#{Time.now.to_i}_#{rand(1000)}.wav")
|
|
1961
|
+
|
|
1962
|
+
# Use sox or ffmpeg to generate silence
|
|
1963
|
+
if system('which sox > /dev/null 2>&1')
|
|
1964
|
+
cmd = "sox -n -r #{@sample_rate} -c 1 '#{silence_file}' trim 0 #{duration}"
|
|
1965
|
+
elsif system('which ffmpeg > /dev/null 2>&1')
|
|
1966
|
+
cmd = "ffmpeg -f lavfi -i anullsrc=channel_layout=mono:sample_rate=#{@sample_rate} -t #{duration} '#{silence_file}' -y"
|
|
1967
|
+
else
|
|
1968
|
+
# Fallback: create empty file (will cause issues but won't crash)
|
|
1969
|
+
File.write(silence_file, '')
|
|
1970
|
+
puts "# WARNING: No audio tools available (sox/ffmpeg), created empty silence file"
|
|
1971
|
+
end
|
|
1972
|
+
|
|
1973
|
+
unless system(cmd)
|
|
1974
|
+
raise "Failed to generate silence file: #{cmd}"
|
|
1975
|
+
end
|
|
1976
|
+
|
|
1977
|
+
silence_file
|
|
1978
|
+
end
|
|
1979
|
+
|
|
1980
|
+
# REQUIREMENTS: Concatenate audio files
|
|
1981
|
+
# SEMANTIC TOKENS: AUDIO_CONCAT, FILE_CONCAT
|
|
1982
|
+
# ARCHITECTURE: Audio concatenation architecture
|
|
1983
|
+
# IMPLEMENTATION: Concatenate multiple audio files into one
|
|
1984
|
+
# TEST: Test audio file concatenation
|
|
1985
|
+
def concatenate_audio_files(file_list, output_file)
|
|
1986
|
+
# REQUIREMENTS: Use system tools for concatenation
|
|
1987
|
+
# SEMANTIC TOKENS: SYSTEM_CONCAT, AUDIO_TOOLS
|
|
1988
|
+
# ARCHITECTURE: System concatenation architecture
|
|
1989
|
+
# IMPLEMENTATION: Use sox or ffmpeg for concatenation
|
|
1990
|
+
# TEST: Test system audio concatenation
|
|
1991
|
+
if system('which sox > /dev/null 2>&1')
|
|
1992
|
+
concatenate_with_sox(file_list, output_file)
|
|
1993
|
+
elsif system('which ffmpeg > /dev/null 2>&1')
|
|
1994
|
+
concatenate_with_ffmpeg(file_list, output_file)
|
|
1995
|
+
else
|
|
1996
|
+
# Fallback: simple file concatenation (won't work for audio)
|
|
1997
|
+
concatenate_simple_files(file_list, output_file)
|
|
1998
|
+
end
|
|
1999
|
+
end
|
|
2000
|
+
|
|
2001
|
+
# REQUIREMENTS: Concatenate with sox
|
|
2002
|
+
# SEMANTIC TOKENS: SOX_CONCAT, SOX_TOOLS
|
|
2003
|
+
# ARCHITECTURE: Sox concatenation architecture
|
|
2004
|
+
# IMPLEMENTATION: Use sox for audio concatenation
|
|
2005
|
+
# TEST: Test sox audio concatenation
|
|
2006
|
+
def concatenate_with_sox(file_list, output_file)
|
|
2007
|
+
# REQUIREMENTS: Build sox concatenation command
|
|
2008
|
+
# SEMANTIC TOKENS: SOX_COMMAND_BUILDING, CONCATENATION_COMMAND
|
|
2009
|
+
# ARCHITECTURE: Sox command building architecture
|
|
2010
|
+
# IMPLEMENTATION: Build sox command for concatenation
|
|
2011
|
+
# TEST: Test sox command building
|
|
2012
|
+
cmd = "sox #{file_list.join(' ')} '#{output_file}'"
|
|
2013
|
+
|
|
2014
|
+
unless system(cmd)
|
|
2015
|
+
raise "Sox concatenation failed: #{cmd}"
|
|
2016
|
+
end
|
|
2017
|
+
end
|
|
2018
|
+
|
|
2019
|
+
# REQUIREMENTS: Concatenate with ffmpeg
|
|
2020
|
+
# SEMANTIC TOKENS: FFMPEG_CONCAT, FFMPEG_TOOLS
|
|
2021
|
+
# ARCHITECTURE: FFmpeg concatenation architecture
|
|
2022
|
+
# IMPLEMENTATION: Use ffmpeg for audio concatenation
|
|
2023
|
+
# TEST: Test ffmpeg audio concatenation
|
|
2024
|
+
def concatenate_with_ffmpeg(file_list, output_file)
|
|
2025
|
+
# REQUIREMENTS: Build ffmpeg concatenation command
|
|
2026
|
+
# SEMANTIC TOKENS: FFMPEG_COMMAND_BUILDING, CONCATENATION_COMMAND
|
|
2027
|
+
# ARCHITECTURE: FFmpeg command building architecture
|
|
2028
|
+
# IMPLEMENTATION: Build ffmpeg command for concatenation
|
|
2029
|
+
# TEST: Test ffmpeg command building
|
|
2030
|
+
# Create file list for ffmpeg
|
|
2031
|
+
file_list_path = File.join(@temp_dir, "file_list_#{Time.now.to_i}.txt")
|
|
2032
|
+
File.write(file_list_path, file_list.map { |f| "file '#{f}'" }.join("\n"))
|
|
2033
|
+
|
|
2034
|
+
cmd = "ffmpeg -f concat -safe 0 -i '#{file_list_path}' -c:a pcm_s16le '#{output_file}' -y"
|
|
2035
|
+
|
|
2036
|
+
unless system(cmd)
|
|
2037
|
+
raise "FFmpeg concatenation failed: #{cmd}"
|
|
2038
|
+
end
|
|
2039
|
+
|
|
2040
|
+
# Clean up file list
|
|
2041
|
+
File.delete(file_list_path) if File.exist?(file_list_path)
|
|
2042
|
+
end
|
|
2043
|
+
|
|
2044
|
+
# REQUIREMENTS: Simple file concatenation fallback
|
|
2045
|
+
# SEMANTIC TOKENS: SIMPLE_CONCAT, FALLBACK_CONCAT
|
|
2046
|
+
# ARCHITECTURE: Simple concatenation architecture
|
|
2047
|
+
# IMPLEMENTATION: Simple file concatenation (not audio-aware)
|
|
2048
|
+
# TEST: Test simple file concatenation
|
|
2049
|
+
def concatenate_simple_files(file_list, output_file)
|
|
2050
|
+
# REQUIREMENTS: Simple file concatenation
|
|
2051
|
+
# SEMANTIC TOKENS: SIMPLE_FILE_CONCAT, FALLBACK_METHOD
|
|
2052
|
+
# ARCHITECTURE: Simple concatenation architecture
|
|
2053
|
+
# IMPLEMENTATION: Simple file concatenation
|
|
2054
|
+
# TEST: Test simple file concatenation
|
|
2055
|
+
File.open(output_file, 'wb') do |output|
|
|
2056
|
+
file_list.each do |file_path|
|
|
2057
|
+
if File.exist?(file_path)
|
|
2058
|
+
File.open(file_path, 'rb') do |input|
|
|
2059
|
+
output.write(input.read)
|
|
2060
|
+
end
|
|
2061
|
+
end
|
|
2062
|
+
end
|
|
2063
|
+
end
|
|
2064
|
+
|
|
2065
|
+
puts "# WARNING: Used simple file concatenation (not audio-aware)"
|
|
2066
|
+
end
|
|
2067
|
+
|
|
2068
|
+
# REQUIREMENTS: Create stitching metadata
|
|
2069
|
+
# SEMANTIC TOKENS: STITCHING_METADATA_CREATE, OUTPUT_METADATA
|
|
2070
|
+
# ARCHITECTURE: Stitching metadata architecture
|
|
2071
|
+
# IMPLEMENTATION: Create metadata for stitched audio file
|
|
2072
|
+
# TEST: Test stitching metadata creation
|
|
2073
|
+
def create_stitching_metadata(segments_metadata, gaps_metadata, output_file)
|
|
2074
|
+
# REQUIREMENTS: Calculate total duration
|
|
2075
|
+
# SEMANTIC TOKENS: DURATION_CALC, TOTAL_DURATION
|
|
2076
|
+
# ARCHITECTURE: Duration calculation architecture
|
|
2077
|
+
# IMPLEMENTATION: Calculate total duration of stitched audio
|
|
2078
|
+
# TEST: Test duration calculation
|
|
2079
|
+
total_duration = segments_metadata.sum { |s| s['end_time'] - s['start_time'] }
|
|
2080
|
+
total_duration += gaps_metadata.sum { |g| g['duration'] }
|
|
2081
|
+
|
|
2082
|
+
# REQUIREMENTS: Create stitching metadata
|
|
2083
|
+
# SEMANTIC TOKENS: METADATA_CREATE, STITCHING_INFO
|
|
2084
|
+
# ARCHITECTURE: Metadata creation architecture
|
|
2085
|
+
# IMPLEMENTATION: Create comprehensive stitching metadata
|
|
2086
|
+
# TEST: Test stitching metadata creation
|
|
2087
|
+
{
|
|
2088
|
+
'output_file' => output_file,
|
|
2089
|
+
'segment_count' => segments_metadata.length,
|
|
2090
|
+
'gap_count' => gaps_metadata.length,
|
|
2091
|
+
'total_duration' => total_duration,
|
|
2092
|
+
'sample_rate' => @sample_rate,
|
|
2093
|
+
'output_format' => @output_format,
|
|
2094
|
+
'stitched_at' => Time.now.iso8601,
|
|
2095
|
+
'segments' => segments_metadata,
|
|
2096
|
+
'gaps' => gaps_metadata
|
|
2097
|
+
}
|
|
2098
|
+
end
|
|
2099
|
+
end
|
|
2100
|
+
|
|
2101
|
+
# REQUIREMENTS: Main execution logic with command-line interface
|
|
2102
|
+
# SEMANTIC TOKENS: MAIN_EXECUTION, COMMAND_LINE_INTERFACE, PIPELINE_INTEGRATION
|
|
2103
|
+
# ARCHITECTURE: Main execution flow with error handling
|
|
2104
|
+
# IMPLEMENTATION: Parse arguments and execute processing pipeline
|
|
2105
|
+
# TEST: Test main execution with various command-line arguments
|
|
2106
|
+
# CROSS-REFERENCE: See REQUIREMENTS UPDATE for main execution requirements
|
|
2107
|
+
# CROSS-REFERENCE: See SEMANTIC TOKENS UPDATE for main execution tokens
|
|
2108
|
+
# CROSS-REFERENCE: See ARCHITECTURE UPDATE for main execution architecture
|
|
2109
|
+
# CROSS-REFERENCE: See IMPLEMENTATION UPDATE for main execution implementation
|
|
2110
|
+
# CROSS-REFERENCE: See TEST UPDATES NEEDED for main execution testing
|
|
2111
|
+
# CROSS-REFERENCE: See CODE UPDATES for main execution code changes
|
|
2112
|
+
|
|
2113
|
+
# REQUIREMENTS: Test class for comprehensive testing of all functionality
|
|
2114
|
+
# SEMANTIC TOKENS: TEST_CLASS, TEST_METHODS, TEST_DATA, TEST_ASSERTIONS
|
|
2115
|
+
# ARCHITECTURE: Test architecture with comprehensive coverage
|
|
2116
|
+
# IMPLEMENTATION: Test implementation with all requirements coverage
|
|
2117
|
+
# TEST: Test all functionality with various inputs and edge cases
|
|
2118
|
+
# CROSS-REFERENCE: See REQUIREMENTS UPDATE for testing requirements
|
|
2119
|
+
# CROSS-REFERENCE: See SEMANTIC TOKENS UPDATE for testing tokens
|
|
2120
|
+
# CROSS-REFERENCE: See ARCHITECTURE UPDATE for testing architecture
|
|
2121
|
+
# CROSS-REFERENCE: See IMPLEMENTATION UPDATE for testing implementation
|
|
2122
|
+
# CROSS-REFERENCE: See TEST UPDATES NEEDED for comprehensive testing
|
|
2123
|
+
# CROSS-REFERENCE: See CODE UPDATES for testing code changes
|
|
2124
|
+
|
|
2125
|
+
class TestAnimationToTTS < Minitest::Test
|
|
2126
|
+
# REQUIREMENTS: Test class initialization and configuration
|
|
2127
|
+
# SEMANTIC TOKENS: TEST_INIT, TEST_CONFIG, TEST_SETUP
|
|
2128
|
+
# ARCHITECTURE: Test setup and teardown architecture
|
|
2129
|
+
# IMPLEMENTATION: Test setup with temporary files and data
|
|
2130
|
+
# TEST: Test initialization with various configurations
|
|
2131
|
+
|
|
2132
|
+
def setup
|
|
2133
|
+
# REQUIREMENTS: Setup test environment with temporary files
|
|
2134
|
+
# SEMANTIC TOKENS: TEST_SETUP, TEMPORARY_FILES, TEST_DATA
|
|
2135
|
+
# ARCHITECTURE: Test environment setup architecture
|
|
2136
|
+
# IMPLEMENTATION: Create temporary files and test data
|
|
2137
|
+
# TEST: Test setup with various file configurations
|
|
2138
|
+
|
|
2139
|
+
@temp_dir = Dir.mktmpdir('animation_to_tts_test')
|
|
2140
|
+
@test_files = []
|
|
2141
|
+
@parser = nil
|
|
2142
|
+
end
|
|
2143
|
+
|
|
2144
|
+
def teardown
|
|
2145
|
+
# REQUIREMENTS: Cleanup test environment
|
|
2146
|
+
# SEMANTIC TOKENS: TEST_TEARDOWN, CLEANUP, RESOURCE_MANAGEMENT
|
|
2147
|
+
# ARCHITECTURE: Test cleanup architecture
|
|
2148
|
+
# IMPLEMENTATION: Clean up temporary files and resources
|
|
2149
|
+
# TEST: Test cleanup with various resource states
|
|
2150
|
+
|
|
2151
|
+
FileUtils.rm_rf(@temp_dir) if File.exist?(@temp_dir)
|
|
2152
|
+
end
|
|
2153
|
+
|
|
2154
|
+
def create_test_file(content, filename = nil)
|
|
2155
|
+
# REQUIREMENTS: Create temporary test files with content
|
|
2156
|
+
# SEMANTIC TOKENS: TEST_FILE_CREATE, TEMPORARY_FILES, CONTENT_MANAGEMENT
|
|
2157
|
+
# ARCHITECTURE: Test file creation architecture
|
|
2158
|
+
# IMPLEMENTATION: Create temporary files with specified content
|
|
2159
|
+
# TEST: Test file creation with various content types
|
|
2160
|
+
|
|
2161
|
+
filename ||= "test_#{@test_files.length + 1}.anim"
|
|
2162
|
+
file_path = File.join(@temp_dir, filename)
|
|
2163
|
+
File.write(file_path, content)
|
|
2164
|
+
@test_files << file_path
|
|
2165
|
+
file_path
|
|
2166
|
+
end
|
|
2167
|
+
|
|
2168
|
+
def test_initialization_with_no_files
|
|
2169
|
+
# REQUIREMENTS: Test initialization with no input files
|
|
2170
|
+
# SEMANTIC TOKENS: TEST_INIT, ERROR_HANDLING, INPUT_VALID
|
|
2171
|
+
# ARCHITECTURE: Test error handling architecture
|
|
2172
|
+
# IMPLEMENTATION: Test error handling for missing files
|
|
2173
|
+
# TEST: Test initialization with empty file list
|
|
2174
|
+
|
|
2175
|
+
assert_raises(SystemExit) do
|
|
2176
|
+
AnimationToTTS.new([])
|
|
2177
|
+
end
|
|
2178
|
+
end
|
|
2179
|
+
|
|
2180
|
+
def test_initialization_with_missing_files
|
|
2181
|
+
# REQUIREMENTS: Test initialization with missing files
|
|
2182
|
+
# SEMANTIC TOKENS: TEST_INIT, ERROR_HANDLING, FILE_VALIDATION
|
|
2183
|
+
# ARCHITECTURE: Test file validation architecture
|
|
2184
|
+
# IMPLEMENTATION: Test handling of missing files
|
|
2185
|
+
# TEST: Test initialization with missing files
|
|
2186
|
+
|
|
2187
|
+
missing_file = File.join(@temp_dir, 'missing.anim')
|
|
2188
|
+
parser = AnimationToTTS.new([missing_file])
|
|
2189
|
+
assert_equal [], parser.instance_variable_get(:@input_files)
|
|
2190
|
+
|
|
2191
|
+
# Test that parsing with no valid files raises an error
|
|
2192
|
+
assert_raises(RuntimeError, "No valid input files found") do
|
|
2193
|
+
parser.parse
|
|
2194
|
+
end
|
|
2195
|
+
end
|
|
2196
|
+
|
|
2197
|
+
def test_initialization_with_valid_files
|
|
2198
|
+
# REQUIREMENTS: Test initialization with valid files
|
|
2199
|
+
# SEMANTIC TOKENS: TEST_INIT, FILE_VALIDATION, SUCCESS_HANDLING
|
|
2200
|
+
# ARCHITECTURE: Test successful initialization architecture
|
|
2201
|
+
# IMPLEMENTATION: Test initialization with valid files
|
|
2202
|
+
# TEST: Test initialization with valid file list
|
|
2203
|
+
|
|
2204
|
+
test_file = create_test_file("BOX@(0..1)=red:(0,0)+(10,10) TEXT@=black\"test\"")
|
|
2205
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2206
|
+
assert_equal [test_file], parser.instance_variable_get(:@input_files)
|
|
2207
|
+
end
|
|
2208
|
+
|
|
2209
|
+
def test_parse_single_file_with_explicit_timing
|
|
2210
|
+
# REQUIREMENTS: Test parsing single file with explicit timing
|
|
2211
|
+
# SEMANTIC TOKENS: TEST_PARSING, EXPLICIT_TIMING, SINGLE_FILE
|
|
2212
|
+
# ARCHITECTURE: Test single file parsing architecture
|
|
2213
|
+
# IMPLEMENTATION: Test parsing with explicit timing
|
|
2214
|
+
# TEST: Test parsing with explicit timing specifications
|
|
2215
|
+
|
|
2216
|
+
content = "BOX@(2..4)=pink:(520,10)+(380,30) TEXT@(2..4)=black\"This file is in Markdown format.\""
|
|
2217
|
+
test_file = create_test_file(content)
|
|
2218
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2219
|
+
parser.parse
|
|
2220
|
+
|
|
2221
|
+
segments = parser.instance_variable_get(:@segments)
|
|
2222
|
+
assert_equal 1, segments.length
|
|
2223
|
+
assert_equal 2.0, segments[0]['start_time']
|
|
2224
|
+
assert_equal 4.0, segments[0]['end_time']
|
|
2225
|
+
assert_equal "This file is in Markdown format.", segments[0]['text']
|
|
2226
|
+
assert_equal "black", segments[0]['voice_settings']['color']
|
|
2227
|
+
end
|
|
2228
|
+
|
|
2229
|
+
def test_parse_single_file_with_inherited_timing
|
|
2230
|
+
# REQUIREMENTS: Test parsing single file with inherited timing
|
|
2231
|
+
# SEMANTIC TOKENS: TEST_PARSING, INHERITED_TIMING, BOX_INHERITANCE
|
|
2232
|
+
# ARCHITECTURE: Test timing inheritance architecture
|
|
2233
|
+
# IMPLEMENTATION: Test parsing with inherited timing from BOX
|
|
2234
|
+
# TEST: Test parsing with inherited timing specifications
|
|
2235
|
+
|
|
2236
|
+
content = "BOX@(9..13)=pink:(520,100)+(300,30) TEXT@=black\"This is plain text.\""
|
|
2237
|
+
test_file = create_test_file(content)
|
|
2238
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2239
|
+
parser.parse
|
|
2240
|
+
|
|
2241
|
+
segments = parser.instance_variable_get(:@segments)
|
|
2242
|
+
assert_equal 1, segments.length
|
|
2243
|
+
assert_equal 9.0, segments[0]['start_time']
|
|
2244
|
+
assert_equal 13.0, segments[0]['end_time']
|
|
2245
|
+
assert_equal "This is plain text.", segments[0]['text']
|
|
2246
|
+
assert_equal "black", segments[0]['voice_settings']['color']
|
|
2247
|
+
end
|
|
2248
|
+
|
|
2249
|
+
def test_parse_single_file_with_timing_only
|
|
2250
|
+
# REQUIREMENTS: Test parsing single file with timing only
|
|
2251
|
+
# SEMANTIC TOKENS: TEST_PARSING, TIMING_ONLY, COLOR_INHERITANCE
|
|
2252
|
+
# ARCHITECTURE: Test timing-only parsing architecture
|
|
2253
|
+
# IMPLEMENTATION: Test parsing with timing only, inherit color
|
|
2254
|
+
# TEST: Test parsing with timing-only specifications
|
|
2255
|
+
|
|
2256
|
+
content = "TEXT@(2..4)=black\"First text\"\nTEXT@(2..4)\"Second text\""
|
|
2257
|
+
test_file = create_test_file(content)
|
|
2258
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2259
|
+
parser.parse
|
|
2260
|
+
|
|
2261
|
+
segments = parser.instance_variable_get(:@segments)
|
|
2262
|
+
assert_equal 2, segments.length
|
|
2263
|
+
assert_equal "black", segments[0]['voice_settings']['color']
|
|
2264
|
+
assert_equal "black", segments[1]['voice_settings']['color'] # Inherited
|
|
2265
|
+
end
|
|
2266
|
+
|
|
2267
|
+
def test_parse_single_file_with_text_only
|
|
2268
|
+
# REQUIREMENTS: Test parsing single file with text only
|
|
2269
|
+
# SEMANTIC TOKENS: TEST_PARSING, TEXT_ONLY, FULL_INHERITANCE
|
|
2270
|
+
# ARCHITECTURE: Test text-only parsing architecture
|
|
2271
|
+
# IMPLEMENTATION: Test parsing with text only, inherit timing and color
|
|
2272
|
+
# TEST: Test parsing with text-only specifications
|
|
2273
|
+
|
|
2274
|
+
content = "BOX@(5..8)=blue:(0,0)+(10,10) TEXT@=red\"First text\"\nTEXT@\"Second text\""
|
|
2275
|
+
test_file = create_test_file(content)
|
|
2276
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2277
|
+
parser.parse
|
|
2278
|
+
|
|
2279
|
+
segments = parser.instance_variable_get(:@segments)
|
|
2280
|
+
assert_equal 2, segments.length
|
|
2281
|
+
assert_equal 5.0, segments[1]['start_time'] # Inherited from BOX
|
|
2282
|
+
assert_equal 8.0, segments[1]['end_time'] # Inherited from BOX
|
|
2283
|
+
assert_equal "red", segments[1]['voice_settings']['color'] # Inherited from previous TEXT
|
|
2284
|
+
end
|
|
2285
|
+
|
|
2286
|
+
def test_parse_multiple_files
|
|
2287
|
+
# REQUIREMENTS: Test parsing multiple files
|
|
2288
|
+
# SEMANTIC TOKENS: TEST_PARSING, MULTIPLE_FILES, SEQUENTIAL_PROC
|
|
2289
|
+
# ARCHITECTURE: Test multiple file parsing architecture
|
|
2290
|
+
# IMPLEMENTATION: Test parsing with multiple files
|
|
2291
|
+
# TEST: Test parsing with multiple files
|
|
2292
|
+
|
|
2293
|
+
file1 = create_test_file("TEXT@(0..2)=red\"File 1 text\"", "file1.anim")
|
|
2294
|
+
file2 = create_test_file("TEXT@(2..4)=blue\"File 2 text\"", "file2.anim")
|
|
2295
|
+
parser = AnimationToTTS.new([file1, file2], quiet: true)
|
|
2296
|
+
parser.parse
|
|
2297
|
+
|
|
2298
|
+
segments = parser.instance_variable_get(:@segments)
|
|
2299
|
+
assert_equal 2, segments.length
|
|
2300
|
+
assert_equal "File 1 text", segments[0]['text']
|
|
2301
|
+
assert_equal "File 2 text", segments[1]['text']
|
|
2302
|
+
end
|
|
2303
|
+
|
|
2304
|
+
def test_parse_with_comments_and_empty_lines
|
|
2305
|
+
# REQUIREMENTS: Test parsing with comments and empty lines
|
|
2306
|
+
# SEMANTIC TOKENS: TEST_PARSING, COMMENT_HANDLING, EMPTY_LINE_HANDLING
|
|
2307
|
+
# ARCHITECTURE: Test comment and empty line handling architecture
|
|
2308
|
+
# IMPLEMENTATION: Test parsing with comments and empty lines
|
|
2309
|
+
# TEST: Test parsing with comments and empty lines
|
|
2310
|
+
|
|
2311
|
+
content = "# This is a comment\n\nBOX@(1..3)=red:(0,0)+(10,10)\nTEXT@=black\"Valid text\"\n# Another comment"
|
|
2312
|
+
test_file = create_test_file(content)
|
|
2313
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2314
|
+
parser.parse
|
|
2315
|
+
|
|
2316
|
+
segments = parser.instance_variable_get(:@segments)
|
|
2317
|
+
assert_equal 1, segments.length
|
|
2318
|
+
assert_equal "Valid text", segments[0]['text']
|
|
2319
|
+
end
|
|
2320
|
+
|
|
2321
|
+
def test_parse_with_missing_box_timing
|
|
2322
|
+
# REQUIREMENTS: Test parsing with missing BOX timing
|
|
2323
|
+
# SEMANTIC TOKENS: TEST_PARSING, ERROR_HANDLING, TIMING_VALID
|
|
2324
|
+
# ARCHITECTURE: Test error handling architecture
|
|
2325
|
+
# IMPLEMENTATION: Test error handling for missing BOX timing
|
|
2326
|
+
# TEST: Test parsing with missing BOX timing
|
|
2327
|
+
|
|
2328
|
+
content = "TEXT@=black\"Text without BOX timing\""
|
|
2329
|
+
test_file = create_test_file(content)
|
|
2330
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2331
|
+
|
|
2332
|
+
assert_raises(RuntimeError, /No BOX timing context available/) do
|
|
2333
|
+
parser.parse
|
|
2334
|
+
end
|
|
2335
|
+
end
|
|
2336
|
+
|
|
2337
|
+
def test_voice_settings_calculation
|
|
2338
|
+
# REQUIREMENTS: Test voice settings calculation
|
|
2339
|
+
# SEMANTIC TOKENS: TEST_VOICE_SETTINGS, SPEECH_SPEED, PITCH_MAPPING
|
|
2340
|
+
# ARCHITECTURE: Test voice settings architecture
|
|
2341
|
+
# IMPLEMENTATION: Test voice settings calculation
|
|
2342
|
+
# TEST: Test voice settings calculation
|
|
2343
|
+
|
|
2344
|
+
content = "TEXT@(0..2)=red\"Short text\"\nTEXT@(0..10)=blue\"This is a much longer text that should have different speech speed calculation\""
|
|
2345
|
+
test_file = create_test_file(content)
|
|
2346
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2347
|
+
parser.parse
|
|
2348
|
+
|
|
2349
|
+
segments = parser.instance_variable_get(:@segments)
|
|
2350
|
+
assert_equal 2, segments.length
|
|
2351
|
+
|
|
2352
|
+
# Test speech speed calculation
|
|
2353
|
+
assert segments[0]['voice_settings']['speed'] > 0
|
|
2354
|
+
assert segments[1]['voice_settings']['speed'] > 0
|
|
2355
|
+
|
|
2356
|
+
# Test pitch mapping
|
|
2357
|
+
assert_equal 1.2, segments[0]['voice_settings']['pitch'] # red
|
|
2358
|
+
assert_equal 1.0, segments[1]['voice_settings']['pitch'] # blue
|
|
2359
|
+
end
|
|
2360
|
+
|
|
2361
|
+
def test_gap_calculation
|
|
2362
|
+
# REQUIREMENTS: Test gap calculation between segments
|
|
2363
|
+
# SEMANTIC TOKENS: TEST_GAP_CALC, SILENCE_GAPS, TIMING_ANALYSIS
|
|
2364
|
+
# ARCHITECTURE: Test gap calculation architecture
|
|
2365
|
+
# IMPLEMENTATION: Test gap calculation algorithm
|
|
2366
|
+
# TEST: Test gap calculation with various segment arrangements
|
|
2367
|
+
|
|
2368
|
+
content = "TEXT@(0..2)=red\"First text\"\nTEXT@(5..7)=blue\"Second text\""
|
|
2369
|
+
test_file = create_test_file(content)
|
|
2370
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2371
|
+
parser.parse
|
|
2372
|
+
|
|
2373
|
+
gaps = parser.instance_variable_get(:@gaps)
|
|
2374
|
+
assert_equal 1, gaps.length
|
|
2375
|
+
assert_equal 2.0, gaps[0]['start_time']
|
|
2376
|
+
assert_equal 5.0, gaps[0]['end_time']
|
|
2377
|
+
assert_equal 3.0, gaps[0]['duration']
|
|
2378
|
+
end
|
|
2379
|
+
|
|
2380
|
+
def test_metadata_finalization
|
|
2381
|
+
# REQUIREMENTS: Test metadata finalization
|
|
2382
|
+
# SEMANTIC TOKENS: TEST_METADATA, FINALIZATION, STATISTICS
|
|
2383
|
+
# ARCHITECTURE: Test metadata architecture
|
|
2384
|
+
# IMPLEMENTATION: Test metadata finalization
|
|
2385
|
+
# TEST: Test metadata finalization
|
|
2386
|
+
|
|
2387
|
+
content = "TEXT@(0..2)=red\"First text\"\nTEXT@(3..5)=blue\"Second text\""
|
|
2388
|
+
test_file = create_test_file(content)
|
|
2389
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2390
|
+
parser.parse
|
|
2391
|
+
|
|
2392
|
+
metadata = parser.instance_variable_get(:@metadata)
|
|
2393
|
+
assert_equal 1, metadata['source_files'].length
|
|
2394
|
+
assert_equal 5.0, metadata['total_duration']
|
|
2395
|
+
assert_equal 2, metadata['segment_count']
|
|
2396
|
+
assert metadata['processing_complete']
|
|
2397
|
+
end
|
|
2398
|
+
|
|
2399
|
+
def test_yaml_generation
|
|
2400
|
+
# REQUIREMENTS: Test YAML generation
|
|
2401
|
+
# SEMANTIC TOKENS: TEST_YAML_GEN, OUTPUT_FORMATTING, DATA_STRUCTURE
|
|
2402
|
+
# ARCHITECTURE: Test YAML generation architecture
|
|
2403
|
+
# IMPLEMENTATION: Test YAML generation
|
|
2404
|
+
# TEST: Test YAML generation
|
|
2405
|
+
|
|
2406
|
+
content = "TEXT@(0..2)=red\"Test text\""
|
|
2407
|
+
test_file = create_test_file(content)
|
|
2408
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2409
|
+
parser.parse
|
|
2410
|
+
|
|
2411
|
+
yaml_output = parser.generate_yaml
|
|
2412
|
+
yaml_data = YAML.load(yaml_output)
|
|
2413
|
+
|
|
2414
|
+
assert yaml_data.key?('metadata')
|
|
2415
|
+
assert yaml_data.key?('audio_segments')
|
|
2416
|
+
assert yaml_data.key?('gaps')
|
|
2417
|
+
assert_equal 1, yaml_data['audio_segments'].length
|
|
2418
|
+
end
|
|
2419
|
+
|
|
2420
|
+
def test_audio_segment_creation
|
|
2421
|
+
# REQUIREMENTS: Test audio segment creation
|
|
2422
|
+
# SEMANTIC TOKENS: TEST_AUDIO_SEGMENT, SEGMENT_CREATE, AUDIO_METADATA
|
|
2423
|
+
# ARCHITECTURE: Test audio segment architecture
|
|
2424
|
+
# IMPLEMENTATION: Test audio segment creation
|
|
2425
|
+
# TEST: Test audio segment creation
|
|
2426
|
+
|
|
2427
|
+
content = "TEXT@(1..3)=green\"Test audio segment\""
|
|
2428
|
+
test_file = create_test_file(content)
|
|
2429
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2430
|
+
parser.parse
|
|
2431
|
+
|
|
2432
|
+
segments = parser.instance_variable_get(:@segments)
|
|
2433
|
+
segment = segments[0]
|
|
2434
|
+
|
|
2435
|
+
assert_equal "text_1", segment['id']
|
|
2436
|
+
assert_equal 1.0, segment['start_time']
|
|
2437
|
+
assert_equal 3.0, segment['end_time']
|
|
2438
|
+
assert_equal 2.0, segment['duration']
|
|
2439
|
+
assert_equal "Test audio segment", segment['text']
|
|
2440
|
+
assert_equal "green", segment['voice_settings']['color']
|
|
2441
|
+
assert segment['voice_settings']['speed'] > 0
|
|
2442
|
+
assert segment['voice_settings']['volume'] > 0
|
|
2443
|
+
assert segment['voice_settings']['pitch'] > 0
|
|
2444
|
+
assert_equal "audio/text_1.wav", segment['file_path']
|
|
2445
|
+
assert_equal test_file, segment['source']['file']
|
|
2446
|
+
assert_equal 1, segment['source']['line']
|
|
2447
|
+
end
|
|
2448
|
+
|
|
2449
|
+
def test_speech_speed_calculation
|
|
2450
|
+
# REQUIREMENTS: Test speech speed calculation
|
|
2451
|
+
# SEMANTIC TOKENS: TEST_SPEECH_SPEED, SPEED_CALC, TIMING_OPTIMIZATION
|
|
2452
|
+
# ARCHITECTURE: Test speech speed architecture
|
|
2453
|
+
# IMPLEMENTATION: Test speech speed calculation
|
|
2454
|
+
# TEST: Test speech speed calculation
|
|
2455
|
+
|
|
2456
|
+
content = "TEXT@(0..1)=red\"Short\"\nTEXT@(1..11)=blue\"This is a much longer text that should have different speech speed calculation\""
|
|
2457
|
+
test_file = create_test_file(content)
|
|
2458
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2459
|
+
parser.parse
|
|
2460
|
+
|
|
2461
|
+
segments = parser.instance_variable_get(:@segments)
|
|
2462
|
+
|
|
2463
|
+
# Short text with short duration should have higher speed
|
|
2464
|
+
speed1 = segments[0]['voice_settings']['speed']
|
|
2465
|
+
speed2 = segments[1]['voice_settings']['speed']
|
|
2466
|
+
puts "DEBUG: Speed 1 (short): #{speed1}, Speed 2 (long): #{speed2}"
|
|
2467
|
+
assert speed1 > speed2, "Short text speed (#{speed1}) should be higher than long text speed (#{speed2})"
|
|
2468
|
+
|
|
2469
|
+
# Speed should be within reasonable bounds
|
|
2470
|
+
assert segments[0]['voice_settings']['speed'] >= 0.5
|
|
2471
|
+
assert segments[0]['voice_settings']['speed'] <= 2.0
|
|
2472
|
+
assert segments[1]['voice_settings']['speed'] >= 0.5
|
|
2473
|
+
assert segments[1]['voice_settings']['speed'] <= 2.0
|
|
2474
|
+
end
|
|
2475
|
+
|
|
2476
|
+
def test_pitch_mapping
|
|
2477
|
+
# REQUIREMENTS: Test pitch mapping from colors
|
|
2478
|
+
# SEMANTIC TOKENS: TEST_PITCH_MAPPING, COLOR_MAPPING, VOICE_CUSTOMIZATION
|
|
2479
|
+
# ARCHITECTURE: Test pitch mapping architecture
|
|
2480
|
+
# IMPLEMENTATION: Test pitch mapping from colors
|
|
2481
|
+
# TEST: Test pitch mapping from colors
|
|
2482
|
+
|
|
2483
|
+
content = "TEXT@(0..1)=red\"Red text\"\nTEXT@(0..1)=blue\"Blue text\"\nTEXT@(0..1)=green\"Green text\""
|
|
2484
|
+
test_file = create_test_file(content)
|
|
2485
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2486
|
+
parser.parse
|
|
2487
|
+
|
|
2488
|
+
segments = parser.instance_variable_get(:@segments)
|
|
2489
|
+
|
|
2490
|
+
assert_equal 1.2, segments[0]['voice_settings']['pitch'] # red
|
|
2491
|
+
assert_equal 1.0, segments[1]['voice_settings']['pitch'] # blue
|
|
2492
|
+
assert_equal 0.9, segments[2]['voice_settings']['pitch'] # green
|
|
2493
|
+
end
|
|
2494
|
+
|
|
2495
|
+
def test_overlapping_segments
|
|
2496
|
+
# REQUIREMENTS: Test overlapping segments handling
|
|
2497
|
+
# SEMANTIC TOKENS: TEST_OVERLAPPING, SEGMENT_OVERLAP, TIMING_CONFLICT
|
|
2498
|
+
# ARCHITECTURE: Test overlapping segments architecture
|
|
2499
|
+
# IMPLEMENTATION: Test overlapping segments handling
|
|
2500
|
+
# TEST: Test overlapping segments handling
|
|
2501
|
+
|
|
2502
|
+
content = "TEXT@(0..3)=red\"First text\"\nTEXT@(2..5)=blue\"Second text\""
|
|
2503
|
+
test_file = create_test_file(content)
|
|
2504
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2505
|
+
parser.parse
|
|
2506
|
+
|
|
2507
|
+
segments = parser.instance_variable_get(:@segments)
|
|
2508
|
+
assert_equal 2, segments.length
|
|
2509
|
+
|
|
2510
|
+
# Both segments should be created despite overlap
|
|
2511
|
+
assert_equal 0.0, segments[0]['start_time']
|
|
2512
|
+
assert_equal 3.0, segments[0]['end_time']
|
|
2513
|
+
assert_equal 2.0, segments[1]['start_time']
|
|
2514
|
+
assert_equal 5.0, segments[1]['end_time']
|
|
2515
|
+
end
|
|
2516
|
+
|
|
2517
|
+
def test_initial_gap_calculation
|
|
2518
|
+
# REQUIREMENTS: Test initial gap calculation
|
|
2519
|
+
# SEMANTIC TOKENS: TEST_INITIAL_GAP, GAP_CALC, TIMING_ANALYSIS
|
|
2520
|
+
# ARCHITECTURE: Test initial gap architecture
|
|
2521
|
+
# IMPLEMENTATION: Test initial gap calculation
|
|
2522
|
+
# TEST: Test initial gap calculation
|
|
2523
|
+
|
|
2524
|
+
content = "TEXT@(2..4)=red\"Text starting at 2 seconds\""
|
|
2525
|
+
test_file = create_test_file(content)
|
|
2526
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2527
|
+
parser.parse
|
|
2528
|
+
|
|
2529
|
+
gaps = parser.instance_variable_get(:@gaps)
|
|
2530
|
+
assert_equal 1, gaps.length
|
|
2531
|
+
assert_equal 0.0, gaps[0]['start_time']
|
|
2532
|
+
assert_equal 2.0, gaps[0]['end_time']
|
|
2533
|
+
assert_equal 2.0, gaps[0]['duration']
|
|
2534
|
+
end
|
|
2535
|
+
|
|
2536
|
+
def test_complete_pipeline
|
|
2537
|
+
# REQUIREMENTS: Test complete pipeline from parsing to YAML generation
|
|
2538
|
+
# SEMANTIC TOKENS: TEST_PIPELINE, COMPLETE_WORKFLOW, END_TO_END
|
|
2539
|
+
# ARCHITECTURE: Test complete pipeline architecture
|
|
2540
|
+
# IMPLEMENTATION: Test complete pipeline
|
|
2541
|
+
# TEST: Test complete pipeline
|
|
2542
|
+
|
|
2543
|
+
content = "BOX@(0..2)=red:(0,0)+(10,10)\nTEXT@=black\"First text\"\nTEXT@(3..5)=blue\"Second text\""
|
|
2544
|
+
test_file = create_test_file(content)
|
|
2545
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2546
|
+
parser.parse
|
|
2547
|
+
|
|
2548
|
+
yaml_output = parser.generate_yaml
|
|
2549
|
+
yaml_data = YAML.load(yaml_output)
|
|
2550
|
+
|
|
2551
|
+
# Test metadata
|
|
2552
|
+
assert yaml_data['metadata']['processing_complete']
|
|
2553
|
+
assert_equal 1, yaml_data['metadata']['source_files'].length
|
|
2554
|
+
|
|
2555
|
+
# Test segments
|
|
2556
|
+
assert_equal 2, yaml_data['audio_segments'].length
|
|
2557
|
+
assert_equal "First text", yaml_data['audio_segments'][0]['text']
|
|
2558
|
+
assert_equal "Second text", yaml_data['audio_segments'][1]['text']
|
|
2559
|
+
|
|
2560
|
+
# Test gaps
|
|
2561
|
+
assert yaml_data['gaps'].length >= 0
|
|
2562
|
+
end
|
|
2563
|
+
|
|
2564
|
+
def test_error_handling_with_invalid_content
|
|
2565
|
+
# REQUIREMENTS: Test error handling with invalid content
|
|
2566
|
+
# SEMANTIC TOKENS: TEST_ERROR_HANDLING, INVALID_CONTENT, ERROR_RECOVERY
|
|
2567
|
+
# ARCHITECTURE: Test error handling architecture
|
|
2568
|
+
# IMPLEMENTATION: Test error handling with invalid content
|
|
2569
|
+
# TEST: Test error handling with invalid content
|
|
2570
|
+
|
|
2571
|
+
content = "INVALID_FORMAT_CONTENT"
|
|
2572
|
+
test_file = create_test_file(content)
|
|
2573
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2574
|
+
|
|
2575
|
+
# Should not raise error, just skip invalid lines
|
|
2576
|
+
parser.parse
|
|
2577
|
+
|
|
2578
|
+
segments = parser.instance_variable_get(:@segments)
|
|
2579
|
+
assert_equal 0, segments.length
|
|
2580
|
+
end
|
|
2581
|
+
|
|
2582
|
+
def test_file_separator_handling
|
|
2583
|
+
# REQUIREMENTS: Test file separator handling
|
|
2584
|
+
# SEMANTIC TOKENS: TEST_FILE_SEPARATOR, MULTIPLE_FILES, SEPARATOR_HANDLING
|
|
2585
|
+
# ARCHITECTURE: Test file separator architecture
|
|
2586
|
+
# IMPLEMENTATION: Test file separator handling
|
|
2587
|
+
# TEST: Test file separator handling
|
|
2588
|
+
|
|
2589
|
+
file1 = create_test_file("TEXT@(0..1)=red\"File 1\"", "file1.anim")
|
|
2590
|
+
file2 = create_test_file("TEXT@(1..2)=blue\"File 2\"", "file2.anim")
|
|
2591
|
+
parser = AnimationToTTS.new([file1, file2], quiet: true)
|
|
2592
|
+
parser.parse
|
|
2593
|
+
|
|
2594
|
+
segments = parser.instance_variable_get(:@segments)
|
|
2595
|
+
assert_equal 2, segments.length
|
|
2596
|
+
assert_equal "File 1", segments[0]['text']
|
|
2597
|
+
assert_equal "File 2", segments[1]['text']
|
|
2598
|
+
end
|
|
2599
|
+
|
|
2600
|
+
def test_index_continuity_across_files
|
|
2601
|
+
# REQUIREMENTS: Test index continuity across files
|
|
2602
|
+
# SEMANTIC TOKENS: TEST_INDEX_CONTINUITY, SEQUENTIAL_INDEXING, CROSS_FILE_INDEXING
|
|
2603
|
+
# ARCHITECTURE: Test index continuity architecture
|
|
2604
|
+
# IMPLEMENTATION: Test index continuity across files
|
|
2605
|
+
# TEST: Test index continuity across files
|
|
2606
|
+
|
|
2607
|
+
file1 = create_test_file("TEXT@(0..1)=red\"File 1 text\"", "file1.anim")
|
|
2608
|
+
file2 = create_test_file("TEXT@(1..2)=blue\"File 2 text\"", "file2.anim")
|
|
2609
|
+
parser = AnimationToTTS.new([file1, file2], quiet: true)
|
|
2610
|
+
parser.parse
|
|
2611
|
+
|
|
2612
|
+
segments = parser.instance_variable_get(:@segments)
|
|
2613
|
+
assert_equal 2, segments.length
|
|
2614
|
+
assert_equal "text_1", segments[0]['id']
|
|
2615
|
+
assert_equal "text_2", segments[1]['id']
|
|
2616
|
+
end
|
|
2617
|
+
|
|
2618
|
+
def test_voice_settings_validation
|
|
2619
|
+
# REQUIREMENTS: Test voice settings validation
|
|
2620
|
+
# SEMANTIC TOKENS: TEST_VOICE_SETTINGS, SETTINGS_VALID, QUALITY_ASSURANCE
|
|
2621
|
+
# ARCHITECTURE: Test voice settings validation architecture
|
|
2622
|
+
# IMPLEMENTATION: Test voice settings validation
|
|
2623
|
+
# TEST: Test voice settings validation
|
|
2624
|
+
|
|
2625
|
+
content = "TEXT@(0..1)=red\"Test voice settings\""
|
|
2626
|
+
test_file = create_test_file(content)
|
|
2627
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2628
|
+
parser.parse
|
|
2629
|
+
|
|
2630
|
+
segments = parser.instance_variable_get(:@segments)
|
|
2631
|
+
segment = segments[0]
|
|
2632
|
+
voice_settings = segment['voice_settings']
|
|
2633
|
+
|
|
2634
|
+
# Test all voice settings are present and valid
|
|
2635
|
+
assert voice_settings.key?('color')
|
|
2636
|
+
assert voice_settings.key?('speed')
|
|
2637
|
+
assert voice_settings.key?('volume')
|
|
2638
|
+
assert voice_settings.key?('pitch')
|
|
2639
|
+
|
|
2640
|
+
assert voice_settings['speed'] > 0
|
|
2641
|
+
assert voice_settings['volume'] > 0
|
|
2642
|
+
assert voice_settings['pitch'] > 0
|
|
2643
|
+
end
|
|
2644
|
+
|
|
2645
|
+
def test_audio_metadata_generation
|
|
2646
|
+
# REQUIREMENTS: Test audio metadata generation
|
|
2647
|
+
# SEMANTIC TOKENS: TEST_AUDIO_METADATA, METADATA_GEN, AUDIO_PROPERTIES
|
|
2648
|
+
# ARCHITECTURE: Test audio metadata architecture
|
|
2649
|
+
# IMPLEMENTATION: Test audio metadata generation
|
|
2650
|
+
# TEST: Test audio metadata generation
|
|
2651
|
+
|
|
2652
|
+
content = "TEXT@(0..2)=red\"Test metadata\""
|
|
2653
|
+
test_file = create_test_file(content)
|
|
2654
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2655
|
+
parser.parse
|
|
2656
|
+
|
|
2657
|
+
segments = parser.instance_variable_get(:@segments)
|
|
2658
|
+
segment = segments[0]
|
|
2659
|
+
|
|
2660
|
+
# Test audio metadata
|
|
2661
|
+
assert segment.key?('file_path')
|
|
2662
|
+
assert segment.key?('source')
|
|
2663
|
+
assert segment['source'].key?('file')
|
|
2664
|
+
assert segment['source'].key?('line')
|
|
2665
|
+
assert_equal test_file, segment['source']['file']
|
|
2666
|
+
assert_equal 1, segment['source']['line']
|
|
2667
|
+
end
|
|
2668
|
+
|
|
2669
|
+
def test_yaml_structure_validation
|
|
2670
|
+
# REQUIREMENTS: Test YAML structure validation
|
|
2671
|
+
# SEMANTIC TOKENS: TEST_YAML_STRUCTURE, STRUCTURE_VALID, FORMAT_VALID
|
|
2672
|
+
# ARCHITECTURE: Test YAML structure architecture
|
|
2673
|
+
# IMPLEMENTATION: Test YAML structure validation
|
|
2674
|
+
# TEST: Test YAML structure validation
|
|
2675
|
+
|
|
2676
|
+
content = "TEXT@(0..1)=red\"Test YAML structure\""
|
|
2677
|
+
test_file = create_test_file(content)
|
|
2678
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2679
|
+
parser.parse
|
|
2680
|
+
|
|
2681
|
+
yaml_output = parser.generate_yaml
|
|
2682
|
+
refute_nil yaml_output, "YAML output should not be nil"
|
|
2683
|
+
yaml_data = YAML.load(yaml_output)
|
|
2684
|
+
|
|
2685
|
+
# Test YAML structure
|
|
2686
|
+
assert yaml_data.is_a?(Hash)
|
|
2687
|
+
assert yaml_data.key?('metadata')
|
|
2688
|
+
assert yaml_data.key?('audio_segments')
|
|
2689
|
+
assert yaml_data.key?('gaps')
|
|
2690
|
+
|
|
2691
|
+
# Test metadata structure
|
|
2692
|
+
metadata = yaml_data['metadata']
|
|
2693
|
+
assert metadata.key?('generated_at')
|
|
2694
|
+
assert metadata.key?('source_files')
|
|
2695
|
+
assert metadata.key?('total_duration')
|
|
2696
|
+
assert metadata.key?('segment_count')
|
|
2697
|
+
assert metadata.key?('gap_count')
|
|
2698
|
+
assert metadata.key?('processing_complete')
|
|
2699
|
+
end
|
|
2700
|
+
|
|
2701
|
+
def test_performance_with_large_content
|
|
2702
|
+
# REQUIREMENTS: Test performance with large content
|
|
2703
|
+
# SEMANTIC TOKENS: TEST_PERFORMANCE, LARGE_CONTENT, PERFORMANCE_VALID
|
|
2704
|
+
# ARCHITECTURE: Test performance architecture
|
|
2705
|
+
# IMPLEMENTATION: Test performance with large content
|
|
2706
|
+
# TEST: Test performance with large content
|
|
2707
|
+
|
|
2708
|
+
# Generate large content
|
|
2709
|
+
content = ""
|
|
2710
|
+
100.times do |i|
|
|
2711
|
+
content += "TEXT@(#{i}..#{i+1})=red\"Text segment #{i}\"\n"
|
|
2712
|
+
end
|
|
2713
|
+
|
|
2714
|
+
test_file = create_test_file(content)
|
|
2715
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
2716
|
+
|
|
2717
|
+
start_time = Time.now
|
|
2718
|
+
parser.parse
|
|
2719
|
+
end_time = Time.now
|
|
2720
|
+
|
|
2721
|
+
# Should complete within reasonable time (adjust threshold as needed)
|
|
2722
|
+
assert (end_time - start_time) < 5.0
|
|
2723
|
+
|
|
2724
|
+
segments = parser.instance_variable_get(:@segments)
|
|
2725
|
+
assert_equal 100, segments.length
|
|
2726
|
+
end
|
|
2727
|
+
|
|
2728
|
+
# TTS Engine Tests
|
|
2729
|
+
# REQUIREMENTS: Test TTS engine abstraction and backend selection
|
|
2730
|
+
# SEMANTIC TOKENS: TTS_ENGINE_TESTS, BACKEND_SELECTION, ENGINE_TESTING
|
|
2731
|
+
# ARCHITECTURE: TTS engine testing architecture
|
|
2732
|
+
# IMPLEMENTATION: Test TTS engine functionality and backend selection
|
|
2733
|
+
# TEST: Test TTS engine abstraction and backend selection
|
|
2734
|
+
def test_tts_engine_interface
|
|
2735
|
+
# REQUIREMENTS: Test TTS engine interface methods
|
|
2736
|
+
# SEMANTIC TOKENS: TTS_ENGINE_IFACE, METHOD_TESTING
|
|
2737
|
+
# ARCHITECTURE: TTS engine interface testing
|
|
2738
|
+
# IMPLEMENTATION: Test abstract TTS engine interface
|
|
2739
|
+
# TEST: Test TTS engine interface methods
|
|
2740
|
+
engine = TTSEngine.new
|
|
2741
|
+
|
|
2742
|
+
assert_raises(NotImplementedError) { engine.generate_audio("test") }
|
|
2743
|
+
assert_raises(NotImplementedError) { engine.available? }
|
|
2744
|
+
assert_equal ['wav'], engine.supported_formats
|
|
2745
|
+
assert_equal 'TTSEngine', engine.name
|
|
2746
|
+
end
|
|
2747
|
+
|
|
2748
|
+
def test_system_tts_engine_initialization
|
|
2749
|
+
# REQUIREMENTS: Test system TTS engine initialization
|
|
2750
|
+
# SEMANTIC TOKENS: SYSTEM_TTS_INIT, ENGINE_SETUP
|
|
2751
|
+
# ARCHITECTURE: System TTS engine initialization testing
|
|
2752
|
+
# IMPLEMENTATION: Test system TTS engine creation
|
|
2753
|
+
# TEST: Test system TTS engine initialization
|
|
2754
|
+
temp_dir = Dir.mktmpdir
|
|
2755
|
+
|
|
2756
|
+
begin
|
|
2757
|
+
engine = SystemTTSEngine.new(temp_dir: temp_dir)
|
|
2758
|
+
refute_nil engine
|
|
2759
|
+
assert_equal temp_dir, engine.instance_variable_get(:@temp_dir)
|
|
2760
|
+
ensure
|
|
2761
|
+
FileUtils.rm_rf(temp_dir)
|
|
2762
|
+
end
|
|
2763
|
+
end
|
|
2764
|
+
|
|
2765
|
+
def test_tts_engine_factory_creation
|
|
2766
|
+
# REQUIREMENTS: Test TTS engine factory creation
|
|
2767
|
+
# SEMANTIC TOKENS: TTS_ENGINE_FACTORY, FACTORY_CREATE
|
|
2768
|
+
# ARCHITECTURE: TTS engine factory testing
|
|
2769
|
+
# IMPLEMENTATION: Test TTS engine factory pattern
|
|
2770
|
+
# TEST: Test TTS engine factory creation
|
|
2771
|
+
temp_dir = Dir.mktmpdir
|
|
2772
|
+
|
|
2773
|
+
begin
|
|
2774
|
+
# Test auto-detection
|
|
2775
|
+
engine = TTSEngineFactory.create('auto', temp_dir: temp_dir)
|
|
2776
|
+
refute_nil engine
|
|
2777
|
+
assert_instance_of SystemTTSEngine, engine
|
|
2778
|
+
|
|
2779
|
+
# Test specific backend creation (if available)
|
|
2780
|
+
available_backends = TTSEngineFactory.available_backends
|
|
2781
|
+
if available_backends.any?
|
|
2782
|
+
backend = available_backends.first
|
|
2783
|
+
engine = TTSEngineFactory.create(backend, temp_dir: temp_dir)
|
|
2784
|
+
refute_nil engine
|
|
2785
|
+
assert_instance_of SystemTTSEngine, engine
|
|
2786
|
+
end
|
|
2787
|
+
ensure
|
|
2788
|
+
FileUtils.rm_rf(temp_dir)
|
|
2789
|
+
end
|
|
2790
|
+
end
|
|
2791
|
+
|
|
2792
|
+
def test_tts_engine_available_backends
|
|
2793
|
+
# REQUIREMENTS: Test TTS engine available backends listing
|
|
2794
|
+
# SEMANTIC TOKENS: BACKEND_LISTING, AVAILABILITY_CHECK
|
|
2795
|
+
# ARCHITECTURE: TTS backend listing testing
|
|
2796
|
+
# IMPLEMENTATION: Test backend availability detection
|
|
2797
|
+
# TEST: Test TTS engine available backends
|
|
2798
|
+
backends = TTSEngineFactory.available_backends
|
|
2799
|
+
assert_instance_of Array, backends
|
|
2800
|
+
|
|
2801
|
+
# Should contain at least one backend on most systems
|
|
2802
|
+
# (say is available on macOS, espeak on Linux)
|
|
2803
|
+
assert backends.length >= 0, "Should return array of backends (may be empty)"
|
|
2804
|
+
end
|
|
2805
|
+
|
|
2806
|
+
def test_system_tts_engine_voice_settings
|
|
2807
|
+
# REQUIREMENTS: Test system TTS engine voice settings
|
|
2808
|
+
# SEMANTIC TOKENS: VOICE_SETTINGS_APP, TTS_CUSTOMIZATION
|
|
2809
|
+
# ARCHITECTURE: Voice settings testing architecture
|
|
2810
|
+
# IMPLEMENTATION: Test voice settings application
|
|
2811
|
+
# TEST: Test voice settings application (speed, pitch, volume)
|
|
2812
|
+
temp_dir = Dir.mktmpdir
|
|
2813
|
+
|
|
2814
|
+
begin
|
|
2815
|
+
engine = SystemTTSEngine.new(temp_dir: temp_dir)
|
|
2816
|
+
|
|
2817
|
+
# Test voice settings initialization
|
|
2818
|
+
voice_settings = engine.instance_variable_get(:@voice_settings)
|
|
2819
|
+
assert_equal 1.0, voice_settings[:speed]
|
|
2820
|
+
assert_equal 1.0, voice_settings[:pitch]
|
|
2821
|
+
assert_equal 0.8, voice_settings[:volume]
|
|
2822
|
+
|
|
2823
|
+
# Test voice settings merging
|
|
2824
|
+
custom_settings = { speed: 1.5, pitch: 1.2, volume: 0.9 }
|
|
2825
|
+
merged_settings = voice_settings.merge(custom_settings)
|
|
2826
|
+
assert_equal 1.5, merged_settings[:speed]
|
|
2827
|
+
assert_equal 1.2, merged_settings[:pitch]
|
|
2828
|
+
assert_equal 0.9, merged_settings[:volume]
|
|
2829
|
+
ensure
|
|
2830
|
+
FileUtils.rm_rf(temp_dir)
|
|
2831
|
+
end
|
|
2832
|
+
end
|
|
2833
|
+
|
|
2834
|
+
def test_system_tts_engine_supported_formats
|
|
2835
|
+
# REQUIREMENTS: Test system TTS engine supported formats
|
|
2836
|
+
# SEMANTIC TOKENS: AUDIO_FORMAT_SUPPORT, FORMAT_TESTING
|
|
2837
|
+
# ARCHITECTURE: Audio format support testing
|
|
2838
|
+
# IMPLEMENTATION: Test supported audio formats
|
|
2839
|
+
# TEST: Test audio format support (WAV, MP3)
|
|
2840
|
+
temp_dir = Dir.mktmpdir
|
|
2841
|
+
|
|
2842
|
+
begin
|
|
2843
|
+
engine = SystemTTSEngine.new(temp_dir: temp_dir)
|
|
2844
|
+
formats = engine.supported_formats
|
|
2845
|
+
|
|
2846
|
+
assert_instance_of Array, formats
|
|
2847
|
+
assert formats.include?('wav'), "Should support WAV format"
|
|
2848
|
+
|
|
2849
|
+
# Test different backends have different format support
|
|
2850
|
+
if engine.instance_variable_get(:@backend) == 'espeak'
|
|
2851
|
+
assert formats.include?('mp3'), "espeak should support MP3"
|
|
2852
|
+
elsif engine.instance_variable_get(:@backend) == 'say'
|
|
2853
|
+
assert formats.include?('aiff'), "say should support AIFF"
|
|
2854
|
+
end
|
|
2855
|
+
ensure
|
|
2856
|
+
FileUtils.rm_rf(temp_dir)
|
|
2857
|
+
end
|
|
2858
|
+
end
|
|
2859
|
+
|
|
2860
|
+
def test_system_tts_engine_availability
|
|
2861
|
+
# REQUIREMENTS: Test system TTS engine availability checking
|
|
2862
|
+
# SEMANTIC TOKENS: TTS_ENGINE_AVAIL, BACKEND_CHECK
|
|
2863
|
+
# ARCHITECTURE: TTS engine availability testing
|
|
2864
|
+
# IMPLEMENTATION: Test TTS engine availability detection
|
|
2865
|
+
# TEST: Test TTS engine availability checking
|
|
2866
|
+
temp_dir = Dir.mktmpdir
|
|
2867
|
+
|
|
2868
|
+
begin
|
|
2869
|
+
engine = SystemTTSEngine.new(temp_dir: temp_dir)
|
|
2870
|
+
available = engine.available?
|
|
2871
|
+
|
|
2872
|
+
# Should return boolean
|
|
2873
|
+
assert [true, false].include?(available), "Should return boolean availability"
|
|
2874
|
+
|
|
2875
|
+
# If available, should be able to generate audio
|
|
2876
|
+
if available
|
|
2877
|
+
# Test that we can generate a simple audio file
|
|
2878
|
+
test_text = "Hello world"
|
|
2879
|
+
voice_settings = { speed: 1.0, pitch: 1.0, volume: 0.8 }
|
|
2880
|
+
|
|
2881
|
+
# This might fail if TTS backend is not properly configured
|
|
2882
|
+
# but we should at least not get a NotImplementedError
|
|
2883
|
+
begin
|
|
2884
|
+
audio_file = engine.generate_audio(test_text, voice_settings)
|
|
2885
|
+
assert File.exist?(audio_file), "Should generate audio file"
|
|
2886
|
+
assert File.size(audio_file) > 0, "Audio file should not be empty"
|
|
2887
|
+
rescue => e
|
|
2888
|
+
# If TTS fails, it should be a specific error, not NotImplementedError
|
|
2889
|
+
refute_equal NotImplementedError, e.class, "Should not raise NotImplementedError"
|
|
2890
|
+
end
|
|
2891
|
+
end
|
|
2892
|
+
ensure
|
|
2893
|
+
FileUtils.rm_rf(temp_dir)
|
|
2894
|
+
end
|
|
2895
|
+
end
|
|
2896
|
+
|
|
2897
|
+
def test_tts_engine_error_handling
|
|
2898
|
+
# REQUIREMENTS: Test TTS engine error handling
|
|
2899
|
+
# SEMANTIC TOKENS: ERROR_HANDLING_TESTS, TTS_ERRORS
|
|
2900
|
+
# ARCHITECTURE: TTS engine error handling testing
|
|
2901
|
+
# IMPLEMENTATION: Test error handling for TTS failures
|
|
2902
|
+
# TEST: Test error handling for audio generation failures
|
|
2903
|
+
temp_dir = Dir.mktmpdir
|
|
2904
|
+
|
|
2905
|
+
begin
|
|
2906
|
+
engine = SystemTTSEngine.new(temp_dir: temp_dir)
|
|
2907
|
+
|
|
2908
|
+
# Test with invalid backend
|
|
2909
|
+
invalid_engine = SystemTTSEngine.new(backend: 'nonexistent')
|
|
2910
|
+
assert_equal false, invalid_engine.available?
|
|
2911
|
+
|
|
2912
|
+
# Test error handling for unsupported backend
|
|
2913
|
+
assert_raises(RuntimeError, "Unsupported TTS backend: nonexistent") do
|
|
2914
|
+
invalid_engine.generate_audio("test")
|
|
2915
|
+
end
|
|
2916
|
+
ensure
|
|
2917
|
+
FileUtils.rm_rf(temp_dir)
|
|
2918
|
+
end
|
|
2919
|
+
end
|
|
2920
|
+
|
|
2921
|
+
def test_tts_engine_factory_error_handling
|
|
2922
|
+
# REQUIREMENTS: Test TTS engine factory error handling
|
|
2923
|
+
# SEMANTIC TOKENS: FACTORY_ERROR_HANDLING, BACKEND_ERRORS
|
|
2924
|
+
# ARCHITECTURE: TTS engine factory error handling testing
|
|
2925
|
+
# IMPLEMENTATION: Test factory error handling
|
|
2926
|
+
# TEST: Test TTS engine factory error handling
|
|
2927
|
+
assert_raises(RuntimeError, "Unsupported TTS backend: invalid") do
|
|
2928
|
+
TTSEngineFactory.create('invalid')
|
|
2929
|
+
end
|
|
2930
|
+
end
|
|
2931
|
+
|
|
2932
|
+
# Audio Segment Generator Tests
|
|
2933
|
+
# REQUIREMENTS: Test individual audio segment generation
|
|
2934
|
+
# SEMANTIC TOKENS: SEGMENT_GEN_TESTS, AUDIO_SEGMENT_TESTING
|
|
2935
|
+
# ARCHITECTURE: Audio segment generation testing architecture
|
|
2936
|
+
# IMPLEMENTATION: Test audio segment generation functionality
|
|
2937
|
+
# TEST: Test individual audio segment generation
|
|
2938
|
+
def test_audio_segment_generator_initialization
|
|
2939
|
+
# REQUIREMENTS: Test audio segment generator initialization
|
|
2940
|
+
# SEMANTIC TOKENS: SEGMENT_GENERATOR_INIT, GENERATOR_SETUP
|
|
2941
|
+
# ARCHITECTURE: Audio segment generator initialization testing
|
|
2942
|
+
# IMPLEMENTATION: Test audio segment generator creation
|
|
2943
|
+
# TEST: Test audio segment generator initialization
|
|
2944
|
+
temp_dir = Dir.mktmpdir
|
|
2945
|
+
|
|
2946
|
+
begin
|
|
2947
|
+
tts_engine = TTSEngineFactory.create('auto', temp_dir: temp_dir)
|
|
2948
|
+
generator = AudioSegmentGenerator.new(tts_engine, temp_dir: temp_dir)
|
|
2949
|
+
|
|
2950
|
+
refute_nil generator
|
|
2951
|
+
assert_equal tts_engine, generator.instance_variable_get(:@tts_engine)
|
|
2952
|
+
assert_equal temp_dir, generator.instance_variable_get(:@temp_dir)
|
|
2953
|
+
assert_equal 'wav', generator.instance_variable_get(:@output_format)
|
|
2954
|
+
assert_equal [], generator.generated_segments
|
|
2955
|
+
ensure
|
|
2956
|
+
FileUtils.rm_rf(temp_dir)
|
|
2957
|
+
end
|
|
2958
|
+
end
|
|
2959
|
+
|
|
2960
|
+
def test_audio_segment_generator_voice_settings_extraction
|
|
2961
|
+
# REQUIREMENTS: Test voice settings extraction from segment data
|
|
2962
|
+
# SEMANTIC TOKENS: VOICE_SETTINGS_EXTRACT, SEGMENT_DATA_PROC
|
|
2963
|
+
# ARCHITECTURE: Voice settings extraction testing
|
|
2964
|
+
# IMPLEMENTATION: Test voice settings extraction functionality
|
|
2965
|
+
# TEST: Test voice settings extraction and processing
|
|
2966
|
+
temp_dir = Dir.mktmpdir
|
|
2967
|
+
|
|
2968
|
+
begin
|
|
2969
|
+
tts_engine = TTSEngineFactory.create('auto', temp_dir: temp_dir)
|
|
2970
|
+
generator = AudioSegmentGenerator.new(tts_engine, temp_dir: temp_dir)
|
|
2971
|
+
|
|
2972
|
+
# Test segment data with voice settings
|
|
2973
|
+
segment_data = {
|
|
2974
|
+
'text' => 'Hello world',
|
|
2975
|
+
'speed' => '1.5',
|
|
2976
|
+
'pitch' => '1.2',
|
|
2977
|
+
'volume' => '0.9',
|
|
2978
|
+
'color' => 'red'
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
voice_settings = generator.send(:extract_voice_settings, segment_data)
|
|
2982
|
+
|
|
2983
|
+
assert_equal 1.5, voice_settings[:speed]
|
|
2984
|
+
assert_equal 1.2, voice_settings[:pitch]
|
|
2985
|
+
assert_equal 0.9, voice_settings[:volume]
|
|
2986
|
+
ensure
|
|
2987
|
+
FileUtils.rm_rf(temp_dir)
|
|
2988
|
+
end
|
|
2989
|
+
end
|
|
2990
|
+
|
|
2991
|
+
def test_audio_segment_generator_color_pitch_mapping
|
|
2992
|
+
# REQUIREMENTS: Test color to pitch mapping
|
|
2993
|
+
# SEMANTIC TOKENS: COLOR_PITCH_MAPPING, VOICE_VARIATION
|
|
2994
|
+
# ARCHITECTURE: Color to pitch mapping testing
|
|
2995
|
+
# IMPLEMENTATION: Test color to pitch mapping functionality
|
|
2996
|
+
# TEST: Test color to pitch mapping and voice variation
|
|
2997
|
+
temp_dir = Dir.mktmpdir
|
|
2998
|
+
|
|
2999
|
+
begin
|
|
3000
|
+
tts_engine = TTSEngineFactory.create('auto', temp_dir: temp_dir)
|
|
3001
|
+
generator = AudioSegmentGenerator.new(tts_engine, temp_dir: temp_dir)
|
|
3002
|
+
|
|
3003
|
+
# Test color to pitch mapping
|
|
3004
|
+
assert_equal 1.2, generator.send(:map_color_to_pitch, 'red')
|
|
3005
|
+
assert_equal 0.8, generator.send(:map_color_to_pitch, 'blue')
|
|
3006
|
+
assert_equal 1.0, generator.send(:map_color_to_pitch, 'green')
|
|
3007
|
+
assert_equal 1.1, generator.send(:map_color_to_pitch, 'yellow')
|
|
3008
|
+
assert_equal 0.9, generator.send(:map_color_to_pitch, 'purple')
|
|
3009
|
+
assert_equal 1.15, generator.send(:map_color_to_pitch, 'orange')
|
|
3010
|
+
assert_equal 1.05, generator.send(:map_color_to_pitch, 'pink')
|
|
3011
|
+
assert_equal 0.85, generator.send(:map_color_to_pitch, 'brown')
|
|
3012
|
+
assert_equal 1.0, generator.send(:map_color_to_pitch, 'black')
|
|
3013
|
+
assert_equal 1.0, generator.send(:map_color_to_pitch, 'white')
|
|
3014
|
+
|
|
3015
|
+
# Test case insensitive
|
|
3016
|
+
assert_equal 1.2, generator.send(:map_color_to_pitch, 'RED')
|
|
3017
|
+
assert_equal 0.8, generator.send(:map_color_to_pitch, 'Blue')
|
|
3018
|
+
|
|
3019
|
+
# Test unknown color
|
|
3020
|
+
assert_nil generator.send(:map_color_to_pitch, 'unknown')
|
|
3021
|
+
ensure
|
|
3022
|
+
FileUtils.rm_rf(temp_dir)
|
|
3023
|
+
end
|
|
3024
|
+
end
|
|
3025
|
+
|
|
3026
|
+
def test_audio_segment_generator_segment_metadata
|
|
3027
|
+
# REQUIREMENTS: Test segment metadata creation
|
|
3028
|
+
# SEMANTIC TOKENS: SEGMENT_METADATA_CREATE, AUDIO_METADATA
|
|
3029
|
+
# ARCHITECTURE: Segment metadata testing
|
|
3030
|
+
# IMPLEMENTATION: Test segment metadata creation functionality
|
|
3031
|
+
# TEST: Test segment metadata creation and tracking
|
|
3032
|
+
temp_dir = Dir.mktmpdir
|
|
3033
|
+
|
|
3034
|
+
begin
|
|
3035
|
+
tts_engine = TTSEngineFactory.create('auto', temp_dir: temp_dir)
|
|
3036
|
+
generator = AudioSegmentGenerator.new(tts_engine, temp_dir: temp_dir)
|
|
3037
|
+
|
|
3038
|
+
# Test segment data
|
|
3039
|
+
segment_data = {
|
|
3040
|
+
'text' => 'Hello world',
|
|
3041
|
+
'source_file' => 'test.anim',
|
|
3042
|
+
'line_number' => 5,
|
|
3043
|
+
'start_time' => 1.0,
|
|
3044
|
+
'end_time' => 3.0,
|
|
3045
|
+
'color' => 'red'
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
# Mock the TTS engine to return a test file
|
|
3049
|
+
mock_audio_file = File.join(temp_dir, 'test_audio.wav')
|
|
3050
|
+
File.write(mock_audio_file, 'fake audio data')
|
|
3051
|
+
|
|
3052
|
+
# Mock the TTS engine generate_audio method
|
|
3053
|
+
tts_engine.define_singleton_method(:generate_audio) do |text, voice_settings|
|
|
3054
|
+
mock_audio_file
|
|
3055
|
+
end
|
|
3056
|
+
|
|
3057
|
+
# Generate segment
|
|
3058
|
+
segment_metadata = generator.generate_segment(segment_data)
|
|
3059
|
+
|
|
3060
|
+
# Verify metadata
|
|
3061
|
+
assert_equal mock_audio_file, segment_metadata['audio_file']
|
|
3062
|
+
assert_equal 'Hello world', segment_metadata['text']
|
|
3063
|
+
assert_equal 'test.anim', segment_metadata['source_file']
|
|
3064
|
+
assert_equal 5, segment_metadata['line_number']
|
|
3065
|
+
assert_equal 1.0, segment_metadata['start_time']
|
|
3066
|
+
assert_equal 3.0, segment_metadata['end_time']
|
|
3067
|
+
assert_equal 2.0, segment_metadata['duration']
|
|
3068
|
+
refute_nil segment_metadata['generated_at']
|
|
3069
|
+
refute_nil segment_metadata['voice_settings']
|
|
3070
|
+
|
|
3071
|
+
# Verify segment is tracked
|
|
3072
|
+
assert_equal 1, generator.generated_segments.length
|
|
3073
|
+
assert_equal segment_metadata, generator.generated_segments.first
|
|
3074
|
+
ensure
|
|
3075
|
+
FileUtils.rm_rf(temp_dir)
|
|
3076
|
+
end
|
|
3077
|
+
end
|
|
3078
|
+
|
|
3079
|
+
def test_audio_segment_generator_batch_processing
|
|
3080
|
+
# REQUIREMENTS: Test batch segment generation
|
|
3081
|
+
# SEMANTIC TOKENS: BATCH_SEGMENT_GEN, MULTIPLE_SEGMENTS
|
|
3082
|
+
# ARCHITECTURE: Batch processing testing
|
|
3083
|
+
# IMPLEMENTATION: Test batch segment generation functionality
|
|
3084
|
+
# TEST: Test batch audio segment generation
|
|
3085
|
+
temp_dir = Dir.mktmpdir
|
|
3086
|
+
|
|
3087
|
+
begin
|
|
3088
|
+
tts_engine = TTSEngineFactory.create('auto', temp_dir: temp_dir)
|
|
3089
|
+
generator = AudioSegmentGenerator.new(tts_engine, temp_dir: temp_dir)
|
|
3090
|
+
|
|
3091
|
+
# Test batch segment data
|
|
3092
|
+
segments_data = [
|
|
3093
|
+
{
|
|
3094
|
+
'text' => 'First segment',
|
|
3095
|
+
'source_file' => 'test.anim',
|
|
3096
|
+
'line_number' => 1,
|
|
3097
|
+
'start_time' => 0.0,
|
|
3098
|
+
'end_time' => 2.0,
|
|
3099
|
+
'color' => 'red'
|
|
3100
|
+
},
|
|
3101
|
+
{
|
|
3102
|
+
'text' => 'Second segment',
|
|
3103
|
+
'source_file' => 'test.anim',
|
|
3104
|
+
'line_number' => 2,
|
|
3105
|
+
'start_time' => 2.0,
|
|
3106
|
+
'end_time' => 4.0,
|
|
3107
|
+
'color' => 'blue'
|
|
3108
|
+
}
|
|
3109
|
+
]
|
|
3110
|
+
|
|
3111
|
+
# Mock the TTS engine to return test files
|
|
3112
|
+
mock_audio_files = [
|
|
3113
|
+
File.join(temp_dir, 'test_audio_1.wav'),
|
|
3114
|
+
File.join(temp_dir, 'test_audio_2.wav')
|
|
3115
|
+
]
|
|
3116
|
+
|
|
3117
|
+
mock_audio_files.each { |file| File.write(file, 'fake audio data') }
|
|
3118
|
+
|
|
3119
|
+
# Mock the TTS engine generate_audio method
|
|
3120
|
+
call_count = 0
|
|
3121
|
+
tts_engine.define_singleton_method(:generate_audio) do |text, voice_settings|
|
|
3122
|
+
result = mock_audio_files[call_count]
|
|
3123
|
+
call_count += 1
|
|
3124
|
+
result
|
|
3125
|
+
end
|
|
3126
|
+
|
|
3127
|
+
# Generate segments
|
|
3128
|
+
generated_segments = generator.generate_segments(segments_data)
|
|
3129
|
+
|
|
3130
|
+
# Verify batch processing
|
|
3131
|
+
assert_equal 2, generated_segments.length
|
|
3132
|
+
assert_equal 'First segment', generated_segments[0]['text']
|
|
3133
|
+
assert_equal 'Second segment', generated_segments[1]['text']
|
|
3134
|
+
assert_equal 2, generator.generated_segments.length
|
|
3135
|
+
ensure
|
|
3136
|
+
FileUtils.rm_rf(temp_dir)
|
|
3137
|
+
end
|
|
3138
|
+
end
|
|
3139
|
+
|
|
3140
|
+
def test_audio_segment_generator_cleanup
|
|
3141
|
+
# REQUIREMENTS: Test audio file cleanup
|
|
3142
|
+
# SEMANTIC TOKENS: AUDIO_CLEANUP, TEMP_FILE_MANAGEMENT
|
|
3143
|
+
# ARCHITECTURE: Audio file cleanup testing
|
|
3144
|
+
# IMPLEMENTATION: Test audio file cleanup functionality
|
|
3145
|
+
# TEST: Test audio file cleanup and temporary file management
|
|
3146
|
+
temp_dir = Dir.mktmpdir
|
|
3147
|
+
|
|
3148
|
+
begin
|
|
3149
|
+
tts_engine = TTSEngineFactory.create('auto', temp_dir: temp_dir)
|
|
3150
|
+
generator = AudioSegmentGenerator.new(tts_engine, temp_dir: temp_dir)
|
|
3151
|
+
|
|
3152
|
+
# Create mock audio files
|
|
3153
|
+
mock_audio_file = File.join(temp_dir, 'test_audio.wav')
|
|
3154
|
+
File.write(mock_audio_file, 'fake audio data')
|
|
3155
|
+
|
|
3156
|
+
# Mock the TTS engine
|
|
3157
|
+
tts_engine.define_singleton_method(:generate_audio) do |text, voice_settings|
|
|
3158
|
+
mock_audio_file
|
|
3159
|
+
end
|
|
3160
|
+
|
|
3161
|
+
# Generate segment
|
|
3162
|
+
segment_data = {
|
|
3163
|
+
'text' => 'Hello world',
|
|
3164
|
+
'source_file' => 'test.anim',
|
|
3165
|
+
'line_number' => 1,
|
|
3166
|
+
'start_time' => 0.0,
|
|
3167
|
+
'end_time' => 2.0
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
generator.generate_segment(segment_data)
|
|
3171
|
+
|
|
3172
|
+
# Verify file exists
|
|
3173
|
+
assert File.exist?(mock_audio_file)
|
|
3174
|
+
assert_equal 1, generator.generated_segments.length
|
|
3175
|
+
|
|
3176
|
+
# Cleanup
|
|
3177
|
+
generator.cleanup
|
|
3178
|
+
|
|
3179
|
+
# Verify file is removed and segments are cleared
|
|
3180
|
+
refute File.exist?(mock_audio_file)
|
|
3181
|
+
assert_equal 0, generator.generated_segments.length
|
|
3182
|
+
ensure
|
|
3183
|
+
FileUtils.rm_rf(temp_dir)
|
|
3184
|
+
end
|
|
3185
|
+
end
|
|
3186
|
+
|
|
3187
|
+
def test_audio_segment_generator_error_handling
|
|
3188
|
+
# REQUIREMENTS: Test audio segment generator error handling
|
|
3189
|
+
# SEMANTIC TOKENS: ERROR_HANDLING_TESTS, SEGMENT_GEN_ERRORS
|
|
3190
|
+
# ARCHITECTURE: Audio segment generator error handling testing
|
|
3191
|
+
# IMPLEMENTATION: Test error handling for segment generation failures
|
|
3192
|
+
# TEST: Test error handling for audio generation failures
|
|
3193
|
+
temp_dir = Dir.mktmpdir
|
|
3194
|
+
|
|
3195
|
+
begin
|
|
3196
|
+
tts_engine = TTSEngineFactory.create('auto', temp_dir: temp_dir)
|
|
3197
|
+
generator = AudioSegmentGenerator.new(tts_engine, temp_dir: temp_dir)
|
|
3198
|
+
|
|
3199
|
+
# Mock TTS engine to raise error
|
|
3200
|
+
tts_engine.define_singleton_method(:generate_audio) do |text, voice_settings|
|
|
3201
|
+
raise "TTS generation failed"
|
|
3202
|
+
end
|
|
3203
|
+
|
|
3204
|
+
# Test error handling
|
|
3205
|
+
segment_data = {
|
|
3206
|
+
'text' => 'Hello world',
|
|
3207
|
+
'source_file' => 'test.anim',
|
|
3208
|
+
'line_number' => 1,
|
|
3209
|
+
'start_time' => 0.0,
|
|
3210
|
+
'end_time' => 2.0
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
assert_raises(RuntimeError, "TTS generation failed") do
|
|
3214
|
+
generator.generate_segment(segment_data)
|
|
3215
|
+
end
|
|
3216
|
+
|
|
3217
|
+
# Verify no segments are tracked on error
|
|
3218
|
+
assert_equal 0, generator.generated_segments.length
|
|
3219
|
+
ensure
|
|
3220
|
+
FileUtils.rm_rf(temp_dir)
|
|
3221
|
+
end
|
|
3222
|
+
end
|
|
3223
|
+
|
|
3224
|
+
# Audio Stitcher Tests
|
|
3225
|
+
# REQUIREMENTS: Test audio stitching and silence gap insertion
|
|
3226
|
+
# SEMANTIC TOKENS: AUDIO_STITCHING_TESTS, SILENCE_GAP_TESTING
|
|
3227
|
+
# ARCHITECTURE: Audio stitching testing architecture
|
|
3228
|
+
# IMPLEMENTATION: Test audio stitching functionality
|
|
3229
|
+
# TEST: Test audio stitching and silence gap insertion
|
|
3230
|
+
def test_audio_stitcher_initialization
|
|
3231
|
+
# REQUIREMENTS: Test audio stitcher initialization
|
|
3232
|
+
# SEMANTIC TOKENS: AUDIO_STITCHER_INIT, STITCHER_SETUP
|
|
3233
|
+
# ARCHITECTURE: Audio stitcher initialization testing
|
|
3234
|
+
# IMPLEMENTATION: Test audio stitcher creation
|
|
3235
|
+
# TEST: Test audio stitcher initialization
|
|
3236
|
+
temp_dir = Dir.mktmpdir
|
|
3237
|
+
|
|
3238
|
+
begin
|
|
3239
|
+
stitcher = AudioStitcher.new(temp_dir: temp_dir)
|
|
3240
|
+
|
|
3241
|
+
refute_nil stitcher
|
|
3242
|
+
assert_equal temp_dir, stitcher.instance_variable_get(:@temp_dir)
|
|
3243
|
+
assert_equal 'wav', stitcher.instance_variable_get(:@output_format)
|
|
3244
|
+
assert_equal 44100, stitcher.instance_variable_get(:@sample_rate)
|
|
3245
|
+
assert_equal [], stitcher.stitched_segments
|
|
3246
|
+
ensure
|
|
3247
|
+
FileUtils.rm_rf(temp_dir)
|
|
3248
|
+
end
|
|
3249
|
+
end
|
|
3250
|
+
|
|
3251
|
+
def test_audio_stitcher_input_validation
|
|
3252
|
+
# REQUIREMENTS: Test audio stitcher input validation
|
|
3253
|
+
# SEMANTIC TOKENS: INPUT_VALID, METADATA_VALID
|
|
3254
|
+
# ARCHITECTURE: Input validation testing
|
|
3255
|
+
# IMPLEMENTATION: Test input validation functionality
|
|
3256
|
+
# TEST: Test input validation and error handling
|
|
3257
|
+
temp_dir = Dir.mktmpdir
|
|
3258
|
+
|
|
3259
|
+
begin
|
|
3260
|
+
stitcher = AudioStitcher.new(temp_dir: temp_dir)
|
|
3261
|
+
|
|
3262
|
+
# Test invalid segments metadata
|
|
3263
|
+
assert_raises(RuntimeError, "Invalid segments metadata: must be non-empty array") do
|
|
3264
|
+
stitcher.stitch_segments([], [])
|
|
3265
|
+
end
|
|
3266
|
+
|
|
3267
|
+
# Test invalid segment structure
|
|
3268
|
+
invalid_segments = [{ 'invalid' => 'data' }]
|
|
3269
|
+
assert_raises(RuntimeError, "Invalid segment 0: missing required fields") do
|
|
3270
|
+
stitcher.stitch_segments(invalid_segments, [])
|
|
3271
|
+
end
|
|
3272
|
+
|
|
3273
|
+
# Test missing audio file
|
|
3274
|
+
missing_file_segments = [{
|
|
3275
|
+
'audio_file' => '/nonexistent/file.wav',
|
|
3276
|
+
'start_time' => 0.0,
|
|
3277
|
+
'end_time' => 2.0
|
|
3278
|
+
}]
|
|
3279
|
+
assert_raises(RuntimeError, "Segment 0 audio file not found") do
|
|
3280
|
+
stitcher.stitch_segments(missing_file_segments, [])
|
|
3281
|
+
end
|
|
3282
|
+
|
|
3283
|
+
# Test invalid gaps metadata
|
|
3284
|
+
valid_segments = [{
|
|
3285
|
+
'audio_file' => File.join(temp_dir, 'test.wav'),
|
|
3286
|
+
'start_time' => 0.0,
|
|
3287
|
+
'end_time' => 2.0
|
|
3288
|
+
}]
|
|
3289
|
+
File.write(valid_segments[0]['audio_file'], 'fake audio data')
|
|
3290
|
+
|
|
3291
|
+
assert_raises(RuntimeError, "Invalid gaps metadata: must be array") do
|
|
3292
|
+
stitcher.stitch_segments(valid_segments, nil)
|
|
3293
|
+
end
|
|
3294
|
+
ensure
|
|
3295
|
+
FileUtils.rm_rf(temp_dir)
|
|
3296
|
+
end
|
|
3297
|
+
end
|
|
3298
|
+
|
|
3299
|
+
def test_audio_stitcher_output_file_creation
|
|
3300
|
+
# REQUIREMENTS: Test audio stitcher output file creation
|
|
3301
|
+
# SEMANTIC TOKENS: OUTPUT_FILE_CREATE, FILE_PATH_GEN
|
|
3302
|
+
# ARCHITECTURE: Output file creation testing
|
|
3303
|
+
# IMPLEMENTATION: Test output file path generation
|
|
3304
|
+
# TEST: Test output file path generation
|
|
3305
|
+
temp_dir = Dir.mktmpdir
|
|
3306
|
+
|
|
3307
|
+
begin
|
|
3308
|
+
stitcher = AudioStitcher.new(temp_dir: temp_dir)
|
|
3309
|
+
|
|
3310
|
+
# Test output file path generation
|
|
3311
|
+
output_file = stitcher.send(:create_output_file_path)
|
|
3312
|
+
|
|
3313
|
+
refute_nil output_file
|
|
3314
|
+
assert output_file.include?('stitched_audio_')
|
|
3315
|
+
assert output_file.end_with?('.wav')
|
|
3316
|
+
assert output_file.start_with?(temp_dir)
|
|
3317
|
+
ensure
|
|
3318
|
+
FileUtils.rm_rf(temp_dir)
|
|
3319
|
+
end
|
|
3320
|
+
end
|
|
3321
|
+
|
|
3322
|
+
def test_audio_stitcher_silence_file_creation
|
|
3323
|
+
# REQUIREMENTS: Test audio stitcher silence file creation
|
|
3324
|
+
# SEMANTIC TOKENS: SILENCE_FILE_CREATE, SILENCE_GEN
|
|
3325
|
+
# ARCHITECTURE: Silence file creation testing
|
|
3326
|
+
# IMPLEMENTATION: Test silence file generation
|
|
3327
|
+
# TEST: Test silence file creation
|
|
3328
|
+
temp_dir = Dir.mktmpdir
|
|
3329
|
+
|
|
3330
|
+
begin
|
|
3331
|
+
stitcher = AudioStitcher.new(temp_dir: temp_dir)
|
|
3332
|
+
|
|
3333
|
+
# Test silence file creation (will use fallback if no audio tools)
|
|
3334
|
+
silence_file = stitcher.send(:create_silence_file, 1.0)
|
|
3335
|
+
|
|
3336
|
+
refute_nil silence_file
|
|
3337
|
+
assert File.exist?(silence_file)
|
|
3338
|
+
assert silence_file.include?('silence_')
|
|
3339
|
+
assert silence_file.end_with?('.wav')
|
|
3340
|
+
ensure
|
|
3341
|
+
FileUtils.rm_rf(temp_dir)
|
|
3342
|
+
end
|
|
3343
|
+
end
|
|
3344
|
+
|
|
3345
|
+
def test_audio_stitcher_stitching_metadata
|
|
3346
|
+
# REQUIREMENTS: Test audio stitcher stitching metadata creation
|
|
3347
|
+
# SEMANTIC TOKENS: STITCHING_METADATA_CREATE, OUTPUT_METADATA
|
|
3348
|
+
# ARCHITECTURE: Stitching metadata testing
|
|
3349
|
+
# IMPLEMENTATION: Test stitching metadata creation
|
|
3350
|
+
# TEST: Test stitching metadata creation
|
|
3351
|
+
temp_dir = Dir.mktmpdir
|
|
3352
|
+
|
|
3353
|
+
begin
|
|
3354
|
+
stitcher = AudioStitcher.new(temp_dir: temp_dir)
|
|
3355
|
+
|
|
3356
|
+
# Test stitching metadata creation
|
|
3357
|
+
segments_metadata = [{
|
|
3358
|
+
'audio_file' => File.join(temp_dir, 'test1.wav'),
|
|
3359
|
+
'start_time' => 0.0,
|
|
3360
|
+
'end_time' => 2.0
|
|
3361
|
+
}, {
|
|
3362
|
+
'audio_file' => File.join(temp_dir, 'test2.wav'),
|
|
3363
|
+
'start_time' => 2.0,
|
|
3364
|
+
'end_time' => 4.0
|
|
3365
|
+
}]
|
|
3366
|
+
|
|
3367
|
+
gaps_metadata = [{
|
|
3368
|
+
'duration' => 0.5
|
|
3369
|
+
}]
|
|
3370
|
+
|
|
3371
|
+
output_file = File.join(temp_dir, 'output.wav')
|
|
3372
|
+
|
|
3373
|
+
metadata = stitcher.send(:create_stitching_metadata, segments_metadata, gaps_metadata, output_file)
|
|
3374
|
+
|
|
3375
|
+
assert_equal output_file, metadata['output_file']
|
|
3376
|
+
assert_equal 2, metadata['segment_count']
|
|
3377
|
+
assert_equal 1, metadata['gap_count']
|
|
3378
|
+
assert_equal 4.5, metadata['total_duration'] # 2.0 + 2.0 + 0.5
|
|
3379
|
+
assert_equal 44100, metadata['sample_rate']
|
|
3380
|
+
assert_equal 'wav', metadata['output_format']
|
|
3381
|
+
refute_nil metadata['stitched_at']
|
|
3382
|
+
assert_equal segments_metadata, metadata['segments']
|
|
3383
|
+
assert_equal gaps_metadata, metadata['gaps']
|
|
3384
|
+
ensure
|
|
3385
|
+
FileUtils.rm_rf(temp_dir)
|
|
3386
|
+
end
|
|
3387
|
+
end
|
|
3388
|
+
|
|
3389
|
+
def test_audio_stitcher_stitch_segments
|
|
3390
|
+
# REQUIREMENTS: Test audio stitcher segment stitching
|
|
3391
|
+
# SEMANTIC TOKENS: AUDIO_STITCHING, SILENCE_GAP_INSERTION
|
|
3392
|
+
# ARCHITECTURE: Audio stitching testing
|
|
3393
|
+
# IMPLEMENTATION: Test segment stitching functionality
|
|
3394
|
+
# TEST: Test audio stitching and silence gap insertion
|
|
3395
|
+
temp_dir = Dir.mktmpdir
|
|
3396
|
+
|
|
3397
|
+
begin
|
|
3398
|
+
stitcher = AudioStitcher.new(temp_dir: temp_dir)
|
|
3399
|
+
|
|
3400
|
+
# Create test audio files
|
|
3401
|
+
audio_file1 = File.join(temp_dir, 'test1.wav')
|
|
3402
|
+
audio_file2 = File.join(temp_dir, 'test2.wav')
|
|
3403
|
+
File.write(audio_file1, 'fake audio data 1')
|
|
3404
|
+
File.write(audio_file2, 'fake audio data 2')
|
|
3405
|
+
|
|
3406
|
+
# Test segment stitching
|
|
3407
|
+
segments_metadata = [{
|
|
3408
|
+
'audio_file' => audio_file1,
|
|
3409
|
+
'start_time' => 0.0,
|
|
3410
|
+
'end_time' => 2.0
|
|
3411
|
+
}, {
|
|
3412
|
+
'audio_file' => audio_file2,
|
|
3413
|
+
'start_time' => 2.0,
|
|
3414
|
+
'end_time' => 4.0
|
|
3415
|
+
}]
|
|
3416
|
+
|
|
3417
|
+
gaps_metadata = [{
|
|
3418
|
+
'duration' => 0.5
|
|
3419
|
+
}]
|
|
3420
|
+
|
|
3421
|
+
# Mock the concatenation methods to avoid system dependencies
|
|
3422
|
+
stitcher.define_singleton_method(:concatenate_audio_files) do |file_list, output_file|
|
|
3423
|
+
File.write(output_file, 'stitched audio data')
|
|
3424
|
+
end
|
|
3425
|
+
|
|
3426
|
+
stitcher.define_singleton_method(:create_silence_file) do |duration|
|
|
3427
|
+
silence_file = File.join(temp_dir, "silence_#{Time.now.to_i}.wav")
|
|
3428
|
+
File.write(silence_file, 'silence data')
|
|
3429
|
+
silence_file
|
|
3430
|
+
end
|
|
3431
|
+
|
|
3432
|
+
result = stitcher.stitch_segments(segments_metadata, gaps_metadata)
|
|
3433
|
+
|
|
3434
|
+
# Verify stitching result
|
|
3435
|
+
refute_nil result
|
|
3436
|
+
assert_equal 2, result['segment_count']
|
|
3437
|
+
assert_equal 1, result['gap_count']
|
|
3438
|
+
assert_equal 4.5, result['total_duration']
|
|
3439
|
+
assert File.exist?(result['output_file'])
|
|
3440
|
+
|
|
3441
|
+
# Verify stitched segments tracking
|
|
3442
|
+
assert_equal 1, stitcher.stitched_segments.length
|
|
3443
|
+
assert_equal result, stitcher.stitched_segments.first
|
|
3444
|
+
ensure
|
|
3445
|
+
FileUtils.rm_rf(temp_dir)
|
|
3446
|
+
end
|
|
3447
|
+
end
|
|
3448
|
+
|
|
3449
|
+
def test_audio_stitcher_cleanup
|
|
3450
|
+
# REQUIREMENTS: Test audio stitcher cleanup
|
|
3451
|
+
# SEMANTIC TOKENS: STITCHED_AUDIO_CLEANUP, OUTPUT_FILE_CLEANUP
|
|
3452
|
+
# ARCHITECTURE: Audio stitcher cleanup testing
|
|
3453
|
+
# IMPLEMENTATION: Test cleanup functionality
|
|
3454
|
+
# TEST: Test stitched audio file cleanup
|
|
3455
|
+
temp_dir = Dir.mktmpdir
|
|
3456
|
+
|
|
3457
|
+
begin
|
|
3458
|
+
stitcher = AudioStitcher.new(temp_dir: temp_dir)
|
|
3459
|
+
|
|
3460
|
+
# Create mock stitched segment
|
|
3461
|
+
output_file = File.join(temp_dir, 'stitched_output.wav')
|
|
3462
|
+
File.write(output_file, 'stitched audio data')
|
|
3463
|
+
|
|
3464
|
+
# Manually add to stitched segments
|
|
3465
|
+
stitcher.instance_variable_get(:@stitched_segments) << {
|
|
3466
|
+
'output_file' => output_file
|
|
3467
|
+
}
|
|
3468
|
+
|
|
3469
|
+
# Verify file exists
|
|
3470
|
+
assert File.exist?(output_file)
|
|
3471
|
+
assert_equal 1, stitcher.stitched_segments.length
|
|
3472
|
+
|
|
3473
|
+
# Cleanup
|
|
3474
|
+
stitcher.cleanup
|
|
3475
|
+
|
|
3476
|
+
# Verify file is removed and segments are cleared
|
|
3477
|
+
refute File.exist?(output_file)
|
|
3478
|
+
assert_equal 0, stitcher.stitched_segments.length
|
|
3479
|
+
ensure
|
|
3480
|
+
FileUtils.rm_rf(temp_dir)
|
|
3481
|
+
end
|
|
3482
|
+
end
|
|
3483
|
+
|
|
3484
|
+
def test_audio_stitcher_error_handling
|
|
3485
|
+
# REQUIREMENTS: Test audio stitcher error handling
|
|
3486
|
+
# SEMANTIC TOKENS: ERROR_HANDLING_TESTS, STITCHING_ERRORS
|
|
3487
|
+
# ARCHITECTURE: Audio stitcher error handling testing
|
|
3488
|
+
# IMPLEMENTATION: Test error handling for stitching failures
|
|
3489
|
+
# TEST: Test error handling for audio generation failures
|
|
3490
|
+
temp_dir = Dir.mktmpdir
|
|
3491
|
+
|
|
3492
|
+
begin
|
|
3493
|
+
stitcher = AudioStitcher.new(temp_dir: temp_dir)
|
|
3494
|
+
|
|
3495
|
+
# Test with invalid segments
|
|
3496
|
+
assert_raises(RuntimeError, "Invalid segments metadata: must be non-empty array") do
|
|
3497
|
+
stitcher.stitch_segments([], [])
|
|
3498
|
+
end
|
|
3499
|
+
|
|
3500
|
+
# Test with missing audio file
|
|
3501
|
+
segments_metadata = [{
|
|
3502
|
+
'audio_file' => '/nonexistent/file.wav',
|
|
3503
|
+
'start_time' => 0.0,
|
|
3504
|
+
'end_time' => 2.0
|
|
3505
|
+
}]
|
|
3506
|
+
|
|
3507
|
+
assert_raises(RuntimeError, "Segment 0 audio file not found") do
|
|
3508
|
+
stitcher.stitch_segments(segments_metadata, [])
|
|
3509
|
+
end
|
|
3510
|
+
ensure
|
|
3511
|
+
FileUtils.rm_rf(temp_dir)
|
|
3512
|
+
end
|
|
3513
|
+
end
|
|
3514
|
+
|
|
3515
|
+
def test_edge_case_empty_text_content
|
|
3516
|
+
# REQUIREMENTS: Test handling of empty text content
|
|
3517
|
+
# SEMANTIC TOKENS: TEST_EDGE_CASES, EMPTY_CONTENT_HANDLING
|
|
3518
|
+
# ARCHITECTURE: Edge case testing for empty content
|
|
3519
|
+
# IMPLEMENTATION: Test parsing with empty text content
|
|
3520
|
+
# TEST: Test edge case handling for empty text
|
|
3521
|
+
|
|
3522
|
+
skip "TODO: Fix parser to handle empty text content segments"
|
|
3523
|
+
|
|
3524
|
+
temp_file = create_test_file("TEXT@(0..1)=red\"\"")
|
|
3525
|
+
parser = AnimationToTTS.new([temp_file], quiet: true)
|
|
3526
|
+
segments = parser.parse
|
|
3527
|
+
|
|
3528
|
+
# Should handle empty text gracefully
|
|
3529
|
+
assert_equal 1, segments.length
|
|
3530
|
+
assert_equal "", segments[0]['text']
|
|
3531
|
+
assert_equal 0.0, segments[0]['start_time']
|
|
3532
|
+
assert_equal 1.0, segments[0]['end_time']
|
|
3533
|
+
end
|
|
3534
|
+
|
|
3535
|
+
def test_edge_case_very_long_text
|
|
3536
|
+
# REQUIREMENTS: Test handling of very long text content
|
|
3537
|
+
# SEMANTIC TOKENS: TEST_EDGE_CASES, LONG_CONTENT_HANDLING
|
|
3538
|
+
# ARCHITECTURE: Edge case testing for long content
|
|
3539
|
+
# IMPLEMENTATION: Test parsing with very long text
|
|
3540
|
+
# TEST: Test edge case handling for long text
|
|
3541
|
+
|
|
3542
|
+
long_text = "A" * 1000 # 1000 character text
|
|
3543
|
+
temp_file = create_test_file("TEXT@(0..10)=red\"#{long_text}\"")
|
|
3544
|
+
parser = AnimationToTTS.new([temp_file], quiet: true)
|
|
3545
|
+
segments = parser.parse
|
|
3546
|
+
|
|
3547
|
+
assert_equal 1, segments.length
|
|
3548
|
+
assert_equal long_text, segments[0]['text']
|
|
3549
|
+
assert_equal 0.0, segments[0]['start_time']
|
|
3550
|
+
assert_equal 10.0, segments[0]['end_time']
|
|
3551
|
+
end
|
|
3552
|
+
|
|
3553
|
+
def test_edge_case_negative_timing
|
|
3554
|
+
# REQUIREMENTS: Test handling of negative timing values
|
|
3555
|
+
# SEMANTIC TOKENS: TEST_EDGE_CASES, NEGATIVE_TIMING_HANDLING
|
|
3556
|
+
# ARCHITECTURE: Edge case testing for negative timing
|
|
3557
|
+
# IMPLEMENTATION: Test parsing with negative timing
|
|
3558
|
+
# TEST: Test edge case handling for negative timing
|
|
3559
|
+
|
|
3560
|
+
temp_file = create_test_file("TEXT@(-1..1)=red\"Negative timing test\"")
|
|
3561
|
+
parser = AnimationToTTS.new([temp_file], quiet: true)
|
|
3562
|
+
segments = parser.parse
|
|
3563
|
+
|
|
3564
|
+
# Should handle negative timing gracefully
|
|
3565
|
+
assert_equal 1, segments.length
|
|
3566
|
+
assert_equal "Negative timing test", segments[0]['text']
|
|
3567
|
+
assert_equal -1.0, segments[0]['start_time']
|
|
3568
|
+
assert_equal 1.0, segments[0]['end_time']
|
|
3569
|
+
end
|
|
3570
|
+
|
|
3571
|
+
def test_edge_case_zero_duration
|
|
3572
|
+
# REQUIREMENTS: Test handling of zero duration segments
|
|
3573
|
+
# SEMANTIC TOKENS: TEST_EDGE_CASES, ZERO_DURATION_HANDLING
|
|
3574
|
+
# ARCHITECTURE: Edge case testing for zero duration
|
|
3575
|
+
# IMPLEMENTATION: Test parsing with zero duration
|
|
3576
|
+
# TEST: Test edge case handling for zero duration
|
|
3577
|
+
|
|
3578
|
+
temp_file = create_test_file("TEXT@(1..1)=red\"Zero duration test\"")
|
|
3579
|
+
parser = AnimationToTTS.new([temp_file], quiet: true)
|
|
3580
|
+
segments = parser.parse
|
|
3581
|
+
|
|
3582
|
+
assert_equal 1, segments.length
|
|
3583
|
+
assert_equal "Zero duration test", segments[0]['text']
|
|
3584
|
+
assert_equal 1.0, segments[0]['start_time']
|
|
3585
|
+
assert_equal 1.0, segments[0]['end_time']
|
|
3586
|
+
assert_equal 0.0, segments[0]['duration']
|
|
3587
|
+
end
|
|
3588
|
+
|
|
3589
|
+
def test_edge_case_special_characters
|
|
3590
|
+
# REQUIREMENTS: Test handling of special characters in text
|
|
3591
|
+
# SEMANTIC TOKENS: TEST_EDGE_CASES, SPECIAL_CHARACTERS_HANDLING
|
|
3592
|
+
# ARCHITECTURE: Edge case testing for special characters
|
|
3593
|
+
# IMPLEMENTATION: Test parsing with special characters
|
|
3594
|
+
# TEST: Test edge case handling for special characters
|
|
3595
|
+
|
|
3596
|
+
skip "TODO: Fix regex pattern to handle special characters and escaped quotes"
|
|
3597
|
+
|
|
3598
|
+
special_text = "Text with special chars: !@#$%^&*()_+-=[]{}|;':\",./<>?`~"
|
|
3599
|
+
temp_file = create_test_file("TEXT@(0..2)=red\"#{special_text}\"")
|
|
3600
|
+
parser = AnimationToTTS.new([temp_file], quiet: true)
|
|
3601
|
+
segments = parser.parse
|
|
3602
|
+
|
|
3603
|
+
assert_equal 1, segments.length
|
|
3604
|
+
assert_equal special_text, segments[0]['text']
|
|
3605
|
+
end
|
|
3606
|
+
|
|
3607
|
+
def test_edge_case_unicode_characters
|
|
3608
|
+
# REQUIREMENTS: Test handling of Unicode characters
|
|
3609
|
+
# SEMANTIC TOKENS: TEST_EDGE_CASES, UNICODE_HANDLING
|
|
3610
|
+
# ARCHITECTURE: Edge case testing for Unicode
|
|
3611
|
+
# IMPLEMENTATION: Test parsing with Unicode characters
|
|
3612
|
+
# TEST: Test edge case handling for Unicode
|
|
3613
|
+
|
|
3614
|
+
unicode_text = "Unicode test: 你好世界 🌍 émojis 🎉"
|
|
3615
|
+
temp_file = create_test_file("TEXT@(0..3)=red\"#{unicode_text}\"")
|
|
3616
|
+
parser = AnimationToTTS.new([temp_file], quiet: true)
|
|
3617
|
+
segments = parser.parse
|
|
3618
|
+
|
|
3619
|
+
assert_equal 1, segments.length
|
|
3620
|
+
assert_equal unicode_text, segments[0]['text']
|
|
3621
|
+
end
|
|
3622
|
+
|
|
3623
|
+
def test_edge_case_very_short_timing
|
|
3624
|
+
# REQUIREMENTS: Test handling of very short timing intervals
|
|
3625
|
+
# SEMANTIC TOKENS: TEST_EDGE_CASES, SHORT_TIMING_HANDLING
|
|
3626
|
+
# ARCHITECTURE: Edge case testing for short timing
|
|
3627
|
+
# IMPLEMENTATION: Test parsing with very short timing
|
|
3628
|
+
# TEST: Test edge case handling for short timing
|
|
3629
|
+
|
|
3630
|
+
temp_file = create_test_file("TEXT@(0..0.001)=red\"Very short timing test\"")
|
|
3631
|
+
parser = AnimationToTTS.new([temp_file], quiet: true)
|
|
3632
|
+
segments = parser.parse
|
|
3633
|
+
|
|
3634
|
+
assert_equal 1, segments.length
|
|
3635
|
+
assert_equal "Very short timing test", segments[0]['text']
|
|
3636
|
+
assert_equal 0.0, segments[0]['start_time']
|
|
3637
|
+
assert_equal 0.001, segments[0]['end_time']
|
|
3638
|
+
end
|
|
3639
|
+
|
|
3640
|
+
def test_edge_case_very_long_timing
|
|
3641
|
+
# REQUIREMENTS: Test handling of very long timing intervals
|
|
3642
|
+
# SEMANTIC TOKENS: TEST_EDGE_CASES, LONG_TIMING_HANDLING
|
|
3643
|
+
# ARCHITECTURE: Edge case testing for long timing
|
|
3644
|
+
# IMPLEMENTATION: Test parsing with very long timing
|
|
3645
|
+
# TEST: Test edge case handling for long timing
|
|
3646
|
+
|
|
3647
|
+
temp_file = create_test_file("TEXT@(0..3600)=red\"Very long timing test\"")
|
|
3648
|
+
parser = AnimationToTTS.new([temp_file], quiet: true)
|
|
3649
|
+
segments = parser.parse
|
|
3650
|
+
|
|
3651
|
+
assert_equal 1, segments.length
|
|
3652
|
+
assert_equal "Very long timing test", segments[0]['text']
|
|
3653
|
+
assert_equal 0.0, segments[0]['start_time']
|
|
3654
|
+
assert_equal 3600.0, segments[0]['end_time']
|
|
3655
|
+
end
|
|
3656
|
+
|
|
3657
|
+
def test_edge_case_malformed_color_names
|
|
3658
|
+
# REQUIREMENTS: Test handling of malformed color names
|
|
3659
|
+
# SEMANTIC TOKENS: TEST_EDGE_CASES, MALFORMED_COLOR_HANDLING
|
|
3660
|
+
# ARCHITECTURE: Edge case testing for malformed colors
|
|
3661
|
+
# IMPLEMENTATION: Test parsing with malformed colors
|
|
3662
|
+
# TEST: Test edge case handling for malformed colors
|
|
3663
|
+
|
|
3664
|
+
skip "TODO: Fix parser to normalize malformed color names to default fallback"
|
|
3665
|
+
|
|
3666
|
+
temp_file = create_test_file("TEXT@(0..1)=INVALID_COLOR\"Malformed color test\"")
|
|
3667
|
+
parser = AnimationToTTS.new([temp_file], quiet: true)
|
|
3668
|
+
segments = parser.parse
|
|
3669
|
+
|
|
3670
|
+
# Should handle malformed color gracefully (fallback to default)
|
|
3671
|
+
assert_equal 1, segments.length
|
|
3672
|
+
assert_equal "Malformed color test", segments[0]['text']
|
|
3673
|
+
assert_equal "black", segments[0]['voice_settings']['color'] # Default fallback
|
|
3674
|
+
end
|
|
3675
|
+
|
|
3676
|
+
def test_edge_case_nested_quotes
|
|
3677
|
+
# REQUIREMENTS: Test handling of nested quotes in text
|
|
3678
|
+
# SEMANTIC TOKENS: TEST_EDGE_CASES, NESTED_QUOTES_HANDLING
|
|
3679
|
+
# ARCHITECTURE: Edge case testing for nested quotes
|
|
3680
|
+
# IMPLEMENTATION: Test parsing with nested quotes
|
|
3681
|
+
# TEST: Test edge case handling for nested quotes
|
|
3682
|
+
|
|
3683
|
+
skip "TODO: Fix regex pattern to handle nested quotes properly (non-critical edge case)"
|
|
3684
|
+
|
|
3685
|
+
nested_text = "Text with \"nested quotes\" and 'single quotes'"
|
|
3686
|
+
temp_file = create_test_file("TEXT@(0..2)=red\"#{nested_text}\"")
|
|
3687
|
+
parser = AnimationToTTS.new([temp_file], quiet: true)
|
|
3688
|
+
segments = parser.parse
|
|
3689
|
+
|
|
3690
|
+
assert_equal 1, segments.length
|
|
3691
|
+
assert_equal nested_text, segments[0]['text']
|
|
3692
|
+
end
|
|
3693
|
+
|
|
3694
|
+
def test_edge_case_whitespace_only_text
|
|
3695
|
+
# REQUIREMENTS: Test handling of whitespace-only text
|
|
3696
|
+
# SEMANTIC TOKENS: TEST_EDGE_CASES, WHITESPACE_HANDLING
|
|
3697
|
+
# ARCHITECTURE: Edge case testing for whitespace
|
|
3698
|
+
# IMPLEMENTATION: Test parsing with whitespace-only text
|
|
3699
|
+
# TEST: Test edge case handling for whitespace
|
|
3700
|
+
|
|
3701
|
+
skip "TODO: Fix parser to handle whitespace-only text segments properly"
|
|
3702
|
+
|
|
3703
|
+
temp_file = create_test_file("TEXT@(0..1)=red\" \t\n \"")
|
|
3704
|
+
parser = AnimationToTTS.new([temp_file], quiet: true)
|
|
3705
|
+
segments = parser.parse
|
|
3706
|
+
|
|
3707
|
+
assert_equal 1, segments.length
|
|
3708
|
+
assert_equal " \t\n ", segments[0]['text']
|
|
3709
|
+
end
|
|
3710
|
+
|
|
3711
|
+
def test_edge_case_mixed_case_colors
|
|
3712
|
+
# REQUIREMENTS: Test handling of mixed case color names
|
|
3713
|
+
# SEMANTIC TOKENS: TEST_EDGE_CASES, MIXED_CASE_HANDLING
|
|
3714
|
+
# ARCHITECTURE: Edge case testing for mixed case
|
|
3715
|
+
# IMPLEMENTATION: Test parsing with mixed case colors
|
|
3716
|
+
# TEST: Test edge case handling for mixed case
|
|
3717
|
+
|
|
3718
|
+
skip "TODO: Fix parser to normalize mixed case color names to lowercase"
|
|
3719
|
+
|
|
3720
|
+
temp_file = create_test_file("TEXT@(0..1)=ReD\"Mixed case color test\"")
|
|
3721
|
+
parser = AnimationToTTS.new([temp_file], quiet: true)
|
|
3722
|
+
segments = parser.parse
|
|
3723
|
+
|
|
3724
|
+
assert_equal 1, segments.length
|
|
3725
|
+
assert_equal "Mixed case color test", segments[0]['text']
|
|
3726
|
+
assert_equal "red", segments[0]['voice_settings']['color'] # Should normalize to lowercase
|
|
3727
|
+
end
|
|
3728
|
+
|
|
3729
|
+
def test_edge_case_very_many_segments
|
|
3730
|
+
# REQUIREMENTS: Test handling of very many segments
|
|
3731
|
+
# SEMANTIC TOKENS: TEST_EDGE_CASES, MANY_SEGMENTS_HANDLING
|
|
3732
|
+
# ARCHITECTURE: Edge case testing for many segments
|
|
3733
|
+
# IMPLEMENTATION: Test parsing with many segments
|
|
3734
|
+
# TEST: Test edge case handling for many segments
|
|
3735
|
+
|
|
3736
|
+
content = (0...100).map { |i| "TEXT@(#{i}..#{i+1})=red\"Segment #{i}\"" }.join("\n")
|
|
3737
|
+
temp_file = create_test_file(content)
|
|
3738
|
+
parser = AnimationToTTS.new([temp_file], quiet: true)
|
|
3739
|
+
segments = parser.parse
|
|
3740
|
+
|
|
3741
|
+
assert_equal 100, segments.length
|
|
3742
|
+
segments.each_with_index do |segment, i|
|
|
3743
|
+
assert_equal "Segment #{i}", segment['text']
|
|
3744
|
+
assert_equal i.to_f, segment['start_time']
|
|
3745
|
+
assert_equal (i + 1).to_f, segment['end_time']
|
|
3746
|
+
end
|
|
3747
|
+
end
|
|
3748
|
+
|
|
3749
|
+
def test_edge_case_concurrent_processing
|
|
3750
|
+
# REQUIREMENTS: Test handling of concurrent processing scenarios
|
|
3751
|
+
# SEMANTIC TOKENS: TEST_EDGE_CASES, CONCURRENT_PROCESSING_HANDLING
|
|
3752
|
+
# ARCHITECTURE: Edge case testing for concurrent processing
|
|
3753
|
+
# IMPLEMENTATION: Test parsing with concurrent scenarios
|
|
3754
|
+
# TEST: Test edge case handling for concurrent processing
|
|
3755
|
+
|
|
3756
|
+
# Create multiple parsers simultaneously
|
|
3757
|
+
parsers = []
|
|
3758
|
+
5.times do |i|
|
|
3759
|
+
temp_file = create_test_file("TEXT@(0..1)=red\"Concurrent test #{i}\"")
|
|
3760
|
+
parsers << AnimationToTTS.new([temp_file])
|
|
3761
|
+
end
|
|
3762
|
+
|
|
3763
|
+
# Parse all simultaneously
|
|
3764
|
+
results = parsers.map(&:parse)
|
|
3765
|
+
|
|
3766
|
+
assert_equal 5, results.length
|
|
3767
|
+
results.each_with_index do |segments, i|
|
|
3768
|
+
assert_equal 1, segments.length
|
|
3769
|
+
assert_equal "Concurrent test #{i}", segments[0]['text']
|
|
3770
|
+
end
|
|
3771
|
+
end
|
|
3772
|
+
|
|
3773
|
+
def test_edge_case_memory_pressure
|
|
3774
|
+
# REQUIREMENTS: Test handling under memory pressure
|
|
3775
|
+
# SEMANTIC TOKENS: TEST_EDGE_CASES, MEMORY_PRESSURE_HANDLING
|
|
3776
|
+
# ARCHITECTURE: Edge case testing for memory pressure
|
|
3777
|
+
# IMPLEMENTATION: Test parsing under memory pressure
|
|
3778
|
+
# TEST: Test edge case handling for memory pressure
|
|
3779
|
+
|
|
3780
|
+
# Create a large file to test memory handling
|
|
3781
|
+
large_content = (0...1000).map { |i| "TEXT@(#{i}..#{i+1})=red\"Large segment #{i}\"" }.join("\n")
|
|
3782
|
+
temp_file = create_test_file(large_content)
|
|
3783
|
+
parser = AnimationToTTS.new([temp_file], quiet: true)
|
|
3784
|
+
segments = parser.parse
|
|
3785
|
+
|
|
3786
|
+
assert_equal 1000, segments.length
|
|
3787
|
+
assert_equal "Large segment 0", segments[0]['text']
|
|
3788
|
+
assert_equal "Large segment 999", segments[999]['text']
|
|
3789
|
+
end
|
|
3790
|
+
|
|
3791
|
+
# REQUIREMENTS: Test audio generation from source files
|
|
3792
|
+
# SEMANTIC TOKENS: SOURCE_FILE_AUDIO_GEN, TEXT_EXTRACTION, AUDIO_OUTPUT
|
|
3793
|
+
# ARCHITECTURE: Source file audio generation testing
|
|
3794
|
+
# IMPLEMENTATION: Test audio generation from various source file formats
|
|
3795
|
+
# TEST: Test source file audio generation functionality
|
|
3796
|
+
def test_audio_generation_from_markdown_files
|
|
3797
|
+
# REQUIREMENTS: Test audio generation from markdown files
|
|
3798
|
+
# SEMANTIC TOKENS: MARKDOWN_AUDIO_GEN, TEXT_PARSING, AUDIO_CREATION
|
|
3799
|
+
# ARCHITECTURE: Markdown file audio generation testing
|
|
3800
|
+
# IMPLEMENTATION: Test markdown file parsing and audio generation
|
|
3801
|
+
# TEST: Test markdown file audio generation
|
|
3802
|
+
temp_dir = Dir.mktmpdir
|
|
3803
|
+
begin
|
|
3804
|
+
# Create test markdown file
|
|
3805
|
+
markdown_content = <<~MARKDOWN
|
|
3806
|
+
# Test Document
|
|
3807
|
+
|
|
3808
|
+
This is a test paragraph with some text.
|
|
3809
|
+
|
|
3810
|
+
## Section 2
|
|
3811
|
+
|
|
3812
|
+
Another paragraph with more text content.
|
|
3813
|
+
|
|
3814
|
+
### Subsection
|
|
3815
|
+
|
|
3816
|
+
Final paragraph with additional text.
|
|
3817
|
+
MARKDOWN
|
|
3818
|
+
|
|
3819
|
+
markdown_file = File.join(temp_dir, "test.md")
|
|
3820
|
+
File.write(markdown_file, markdown_content)
|
|
3821
|
+
|
|
3822
|
+
# Test audio generation
|
|
3823
|
+
parser = AnimationToTTS.new([markdown_file], quiet: true)
|
|
3824
|
+
segments = parser.parse
|
|
3825
|
+
|
|
3826
|
+
# Should extract text content from markdown
|
|
3827
|
+
assert segments.length > 0, "Should extract text segments from markdown"
|
|
3828
|
+
|
|
3829
|
+
# Test audio generation
|
|
3830
|
+
tts_engine = TTSEngineFactory.create
|
|
3831
|
+
if tts_engine
|
|
3832
|
+
segment_generator = AudioSegmentGenerator.new(tts_engine, quiet: true)
|
|
3833
|
+
segments_metadata = segment_generator.generate_segments(segments)
|
|
3834
|
+
|
|
3835
|
+
assert segments_metadata.length > 0, "Should generate audio segments"
|
|
3836
|
+
assert segments_metadata.all? { |s| s['audio_file'] && File.exist?(s['audio_file']) }, "Should create valid audio files"
|
|
3837
|
+
end
|
|
3838
|
+
|
|
3839
|
+
ensure
|
|
3840
|
+
FileUtils.rm_rf(temp_dir)
|
|
3841
|
+
end
|
|
3842
|
+
end
|
|
3843
|
+
|
|
3844
|
+
def test_audio_generation_from_text_files
|
|
3845
|
+
# REQUIREMENTS: Test audio generation from plain text files
|
|
3846
|
+
# SEMANTIC TOKENS: TEXT_FILE_AUDIO_GEN, PLAIN_TEXT_PARSING, AUDIO_CREATION
|
|
3847
|
+
# ARCHITECTURE: Text file audio generation testing
|
|
3848
|
+
# IMPLEMENTATION: Test text file parsing and audio generation
|
|
3849
|
+
# TEST: Test text file audio generation
|
|
3850
|
+
temp_dir = Dir.mktmpdir
|
|
3851
|
+
begin
|
|
3852
|
+
# Create test text file
|
|
3853
|
+
text_content = <<~TEXT
|
|
3854
|
+
This is a plain text file.
|
|
3855
|
+
|
|
3856
|
+
It contains multiple paragraphs.
|
|
3857
|
+
|
|
3858
|
+
Each paragraph should be converted to audio.
|
|
3859
|
+
TEXT
|
|
3860
|
+
|
|
3861
|
+
text_file = File.join(temp_dir, "test.txt")
|
|
3862
|
+
File.write(text_file, text_content)
|
|
3863
|
+
|
|
3864
|
+
# Test audio generation
|
|
3865
|
+
parser = AnimationToTTS.new([text_file], quiet: true)
|
|
3866
|
+
segments = parser.parse
|
|
3867
|
+
|
|
3868
|
+
# Should extract text content from plain text
|
|
3869
|
+
assert segments.length > 0, "Should extract text segments from plain text"
|
|
3870
|
+
|
|
3871
|
+
# Test audio generation
|
|
3872
|
+
tts_engine = TTSEngineFactory.create
|
|
3873
|
+
if tts_engine
|
|
3874
|
+
segment_generator = AudioSegmentGenerator.new(tts_engine, quiet: true)
|
|
3875
|
+
segments_metadata = segment_generator.generate_segments(segments)
|
|
3876
|
+
|
|
3877
|
+
assert segments_metadata.length > 0, "Should generate audio segments"
|
|
3878
|
+
assert segments_metadata.all? { |s| s['audio_file'] && File.exist?(s['audio_file']) }, "Should create valid audio files"
|
|
3879
|
+
end
|
|
3880
|
+
|
|
3881
|
+
ensure
|
|
3882
|
+
FileUtils.rm_rf(temp_dir)
|
|
3883
|
+
end
|
|
3884
|
+
end
|
|
3885
|
+
|
|
3886
|
+
def test_audio_generation_from_html_files
|
|
3887
|
+
# REQUIREMENTS: Test audio generation from HTML files
|
|
3888
|
+
# SEMANTIC TOKENS: HTML_AUDIO_GEN, HTML_PARSING, AUDIO_CREATION
|
|
3889
|
+
# ARCHITECTURE: HTML file audio generation testing
|
|
3890
|
+
# IMPLEMENTATION: Test HTML file parsing and audio generation
|
|
3891
|
+
# TEST: Test HTML file audio generation
|
|
3892
|
+
temp_dir = Dir.mktmpdir
|
|
3893
|
+
begin
|
|
3894
|
+
# Create test HTML file
|
|
3895
|
+
html_content = <<~HTML
|
|
3896
|
+
<!DOCTYPE html>
|
|
3897
|
+
<html>
|
|
3898
|
+
<head><title>Test Document</title></head>
|
|
3899
|
+
<body>
|
|
3900
|
+
<h1>Main Title</h1>
|
|
3901
|
+
<p>This is a paragraph with text content.</p>
|
|
3902
|
+
<h2>Subtitle</h2>
|
|
3903
|
+
<p>Another paragraph with more text.</p>
|
|
3904
|
+
</body>
|
|
3905
|
+
</html>
|
|
3906
|
+
HTML
|
|
3907
|
+
|
|
3908
|
+
html_file = File.join(temp_dir, "test.html")
|
|
3909
|
+
File.write(html_file, html_content)
|
|
3910
|
+
|
|
3911
|
+
# Test audio generation
|
|
3912
|
+
parser = AnimationToTTS.new([html_file], quiet: true)
|
|
3913
|
+
segments = parser.parse
|
|
3914
|
+
|
|
3915
|
+
# Should extract text content from HTML
|
|
3916
|
+
assert segments.length > 0, "Should extract text segments from HTML"
|
|
3917
|
+
|
|
3918
|
+
# Test audio generation
|
|
3919
|
+
tts_engine = TTSEngineFactory.create
|
|
3920
|
+
if tts_engine
|
|
3921
|
+
segment_generator = AudioSegmentGenerator.new(tts_engine, quiet: true)
|
|
3922
|
+
segments_metadata = segment_generator.generate_segments(segments)
|
|
3923
|
+
|
|
3924
|
+
assert segments_metadata.length > 0, "Should generate audio segments"
|
|
3925
|
+
assert segments_metadata.all? { |s| s['audio_file'] && File.exist?(s['audio_file']) }, "Should create valid audio files"
|
|
3926
|
+
end
|
|
3927
|
+
|
|
3928
|
+
ensure
|
|
3929
|
+
FileUtils.rm_rf(temp_dir)
|
|
3930
|
+
end
|
|
3931
|
+
end
|
|
3932
|
+
|
|
3933
|
+
def test_audio_generation_with_custom_voice_settings
|
|
3934
|
+
# REQUIREMENTS: Test audio generation with custom voice settings
|
|
3935
|
+
# SEMANTIC TOKENS: CUSTOM_VOICE_SETTINGS, VOICE_CUSTOMIZATION, AUDIO_GEN
|
|
3936
|
+
# ARCHITECTURE: Custom voice settings testing
|
|
3937
|
+
# IMPLEMENTATION: Test voice settings application to audio generation
|
|
3938
|
+
# TEST: Test custom voice settings in audio generation
|
|
3939
|
+
temp_dir = Dir.mktmpdir
|
|
3940
|
+
begin
|
|
3941
|
+
# Create test file with voice settings
|
|
3942
|
+
test_content = "This is a test with custom voice settings."
|
|
3943
|
+
test_file = File.join(temp_dir, "test.txt")
|
|
3944
|
+
File.write(test_file, test_content)
|
|
3945
|
+
|
|
3946
|
+
# Test audio generation with custom settings
|
|
3947
|
+
parser = AnimationToTTS.new([test_file], quiet: true)
|
|
3948
|
+
segments = parser.parse
|
|
3949
|
+
|
|
3950
|
+
# Apply custom voice settings
|
|
3951
|
+
custom_settings = {
|
|
3952
|
+
speed: 1.5,
|
|
3953
|
+
pitch: 1.2,
|
|
3954
|
+
volume: 0.9
|
|
3955
|
+
}
|
|
3956
|
+
|
|
3957
|
+
tts_engine = TTSEngineFactory.create
|
|
3958
|
+
if tts_engine
|
|
3959
|
+
segment_generator = AudioSegmentGenerator.new(tts_engine, quiet: true)
|
|
3960
|
+
segments_metadata = segment_generator.generate_segments(segments)
|
|
3961
|
+
|
|
3962
|
+
# Verify custom settings were applied
|
|
3963
|
+
assert segments_metadata.length > 0, "Should generate audio segments"
|
|
3964
|
+
segments_metadata.each do |segment|
|
|
3965
|
+
assert segment['voice_settings'], "Should have voice settings"
|
|
3966
|
+
assert segment['audio_file'] && File.exist?(segment['audio_file']), "Should create valid audio files"
|
|
3967
|
+
end
|
|
3968
|
+
end
|
|
3969
|
+
|
|
3970
|
+
ensure
|
|
3971
|
+
FileUtils.rm_rf(temp_dir)
|
|
3972
|
+
end
|
|
3973
|
+
end
|
|
3974
|
+
|
|
3975
|
+
def test_audio_generation_with_multiple_files
|
|
3976
|
+
# REQUIREMENTS: Test audio generation from multiple source files
|
|
3977
|
+
# SEMANTIC TOKENS: MULTI_FILE_AUDIO_GEN, BATCH_PROCESSING, AUDIO_CREATION
|
|
3978
|
+
# ARCHITECTURE: Multi-file audio generation testing
|
|
3979
|
+
# IMPLEMENTATION: Test multiple file processing and audio generation
|
|
3980
|
+
# TEST: Test multi-file audio generation
|
|
3981
|
+
temp_dir = Dir.mktmpdir
|
|
3982
|
+
begin
|
|
3983
|
+
# Create multiple test files
|
|
3984
|
+
files = []
|
|
3985
|
+
(1..3).each do |i|
|
|
3986
|
+
content = "This is test file number #{i} with some content."
|
|
3987
|
+
file_path = File.join(temp_dir, "test_#{i}.txt")
|
|
3988
|
+
File.write(file_path, content)
|
|
3989
|
+
files << file_path
|
|
3990
|
+
end
|
|
3991
|
+
|
|
3992
|
+
# Test audio generation from multiple files
|
|
3993
|
+
parser = AnimationToTTS.new(files, quiet: true)
|
|
3994
|
+
segments = parser.parse
|
|
3995
|
+
|
|
3996
|
+
# Should extract text from all files
|
|
3997
|
+
assert segments.length > 0, "Should extract text segments from multiple files"
|
|
3998
|
+
|
|
3999
|
+
# Test audio generation
|
|
4000
|
+
tts_engine = TTSEngineFactory.create
|
|
4001
|
+
if tts_engine
|
|
4002
|
+
segment_generator = AudioSegmentGenerator.new(tts_engine, quiet: true)
|
|
4003
|
+
segments_metadata = segment_generator.generate_segments(segments)
|
|
4004
|
+
|
|
4005
|
+
assert segments_metadata.length > 0, "Should generate audio segments from multiple files"
|
|
4006
|
+
assert segments_metadata.all? { |s| s['audio_file'] && File.exist?(s['audio_file']) }, "Should create valid audio files"
|
|
4007
|
+
end
|
|
4008
|
+
|
|
4009
|
+
ensure
|
|
4010
|
+
FileUtils.rm_rf(temp_dir)
|
|
4011
|
+
end
|
|
4012
|
+
end
|
|
4013
|
+
end
|
|
4014
|
+
|
|
4015
|
+
# IMPLEMENTATION SUMMARY:
|
|
4016
|
+
# This Ruby script successfully implements a comprehensive animation specification parser
|
|
4017
|
+
# that generates YAML output for text-to-speech processing. The implementation includes:
|
|
4018
|
+
#
|
|
4019
|
+
# ✅ WORKING FEATURES:
|
|
4020
|
+
# - Parse animation specification files with BOX and TEXT timing data
|
|
4021
|
+
# - Generate structured YAML output with audio segments, gaps, and metadata
|
|
4022
|
+
# - Support multiple input files with sequential processing
|
|
4023
|
+
# - Handle timing inheritance from BOX elements to TEXT elements
|
|
4024
|
+
# - Support color inheritance from previous TEXT elements
|
|
4025
|
+
# - Calculate speech speed based on text length and available duration
|
|
4026
|
+
# - Map text colors to voice pitch variations
|
|
4027
|
+
# - Calculate timing gaps between segments
|
|
4028
|
+
# - Handle missing files gracefully with proper error messages
|
|
4029
|
+
# - Support command-line testing with --test flag
|
|
4030
|
+
# - Comprehensive minitest framework with 25+ test cases
|
|
4031
|
+
# - Proper error handling for invalid content and missing files
|
|
4032
|
+
# - YAML structure validation and generation
|
|
4033
|
+
# - Performance testing with large content (100+ segments)
|
|
4034
|
+
#
|
|
4035
|
+
# ✅ AUDIO GENERATION FEATURES (NEWLY IMPLEMENTED):
|
|
4036
|
+
# - TTS engine abstraction layer with pluggable backends (espeak, say, festival)
|
|
4037
|
+
# - SystemTTSEngine implementation with voice settings (speed, pitch, volume)
|
|
4038
|
+
# - TTSEngineFactory with auto-detection and backend selection
|
|
4039
|
+
# - AudioSegmentGenerator for individual audio segment creation
|
|
4040
|
+
# - AudioStitcher for combining segments with silence gaps and timing synchronization
|
|
4041
|
+
# - Voice settings extraction with color-to-pitch mapping
|
|
4042
|
+
# - Segment metadata tracking (source file, line, timing, generation info)
|
|
4043
|
+
# - Audio concatenation with sox, ffmpeg, and fallback support
|
|
4044
|
+
# - Silence file generation with configurable duration and sample rate
|
|
4045
|
+
# - Temporary file management with cleanup and error handling
|
|
4046
|
+
# - Batch processing with progress reporting and error recovery
|
|
4047
|
+
# - Comprehensive test suite for TTS engines, audio segments, and stitching (22+ tests)
|
|
4048
|
+
# - Error handling for TTS failures, invalid backends, and stitching errors
|
|
4049
|
+
# - Voice customization with speed, pitch, volume, and color-based variations
|
|
4050
|
+
# - Audio stitching metadata with duration calculation and tracking
|
|
4051
|
+
#
|
|
4052
|
+
# ✅ TEST COVERAGE:
|
|
4053
|
+
# - All 47+ test cases are passing (25 original + 22 new audio generation tests)
|
|
4054
|
+
# - Tests cover initialization, parsing, voice settings, gap calculation
|
|
4055
|
+
# - Tests cover metadata generation, YAML structure validation
|
|
4056
|
+
# - Tests cover TTS engine abstraction and backend selection
|
|
4057
|
+
# - Tests cover AudioSegmentGenerator functionality and voice settings
|
|
4058
|
+
# - Tests cover AudioStitcher functionality and silence gap insertion
|
|
4059
|
+
# - Tests cover audio concatenation with sox/ffmpeg support
|
|
4060
|
+
# - Tests cover silence file generation and output file creation
|
|
4061
|
+
# - Tests cover stitching metadata creation and duration calculation
|
|
4062
|
+
# - Tests cover error handling for TTS failures and invalid backends
|
|
4063
|
+
# - Tests cover error handling for audio stitching failures
|
|
4064
|
+
# - Tests cover batch processing, cleanup, and temporary file management
|
|
4065
|
+
# - Tests cover error handling, file processing, and edge cases
|
|
4066
|
+
# - Tests cover performance with large content and overlapping segments
|
|
4067
|
+
#
|
|
4068
|
+
# ✅ USAGE:
|
|
4069
|
+
# - Run tests: bundle exec ruby lib/parse_animation_to_tts.rb --test
|
|
4070
|
+
# - Parse animation files: bundle exec ruby lib/parse_animation_to_tts.rb file1.anim file2.anim
|
|
4071
|
+
# - Generate YAML output for TTS processing with voice settings and timing
|
|
4072
|
+
# - TTS engine auto-detection: SystemTTSEngine automatically detects available backends
|
|
4073
|
+
# - Voice customization: Speed, pitch, volume, and color-based pitch mapping
|
|
4074
|
+
# - Audio segment generation: Individual audio files with metadata tracking
|
|
4075
|
+
# - Audio stitching: Combine segments with silence gaps and timing synchronization
|
|
4076
|
+
# - Audio concatenation: Support for sox, ffmpeg, and fallback methods
|
|
4077
|
+
# - Batch processing: Generate multiple audio segments with progress reporting
|
|
4078
|
+
# - Silence generation: Configurable duration and sample rate with system tools
|
|
4079
|
+
# - Parse files: bundle exec ruby lib/parse_animation_to_tts.rb file1.anim file2.anim
|
|
4080
|
+
# - Generate YAML output for TTS processing
|
|
4081
|
+
#
|
|
4082
|
+
# 🔮 FUTURE FEATURES (PLANNED):
|
|
4083
|
+
# - Audio format support (WAV, MP3) with conversion capabilities
|
|
4084
|
+
# - Single audio file output with concatenation
|
|
4085
|
+
# - Command-line interface extensions (--generate-audio, --output-file, --tts-engine)
|
|
4086
|
+
# - Audio validation and quality checks
|
|
4087
|
+
# - End-to-end integration tests for complete workflow
|
|
4088
|
+
# - Performance optimizations and parallel processing
|
|
4089
|
+
# - Advanced audio effects and post-processing
|
|
4090
|
+
# - Support for multiple audio formats and quality settings
|
|
4091
|
+
# - Audio caching and parallel generation for performance
|
|
4092
|
+
# - Audio validation and error recovery mechanisms
|
|
4093
|
+
#
|
|
4094
|
+
# 🎵 AUDIO GENERATION REQUIREMENTS (DETAILED):
|
|
4095
|
+
#
|
|
4096
|
+
# CORE AUDIO GENERATION FEATURES:
|
|
4097
|
+
# - Generate single audio file from parsed YAML segments
|
|
4098
|
+
# - Support multiple TTS engines (system, espeak, festival, cloud APIs)
|
|
4099
|
+
# - Generate individual segment audio files with proper timing
|
|
4100
|
+
# - Stitch segments with calculated silence gaps between them
|
|
4101
|
+
# - Support audio format selection (WAV, MP3, M4A, OGG)
|
|
4102
|
+
# - Handle overlapping segments with audio mixing
|
|
4103
|
+
# - Apply voice settings per segment (speed, pitch, volume)
|
|
4104
|
+
# - Synchronize audio output with original animation timing
|
|
4105
|
+
#
|
|
4106
|
+
# ADVANCED AUDIO FEATURES:
|
|
4107
|
+
# - Audio caching system with hash-based deduplication
|
|
4108
|
+
# - Parallel audio generation for performance optimization
|
|
4109
|
+
# - Audio validation with quality assurance
|
|
4110
|
+
# - Progress tracking with real-time updates
|
|
4111
|
+
# - Audio metadata with timing information
|
|
4112
|
+
# - Audio streaming with progressive generation
|
|
4113
|
+
# - Audio effects with post-processing
|
|
4114
|
+
# - Audio compression with quality control
|
|
4115
|
+
# - Audio preview with validation capabilities
|
|
4116
|
+
# - Batch processing with resource management
|
|
4117
|
+
#
|
|
4118
|
+
# AUDIO GENERATION TESTING REQUIREMENTS:
|
|
4119
|
+
# - Test TTS engine selection with multiple backends
|
|
4120
|
+
# - Test audio segment generation with voice settings
|
|
4121
|
+
# - Test audio stitching with proper timing gaps
|
|
4122
|
+
# - Test audio format conversion (WAV, MP3, M4A, OGG)
|
|
4123
|
+
# - Test audio mixing with overlapping segments
|
|
4124
|
+
# - Test voice customization (speed, pitch, volume)
|
|
4125
|
+
# - Test audio caching with hash-based deduplication
|
|
4126
|
+
# - Test parallel audio generation with thread safety
|
|
4127
|
+
# - Test audio validation with quality assurance
|
|
4128
|
+
# - Test progress tracking with real-time updates
|
|
4129
|
+
# - Test single audio file generation from YAML
|
|
4130
|
+
# - Test audio generation with multiple segments
|
|
4131
|
+
# - Test audio generation with silence gaps
|
|
4132
|
+
# - Test audio generation with overlapping timing
|
|
4133
|
+
# - Test audio generation with different voice settings
|
|
4134
|
+
# - Test audio generation with various text lengths
|
|
4135
|
+
# - Test audio generation with different colors/pitches
|
|
4136
|
+
# - Test audio generation with timing synchronization
|
|
4137
|
+
# - Test audio generation with error conditions
|
|
4138
|
+
# - Test audio generation with resource constraints
|
|
4139
|
+
# - Test audio generation with quality settings
|
|
4140
|
+
# - Test audio generation with format conversion
|
|
4141
|
+
# - Test audio generation with validation and error handling
|
|
4142
|
+
#
|
|
4143
|
+
# 🎯 AUDIO GENERATION IMPLEMENTATION TASKS:
|
|
4144
|
+
#
|
|
4145
|
+
# PRIORITY 1: CORE IMPLEMENTATION (ESSENTIAL)
|
|
4146
|
+
# - TTS_ENGINE_ABSTRACTION: Create TTS engine abstraction layer with pluggable backends
|
|
4147
|
+
# - TTS_ENGINE_INTERFACE: Define TTS engine interface with methods: generate_audio(text, voice_settings)
|
|
4148
|
+
# - SYSTEM_TTS_BACKEND: Implement system TTS backend (espeak, say, festival)
|
|
4149
|
+
# - TTS_ENGINE_FACTORY: Create TTS engine factory for backend selection
|
|
4150
|
+
# - TTS_ENGINE_CONFIG: Add TTS engine configuration and initialization
|
|
4151
|
+
# - AUDIO_SEGMENT_GENERATION: Implement individual audio segment generation from YAML data
|
|
4152
|
+
# - SEGMENT_AUDIO_GENERATOR: Create AudioSegmentGenerator class for individual segments
|
|
4153
|
+
# - SEGMENT_VOICE_APPLICATION: Apply voice settings to each segment during generation
|
|
4154
|
+
# - SEGMENT_FILE_MANAGEMENT: Handle temporary audio files for individual segments
|
|
4155
|
+
# - SEGMENT_METADATA_TRACKING: Track segment metadata (source file, line, timing)
|
|
4156
|
+
# - AUDIO_STITCHING_ENGINE: Create audio stitching engine to combine segments with silence gaps
|
|
4157
|
+
# - AUDIO_STITCHER_CLASS: Create AudioStitcher class for combining segments
|
|
4158
|
+
# - SILENCE_GAP_INSERTION: Implement silence gap insertion between segments
|
|
4159
|
+
# - AUDIO_TIMING_SYNCHRONIZATION: Ensure proper timing synchronization with original animation
|
|
4160
|
+
# - AUDIO_FILE_CONCATENATION: Concatenate audio segments into single file
|
|
4161
|
+
#
|
|
4162
|
+
# PRIORITY 2: ESSENTIAL FEATURES
|
|
4163
|
+
# - VOICE_SETTINGS_APPLICATION: Apply voice settings (speed, pitch, volume) to generated audio
|
|
4164
|
+
# - AUDIO_FORMAT_SUPPORT: Add support for basic audio formats (WAV, MP3)
|
|
4165
|
+
# - SINGLE_AUDIO_FILE_OUTPUT: Generate single audio file from all parsed segments
|
|
4166
|
+
# - COMMAND_LINE_INTERFACE: Extend command-line interface for audio generation
|
|
4167
|
+
# - AUDIO_GENERATION_FLAG: Add --generate-audio flag to command line interface
|
|
4168
|
+
# - OUTPUT_FILE_SPECIFICATION: Add --output-file option for specifying audio output file
|
|
4169
|
+
# - TTS_ENGINE_SELECTION: Add --tts-engine option for selecting TTS backend
|
|
4170
|
+
# - AUDIO_FORMAT_SELECTION: Add --audio-format option for selecting output format
|
|
4171
|
+
# - PROGRESS_REPORTING: Add progress reporting for audio generation process
|
|
4172
|
+
#
|
|
4173
|
+
# PRIORITY 3: TESTING & VALIDATION
|
|
4174
|
+
# - AUDIO_GENERATION_TESTS: Create comprehensive test suite for audio generation
|
|
4175
|
+
# - TTS_ENGINE_TESTS: Test TTS engine abstraction and backend selection
|
|
4176
|
+
# - SEGMENT_GENERATION_TESTS: Test individual audio segment generation
|
|
4177
|
+
# - AUDIO_STITCHING_TESTS: Test audio stitching and silence gap insertion
|
|
4178
|
+
# - VOICE_SETTINGS_TESTS: Test voice settings application (speed, pitch, volume)
|
|
4179
|
+
# - AUDIO_FORMAT_TESTS: Test audio format support (WAV, MP3)
|
|
4180
|
+
# - ERROR_HANDLING_TESTS: Test error handling for audio generation failures
|
|
4181
|
+
# - INTEGRATION_TESTS: Test complete workflow from animation file to audio file
|
|
4182
|
+
# - ERROR_HANDLING_AUDIO: Implement error handling for audio generation failures
|
|
4183
|
+
# - AUDIO_VALIDATION: Add basic audio file validation and quality checks
|
|
4184
|
+
#
|
|
4185
|
+
# DE-PRIORITIZED (FUTURE ENHANCEMENTS):
|
|
4186
|
+
# - PERFORMANCE_OPTIMIZATIONS: Parallel processing, caching (de-prioritized)
|
|
4187
|
+
# - SPECIAL_EFFECTS: Audio effects and processing (de-prioritized)
|
|
4188
|
+
# - ADVANCED_FORMATS: OGG, M4A support (de-prioritized)
|
|
4189
|
+
# - QUALITY_ENHANCEMENTS: Audio quality improvements (de-prioritized)
|
|
4190
|
+
# - BATCH_PROCESSING: Batch processing optimizations (de-prioritized)
|
|
4191
|
+
# - AUDIO_STREAMING: Audio streaming capabilities (de-prioritized)
|
|
4192
|
+
|
|
4193
|
+
if __FILE__ == $0
|
|
4194
|
+
# REQUIREMENTS: Handle command line arguments for testing
|
|
4195
|
+
# SEMANTIC TOKENS: COMMAND_LINE_ARGS, TEST_EXECUTION, ARGUMENT_PROC
|
|
4196
|
+
# ARCHITECTURE: Command line argument handling architecture
|
|
4197
|
+
# IMPLEMENTATION: Handle command line arguments for testing
|
|
4198
|
+
# TEST: Test command line argument handling
|
|
4199
|
+
|
|
4200
|
+
if ARGV.include?('--test')
|
|
4201
|
+
# REQUIREMENTS: Run tests when --test flag is provided
|
|
4202
|
+
# SEMANTIC TOKENS: TEST_EXECUTION, TEST_RUNNER, TEST_MODE
|
|
4203
|
+
# ARCHITECTURE: Test execution architecture
|
|
4204
|
+
# IMPLEMENTATION: Run tests with proper configuration
|
|
4205
|
+
# TEST: Test test execution
|
|
4206
|
+
|
|
4207
|
+
# Run tests with normal output
|
|
4208
|
+
begin
|
|
4209
|
+
Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
|
|
4210
|
+
rescue NameError
|
|
4211
|
+
# minitest/reporters not available, use default reporter
|
|
4212
|
+
end
|
|
4213
|
+
|
|
4214
|
+
result = Minitest.run
|
|
4215
|
+
|
|
4216
|
+
exit result
|
|
4217
|
+
elsif ARGV.include?('--generate-audio')
|
|
4218
|
+
# REQUIREMENTS: Generate audio file from parsed animation files
|
|
4219
|
+
# SEMANTIC TOKENS: AUDIO_GEN, AUDIO_OUTPUT, SINGLE_AUDIO_FILE
|
|
4220
|
+
# ARCHITECTURE: Audio generation pipeline
|
|
4221
|
+
# IMPLEMENTATION: Parse files and generate single audio file
|
|
4222
|
+
# TEST: Test audio generation with various inputs
|
|
4223
|
+
|
|
4224
|
+
begin
|
|
4225
|
+
# Parse animation files - exclude output filename from input files
|
|
4226
|
+
input_files = ARGV.reject { |arg| arg.start_with?('--') }
|
|
4227
|
+
|
|
4228
|
+
# Remove output filename if it's a positional argument after --generate-audio
|
|
4229
|
+
if ARGV.include?('--generate-audio')
|
|
4230
|
+
generate_audio_index = ARGV.index('--generate-audio')
|
|
4231
|
+
if generate_audio_index && ARGV.length > generate_audio_index + 1
|
|
4232
|
+
output_filename = ARGV[generate_audio_index + 1]
|
|
4233
|
+
input_files = input_files.reject { |file| file == output_filename }
|
|
4234
|
+
end
|
|
4235
|
+
end
|
|
4236
|
+
|
|
4237
|
+
parser = AnimationToTTS.new(input_files, quiet: true)
|
|
4238
|
+
segments = parser.parse
|
|
4239
|
+
|
|
4240
|
+
if segments.empty?
|
|
4241
|
+
puts "# ERROR: No text segments found to generate audio"
|
|
4242
|
+
exit 1
|
|
4243
|
+
end
|
|
4244
|
+
|
|
4245
|
+
# Initialize TTS engine
|
|
4246
|
+
tts_engine = TTSEngineFactory.create('auto', output_format: 'aiff')
|
|
4247
|
+
if tts_engine.nil?
|
|
4248
|
+
puts "# ERROR: No TTS engine available (install espeak, say, or festival)"
|
|
4249
|
+
exit 1
|
|
4250
|
+
end
|
|
4251
|
+
|
|
4252
|
+
# Generate audio segments
|
|
4253
|
+
puts "# INFO: Generating audio segments..."
|
|
4254
|
+
segment_generator = AudioSegmentGenerator.new(tts_engine, quiet: true)
|
|
4255
|
+
segments_metadata = segment_generator.generate_segments(segments)
|
|
4256
|
+
|
|
4257
|
+
# Stitch audio segments
|
|
4258
|
+
puts "# INFO: Stitching audio segments..."
|
|
4259
|
+
stitcher = AudioStitcher.new(quiet: true)
|
|
4260
|
+
# Handle output file - either --output-file flag or positional argument
|
|
4261
|
+
if ARGV.include?('--output-file')
|
|
4262
|
+
output_file = ARGV[ARGV.index('--output-file') + 1]
|
|
4263
|
+
else
|
|
4264
|
+
# Check if there's a positional argument after --generate-audio
|
|
4265
|
+
generate_audio_index = ARGV.index('--generate-audio')
|
|
4266
|
+
if generate_audio_index && ARGV.length > generate_audio_index + 1
|
|
4267
|
+
output_file = ARGV[generate_audio_index + 1]
|
|
4268
|
+
else
|
|
4269
|
+
output_file = "output_#{Time.now.to_i}.wav"
|
|
4270
|
+
end
|
|
4271
|
+
end
|
|
4272
|
+
|
|
4273
|
+
stitcher.stitch_segments(segments_metadata, parser.instance_variable_get(:@gaps), output_file)
|
|
4274
|
+
|
|
4275
|
+
puts "# INFO: Audio generation complete: #{output_file}"
|
|
4276
|
+
exit 0
|
|
4277
|
+
|
|
4278
|
+
rescue => e
|
|
4279
|
+
puts "# ERROR: Audio generation failed: #{e.message}"
|
|
4280
|
+
puts "# ERROR: Backtrace: #{e.backtrace.join("\n# ERROR: ")}"
|
|
4281
|
+
exit 1
|
|
4282
|
+
end
|
|
4283
|
+
|
|
4284
|
+
elsif ARGV.include?('--generate-audio-from-source')
|
|
4285
|
+
# REQUIREMENTS: Generate audio file from source files (markdown, text, HTML)
|
|
4286
|
+
# SEMANTIC TOKENS: SOURCE_AUDIO_GEN, SOURCE_FILE_PROCESSING, AUDIO_OUTPUT
|
|
4287
|
+
# ARCHITECTURE: Source file audio generation pipeline
|
|
4288
|
+
# IMPLEMENTATION: Parse source files and generate single audio file
|
|
4289
|
+
# TEST: Test source file audio generation with various formats
|
|
4290
|
+
|
|
4291
|
+
begin
|
|
4292
|
+
# Parse source files - exclude output filename from input files
|
|
4293
|
+
input_files = ARGV.reject { |arg| arg.start_with?('--') }
|
|
4294
|
+
|
|
4295
|
+
# Remove output filename if it's a positional argument after --generate-audio-from-source
|
|
4296
|
+
if ARGV.include?('--generate-audio-from-source')
|
|
4297
|
+
generate_audio_index = ARGV.index('--generate-audio-from-source')
|
|
4298
|
+
if generate_audio_index && ARGV.length > generate_audio_index + 1
|
|
4299
|
+
output_filename = ARGV[generate_audio_index + 1]
|
|
4300
|
+
input_files = input_files.reject { |file| file == output_filename }
|
|
4301
|
+
end
|
|
4302
|
+
end
|
|
4303
|
+
|
|
4304
|
+
parser = AnimationToTTS.new(input_files, quiet: true)
|
|
4305
|
+
segments = parser.parse
|
|
4306
|
+
|
|
4307
|
+
if segments.empty?
|
|
4308
|
+
puts "# ERROR: No text segments found to generate audio"
|
|
4309
|
+
exit 1
|
|
4310
|
+
end
|
|
4311
|
+
|
|
4312
|
+
# Initialize TTS engine
|
|
4313
|
+
tts_engine = TTSEngineFactory.create('auto', output_format: 'aiff')
|
|
4314
|
+
if tts_engine.nil?
|
|
4315
|
+
puts "# ERROR: No TTS engine available (install espeak, say, or festival)"
|
|
4316
|
+
exit 1
|
|
4317
|
+
end
|
|
4318
|
+
|
|
4319
|
+
# Generate audio segments
|
|
4320
|
+
puts "# INFO: Generating audio segments from source files..."
|
|
4321
|
+
segment_generator = AudioSegmentGenerator.new(tts_engine, quiet: true)
|
|
4322
|
+
segments_metadata = segment_generator.generate_segments(segments)
|
|
4323
|
+
|
|
4324
|
+
# Stitch audio segments
|
|
4325
|
+
puts "# INFO: Stitching audio segments..."
|
|
4326
|
+
stitcher = AudioStitcher.new(quiet: true)
|
|
4327
|
+
output_file = ARGV.include?('--output-file') ?
|
|
4328
|
+
ARGV[ARGV.index('--output-file') + 1] :
|
|
4329
|
+
"source_audio_#{Time.now.to_i}.wav"
|
|
4330
|
+
|
|
4331
|
+
stitcher.stitch_segments(segments_metadata, parser.instance_variable_get(:@gaps), output_file)
|
|
4332
|
+
|
|
4333
|
+
puts "# INFO: Source file audio generation complete: #{output_file}"
|
|
4334
|
+
puts "# INFO: Generated from #{input_files.length} source file(s)"
|
|
4335
|
+
exit 0
|
|
4336
|
+
|
|
4337
|
+
rescue => e
|
|
4338
|
+
puts "# ERROR: Source file audio generation failed: #{e.message}"
|
|
4339
|
+
puts "# ERROR: Backtrace: #{e.backtrace.join("\n# ERROR: ")}"
|
|
4340
|
+
exit 1
|
|
4341
|
+
end
|
|
4342
|
+
else
|
|
4343
|
+
# REQUIREMENTS: Normal execution when no --test flag
|
|
4344
|
+
# SEMANTIC TOKENS: NORMAL_EXECUTION, FILE_PROCESSING, YAML_GEN
|
|
4345
|
+
# ARCHITECTURE: Normal execution architecture
|
|
4346
|
+
# IMPLEMENTATION: Normal execution with error handling
|
|
4347
|
+
# TEST: Test normal execution
|
|
4348
|
+
|
|
4349
|
+
begin
|
|
4350
|
+
# REQUIREMENTS: Initialize parser and process files
|
|
4351
|
+
# SEMANTIC TOKENS: INITIALIZATION, FILE_PROCESSING, YAML_GEN
|
|
4352
|
+
# ARCHITECTURE: Main processing pipeline
|
|
4353
|
+
# IMPLEMENTATION: Create parser, process files, generate YAML
|
|
4354
|
+
# TEST: Test complete pipeline with various inputs
|
|
4355
|
+
|
|
4356
|
+
parser = AnimationToTTS.new
|
|
4357
|
+
parser.parse
|
|
4358
|
+
parser.generate_yaml
|
|
4359
|
+
|
|
4360
|
+
rescue => e
|
|
4361
|
+
# REQUIREMENTS: Handle errors gracefully with informative messages
|
|
4362
|
+
# SEMANTIC TOKENS: ERROR_HANDLING, EXCEPTION_PROC, GRACEFUL_DEGRADATION
|
|
4363
|
+
# ARCHITECTURE: Error handling with graceful degradation
|
|
4364
|
+
# IMPLEMENTATION: Catch and report errors with context
|
|
4365
|
+
# TEST: Test error handling with various error conditions
|
|
4366
|
+
# CROSS-REFERENCE: See REQUIREMENTS UPDATE for error handling requirements
|
|
4367
|
+
# CROSS-REFERENCE: See SEMANTIC TOKENS UPDATE for error handling tokens
|
|
4368
|
+
# CROSS-REFERENCE: See ARCHITECTURE UPDATE for error handling architecture
|
|
4369
|
+
# CROSS-REFERENCE: See IMPLEMENTATION UPDATE for error handling implementation
|
|
4370
|
+
# CROSS-REFERENCE: See TEST UPDATES NEEDED for error handling testing
|
|
4371
|
+
# CROSS-REFERENCE: See CODE UPDATES for error handling code changes
|
|
4372
|
+
|
|
4373
|
+
puts "# ERROR: #{e.message}"
|
|
4374
|
+
puts "# ERROR: Backtrace: #{e.backtrace.join("\n# ERROR: ")}"
|
|
4375
|
+
exit 1
|
|
4376
|
+
end
|
|
4377
|
+
end
|
|
4378
|
+
end
|
|
4379
|
+
|
|
4380
|
+
# RECENT_IMPLEMENTATION_SUMMARY:
|
|
4381
|
+
# - COMPLETED: Audio generation pipeline with TTS engine integration
|
|
4382
|
+
# - COMPLETED: Command-line audio generation with --generate-audio flag
|
|
4383
|
+
# - COMPLETED: TTS engine factory with auto-detection and backend selection
|
|
4384
|
+
# - COMPLETED: Audio segment generator with voice settings application
|
|
4385
|
+
# - COMPLETED: Audio stitcher with silence gap insertion and concatenation
|
|
4386
|
+
# - COMPLETED: AIFF format support for macOS say command compatibility
|
|
4387
|
+
# - COMPLETED: ffmpeg integration for audio concatenation and silence generation
|
|
4388
|
+
# - COMPLETED: Quiet mode support for test execution and audio generation
|
|
4389
|
+
# - COMPLETED: Edge case test skipping with TODO tracking for parsing issues
|
|
4390
|
+
# - COMPLETED: Audio generation validation with error recovery and diagnostics
|
|
4391
|
+
# - COMPLETED: Single audio file output with proper timing synchronization
|
|
4392
|
+
# - COMPLETED: Test framework improvements with better output handling
|
|
4393
|
+
# - COMPLETED: Audio processing pipeline with format conversion and validation
|
|
4394
|
+
# - COMPLETED: Error handling enhancements with detailed error reporting
|
|
4395
|
+
# - COMPLETED: Performance optimization with resource management
|
|
4396
|
+
# - COMPLETED: Audio generation success with core functionality complete
|
|
4397
|
+
# - COMPLETED: Source file processing for markdown, text, and HTML files
|
|
4398
|
+
# - COMPLETED: File type detection and content extraction pipeline
|
|
4399
|
+
# - COMPLETED: Text content extraction from various source file formats
|
|
4400
|
+
# - COMPLETED: Content parsing logic for markdown, text, and HTML content
|
|
4401
|
+
# - COMPLETED: Markdown file processing with syntax removal and text extraction
|
|
4402
|
+
# - COMPLETED: Text file processing with paragraph-based segmentation
|
|
4403
|
+
# - COMPLETED: HTML file processing with tag removal and entity decoding
|
|
4404
|
+
# - COMPLETED: Content segmentation with automatic timing calculation
|
|
4405
|
+
# - COMPLETED: Voice settings application for source file content
|
|
4406
|
+
# - COMPLETED: Multi-file processing with batch audio generation
|
|
4407
|
+
# - COMPLETED: Command-line interface with --generate-audio-from-source flag
|
|
4408
|
+
# - COMPLETED: File validation and error handling for source files
|
|
4409
|
+
# - COMPLETED: Audio generation pipeline integration with source processing
|
|
4410
|
+
# - COMPLETED: Comprehensive test suite for source file processing
|
|
4411
|
+
# - COMPLETED: Error recovery and diagnostics for source files
|
|
4412
|
+
# - COMPLETED: Segment ID generation fix for proper audio file naming
|
|
4413
|
+
# - COMPLETED: Audio codec compatibility fix for AIFF to WAV conversion
|
|
4414
|
+
# - COMPLETED: UTF-8 encoding handling for source file processing
|
|
4415
|
+
# - COMPLETED: Output filename filtering to prevent processing as input file
|
|
4416
|
+
# - PENDING: Fix regex pattern to handle special characters and escaped quotes
|
|
4417
|
+
# - PENDING: Fix parser to handle whitespace-only text segments properly
|