import soundfile as sf
import tempfile
import os
from .ifileinfo import IFileInfo
[docs]class FileInfo(IFileInfo):
"""
Defines the class to manipulate soundfiles.
Receives a path to a file.
Copies the file to a temporary location.
Gets edited through the FileUtil.
FileWriter converts and writes to another location.
NOTE:
- close method MUST always be called or else the temporary file stays in disk.
- ``with`` statements should be used in order to close the files automatically.
"""
def __init__(self,path):
super().__init__(path)
self._path = path #ACTUAL FILE
self._tmpPath = tempfile.gettempdir() #TEMPORARY COPY
try:
import shutil
self._tmpPath = shutil.copy(self._path,self._tmpPath)
except FileExistsError:
print("FEXISTS l28")
pass
self._initinfo()
def __enter__(self):
return self
def __exit__(self,type,value,traceback):
self.close()
def _initinfo(self):
with sf.SoundFile(self._tmpPath, mode ='r') as rfile:
self._samplerate = rfile.samplerate
self._frames = rfile.read()
self._numchannels = rfile.channels
return
#================READ METHODS ==================#
[docs] def getFilepath(self):
"""
See the base class ``ifileinfo``.
"""
return self._path
[docs] def getNumberOfFrames(self):
"""
See the base class ``ifileinfo``.
"""
return len(self.getSamples())
[docs] def getSamplerate(self):
"""
See the base class ``ifileinfo``.
"""
return self._samplerate
[docs] def getNumberOfChannels(self):
"""
Returns:
Number of channels the file has.
"""
return self._numchannels
[docs] def getDuration(self):
"""
Returns:
The duration of the file in seconds.
"""
return self.getNumberOfFrames()/self.getSamplerate()
[docs] def get_rounded_duration(self):
"""
Returns:
The rounded duration in seconds.
"""
from math import ceil
rounded_duration = ceil(self.getDuration()) #in seconds
return rounded_duration
[docs] def getSamples(self):
"""
Reads all of the samples in the file
Returns:
numpy array containing the samples.
"""
return self._frames
#================WRITE METHODS ==================#
[docs] def setSamples(self, samples, samplerate = None):
"""
Writes the ´samples´ as the new samples in the file.
Args:
samples: numpy array shaped like (n,c), where c is the number of channels.
samplerate: the new sample rate the file will have, if none it will use the initial samplerate.
"""
if (samplerate == None):
samplerate = self.getSamplerate()
samples = self._soundfile_array_converter(samples)
with sf.SoundFile(self._tmpPath, mode = 'w', samplerate = self.getSamplerate(), channels = self.getNumberOfChannels()) as wfile:
wfile.truncate(0) #removes all the samples in the file
sf.write(self._tmpPath,samples,samplerate)
self._initinfo()
[docs] def truncate(self,num_frames):
"""
Truncates the file to only have ``num_frames``.
"""
#with sf.SoundFile(self._tmpPath, mode = 'w', samplerate = self.getSamplerate(), channels = self.getNumChannels()) as wfile:
# wfile.truncate(num_frames)
self.setSamples(self.getSamples()[:num_frames])
self._initinfo()
[docs] def addSamples(self,samples):
"""
Appends the samples to the file.
Similar to ´setSamples()´ but appends instead of truncating.
See the base class ``ifileinfo``.
"""
samples = self._soundfile_array_converter(samples)
with sf.SoundFile(self._tmpPath, mode = 'r+') as wfile:
wfile.seek(0,whence = sf.SEEK_END)
wfile.write(samples)
#================MISC METHODS ==================#
def _soundfile_array_converter(self,array):
"""
Checks if it has the correct array shape to be worked by pysoundfile.
If not, it will transpose de array.
Arguments:
array: ndarray to convert.
Returns:
array: the converted array.
"""
if(array.shape[1] > 20): #meaning it doesnt represent the number of channelsnumber of channels
array = array.T
return array
[docs] def close(self):
"""
Must always be called or the file won't be accessible by other processes AND the temp file will stay in disk.
See the base class ``ifileinfo``.
"""
import os
try:
os.unlink(self._tmpPath)
except expression as identifier:
print("err", identifier)