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

40 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-10 20:16 -0700

1from __future__ import annotations 

2 

3from io import BytesIO 

4from pathlib import Path 

5from tempfile import NamedTemporaryFile 

6from typing import NamedTuple, override 

7 

8import numpy 

9import soundfile 

10from moviepy.audio.AudioClip import AudioClip 

11from moviepy.audio.io.AudioFileClip import AudioFileClip 

12 

13from . import BytesBlob 

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__(self, blob: bytes | AudioClip | AudioData) -> None: 

25 if isinstance(blob, AudioClip): 

26 if ( 

27 isinstance(blob, AudioFileClip) 

28 # Need to check whether `filename` is `str`` as it can also be sound array 

29 and isinstance(blob.filename, str) 

30 and blob.filename.endswith(".mp3") 

31 ): 

32 blob = Path(blob.filename).read_bytes() 

33 else: 

34 with NamedTemporaryFile(suffix=".mp3", delete_on_close=False) as f: 

35 blob.write_audiofile(f.name) 

36 blob.close() 

37 

38 f.close() 

39 

40 blob = Path(f.name).read_bytes() 

41 elif isinstance(blob, AudioData): 

42 bio = BytesIO() 

43 bio.name = AudioBlob.__IN_MEMORY_FILE_NAME 

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

45 blob = bio.getvalue() 

46 

47 super().__init__(blob) 

48 

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

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

51 

52 return AudioFileClip(filename) 

53 

54 def as_audio_data(self) -> AudioData: 

55 bio = BytesIO(self._blob_bytes) 

56 bio.name = AudioBlob.__IN_MEMORY_FILE_NAME 

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

58 

59 @override 

60 def __repr__(self) -> str: 

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