Coverage for src/blob_dict/blob/audio.py: 0%

51 statements  

« prev     ^ index     » next       coverage.py v7.8.1, created at 2025-05-31 14:35 -0700

1from __future__ import annotations 

2 

3from io import BytesIO 

4from pathlib import Path 

5from typing import NamedTuple, Self, override 

6 

7import numpy 

8import soundfile 

9from moviepy.audio.AudioClip import AudioClip 

10from moviepy.audio.io.AudioFileClip import AudioFileClip 

11 

12from . import BytesBlob 

13from .audio_video import read_from_clip 

14 

15 

16class AudioData(NamedTuple): 

17 data: numpy.ndarray 

18 sample_rate: int 

19 

20 

21class AudioBlob(BytesBlob): 

22 __IN_MEMORY_FILE_NAME: str = "file.mp3" 

23 

24 def __init__( 

25 self, 

26 blob: bytes | AudioClip | AudioData, 

27 ) -> None: 

28 if isinstance(blob, AudioClip): 

29 blob = read_from_clip( 

30 blob, 

31 ".mp3", 

32 delete_temp_clip_file=delete_temp_clip_file, 

33 ) 

34 elif isinstance(blob, AudioData): 

35 bio = BytesIO() 

36 bio.name = AudioBlob.__IN_MEMORY_FILE_NAME 

37 soundfile.write(bio, AudioData.data, AudioData.sample_rate) 

38 blob = bio.getvalue() 

39 

40 super().__init__(blob) 

41 

42 def as_audio(self, filename: str) -> AudioFileClip: 

43 Path(filename).write_bytes(self._blob_bytes) 

44 

45 return AudioFileClip(filename) 

46 

47 def as_audio_data(self) -> AudioData: 

48 bio = BytesIO(self._blob_bytes) 

49 bio.name = AudioBlob.__IN_MEMORY_FILE_NAME 

50 return AudioData(*soundfile.read(bio)) 

51 

52 @override 

53 def __repr__(self) -> str: 

54 return f"{self.__class__.__name__}(...)" 

55 

56 @classmethod 

57 @override 

58 def load(cls: type[Self], f: Path | str) -> Self: 

59 f = Path(f).expanduser() 

60 

61 if f.suffix.lower() == ".mp3": 

62 return cls(f.read_bytes()) 

63 

64 clip = AudioFileClip(str(f)) 

65 blob = cls(clip) 

66 clip.close() 

67 

68 return blob 

69 

70 @override 

71 def dump(self, f: Path | str) -> None: 

72 f = Path(f).expanduser() 

73 if f.suffix.lower() != ".mp3": 

74 msg = "Only MP3 file is supported." 

75 raise ValueError(msg) 

76 

77 f.write_bytes(self.as_bytes())