Spaces:
Sleeping
Sleeping
| """ | |
| Input validation utilities for the Sunbird AI App. | |
| Pure functions — no I/O, fully unit-testable. | |
| """ | |
| from __future__ import annotations | |
| import io | |
| import mutagen | |
| ACCEPTED_AUDIO_FORMATS = {"wav", "mp3", "m4a", "ogg", "aac"} | |
| MAX_AUDIO_DURATION_SECONDS = 300 # 5 minutes | |
| SUPPORTED_LANGUAGES = ["Luganda", "Runyankole", "Ateso", "Lugbara", "Acholi"] | |
| def validate_text_input(text: str) -> tuple[bool, str]: | |
| """Returns (True, "") for valid non-whitespace text; (False, error_msg) otherwise.""" | |
| if not text or not text.strip(): | |
| return False, "Please enter some text before submitting." | |
| return True, "" | |
| def validate_audio_format(filename: str) -> tuple[bool, str]: | |
| """Returns (True, "") if extension is in ACCEPTED_AUDIO_FORMATS; (False, error_msg) otherwise.""" | |
| if not filename or "." not in filename: | |
| return False, "Unsupported file format. Please upload a WAV, MP3, M4A, OGG, or AAC file." | |
| extension = filename.rsplit(".", 1)[-1].lower() | |
| if extension in ACCEPTED_AUDIO_FORMATS: | |
| return True, "" | |
| return False, "Unsupported file format. Please upload a WAV, MP3, M4A, OGG, or AAC file." | |
| def validate_audio_duration(duration_seconds: float) -> tuple[bool, str]: | |
| """Returns (True, "") if duration <= 300s; (False, error_msg) otherwise.""" | |
| if duration_seconds <= MAX_AUDIO_DURATION_SECONDS: | |
| return True, "" | |
| return False, "Audio file exceeds the 5-minute limit. Please upload a shorter file." | |
| def validate_language_selection(language: str | None) -> tuple[bool, str]: | |
| """Returns (True, "") if language is in SUPPORTED_LANGUAGES; (False, error_msg) otherwise.""" | |
| if language in SUPPORTED_LANGUAGES: | |
| return True, "" | |
| return False, "Please select a target language before submitting." | |
| def get_audio_duration(audio_bytes: bytes, filename: str) -> float: | |
| """ | |
| Returns the duration of the audio in seconds using mutagen. | |
| Wraps the raw bytes in a BytesIO object so mutagen can read them | |
| without writing to disk. | |
| Raises: | |
| ValueError: If mutagen cannot determine the duration (e.g. unsupported | |
| format or corrupt file). | |
| """ | |
| buffer = io.BytesIO(audio_bytes) | |
| # mutagen.File accepts a file-like object; pass the filename hint so | |
| # mutagen can pick the right parser when the format is ambiguous. | |
| audio = mutagen.File(buffer, filename=filename) | |
| if audio is None: | |
| raise ValueError( | |
| f"Could not determine audio duration for '{filename}': " | |
| "mutagen was unable to parse the file. " | |
| "Ensure the file is a valid audio file in a supported format." | |
| ) | |
| duration = getattr(audio.info, "length", None) | |
| if duration is None: | |
| raise ValueError( | |
| f"Could not determine audio duration for '{filename}': " | |
| "mutagen parsed the file but did not report a duration." | |
| ) | |
| return float(duration) | |