def _add_meta( self, file: str, version: str, archive: str, checksum: str, ): r"""Add or update table file. Args: file: relative file path archive: archive name without extension checksum: checksum of file version: version string """ format = audeer.file_extension(file).lower() self._df.loc[file] = [ archive, # archive 0, # bit_depth 0, # channels checksum, # checksum 0.0, # duration format, # format 0, # removed 0, # sampling_rate define.DependType.META, # type version, # version ]
def _add_media( self, root: str, file: str, version: str, archive: str = None, checksum: str = None, ): r"""Add or update media file. If you want to update only the version of an unaltered media file, don't specify ``archive`` and ``checksum``. Args: root: root directory file: relative file path archive: archive name without extension checksum: checksum of file version: version string """ format = audeer.file_extension(file).lower() if archive is None: archive = self.archive(file) if checksum is None: checksum = self.checksum(file) bit_depth = self.bit_depth(file) channels = self.channels(file) duration = self.duration(file) sampling_rate = self.sampling_rate(file) else: bit_depth = channels = sampling_rate = 0 duration = 0.0 if format in define.FORMATS: path = os.path.join(root, file) bit_depth = audiofile.bit_depth(path) channels = audiofile.channels(path) duration = audiofile.duration(path) sampling_rate = audiofile.sampling_rate(path) self._df.loc[file] = [ archive, bit_depth, channels, checksum, duration, format, 0, # removed sampling_rate, define.DependType.MEDIA, version, ]
def test_empty_file(empty_file): # Reading file signal, sampling_rate = af.read(empty_file) assert len(signal) == 0 # Metadata for sloppy in [True, False]: assert af.duration(empty_file, sloppy=sloppy) == 0.0 assert af.channels(empty_file) == 1 assert af.sampling_rate(empty_file) == sampling_rate assert af.samples(empty_file) == 0 if audeer.file_extension(empty_file) == 'wav': assert af.bit_depth(empty_file) == 16 else: assert af.bit_depth(empty_file) is None
def load(self, path: str): r"""Read dependencies from file. Clears existing dependencies. Args: path: path to file. File extension can be ``csv`` or ``pkl``. Raises: ValueError: if file extension is not ``csv`` or ``pkl`` FileNotFoundError: if ``path`` does not exists """ self._df = pd.DataFrame(columns=define.DEPEND_FIELD_NAMES.values()) path = audeer.safe_path(path) extension = audeer.file_extension(path) if extension not in ['csv', 'pkl']: raise ValueError( f"File extension of 'path' has to be 'csv' or 'pkl' " f"not '{extension}'") if not os.path.exists(path): raise FileNotFoundError( errno.ENOENT, os.strerror(errno.ENOENT), path, ) if extension == 'pkl': self._df = pd.read_pickle(path) elif extension == 'csv': # Data type of dependency columns dtype_mapping = { name: dtype for name, dtype in zip( define.DEPEND_FIELD_NAMES.values(), define.DEPEND_FIELD_DTYPES.values(), ) } # Data type of index index = 0 dtype_mapping[index] = str self._df = pd.read_csv( path, index_col=index, na_filter=False, dtype=dtype_mapping, )
def test_mp3(tmpdir, magnitude, sampling_rate, channels): # Currently we are not able to setup the Windows runner with MP3 support # https://github.com/audeering/audiofile/issues/51 if sys.platform == 'win32': return signal = sine(magnitude=magnitude, sampling_rate=sampling_rate, channels=channels) # Create wav file and use sox to convert to mp3 wav_file = str(tmpdir.join('signal.wav')) mp3_file = str(tmpdir.join('signal.mp3')) af.write(wav_file, signal, sampling_rate) subprocess.call(['sox', wav_file, mp3_file]) assert audeer.file_extension(mp3_file) == 'mp3' sig, fs = af.read(mp3_file) assert_allclose(_magnitude(sig), magnitude, rtol=0, atol=tolerance(16)) assert fs == sampling_rate assert _channels(sig) == channels if channels == 1: assert sig.ndim == 1 else: assert sig.ndim == 2 assert af.channels(mp3_file) == _channels(sig) assert af.sampling_rate(mp3_file) == sampling_rate assert af.samples(mp3_file) == _samples(sig) assert af.duration(mp3_file) == _duration(sig, sampling_rate) assert af.duration(mp3_file, sloppy=True) == sox.file_info.duration(mp3_file) assert af.bit_depth(mp3_file) is None # Test additional arguments to read with sox offset = 0.1 duration = 0.5 sig, fs = af.read(mp3_file, offset=offset, duration=duration) assert _duration(sig, sampling_rate) == duration sig, fs = af.read(mp3_file, offset=offset) # Don't test for 48000 Hz and 2 channels # https://github.com/audeering/audiofile/issues/23 if not (sampling_rate == 48000 and channels == 2): assert_allclose( _duration(sig, sampling_rate), af.duration(mp3_file) - offset, rtol=0, atol=tolerance('duration', sampling_rate), )
def _check_convert( self, file: str, bit_depth: typing.Optional[int], channels: typing.Optional[int], sampling_rate: typing.Optional[int], ) -> bool: r"""Check if file needs to be converted to flavor.""" format = audeer.file_extension(file).lower() # format change if self.format is not None: if self.format != format: return True convert = False # precision change if not convert and self.bit_depth is not None: bit_depth = bit_depth or audiofile.bit_depth(file) if self.bit_depth != bit_depth: convert = True # mixdown and channel selection if not convert and self.mixdown or self.channels is not None: channels = channels or audiofile.channels(file) if self.mixdown and channels != 1: convert = True elif list(range(channels)) != self.channels: convert = True # sampling rate change if not convert and self.sampling_rate is not None: sampling_rate = sampling_rate or audiofile.sampling_rate(file) if self.sampling_rate != sampling_rate: convert = True if convert and format not in define.FORMATS: raise RuntimeError( f"You have to specify the 'format' argument " f"to convert '{file}' " f"to the specified flavor " f"as we cannot write to {format.upper()} files." ) return convert
def destination( self, file: str, ) -> str: r"""Return converted file path. The file path will only change if the file is converted to a different format. Args: file: path to input file Returns: path to output file """ if self.format is not None: format = audeer.file_extension(file).lower() if format != self.format: file = f'{file[:-len(format)]}{self.format}' return file
def test_broken_file(non_audio_file): # Only match the beginning of error message # as the default soundfile message differs at the end on macOS error_msg = 'Error opening' # Reading file with pytest.raises(RuntimeError, match=error_msg): af.read(non_audio_file) # Metadata if audeer.file_extension(non_audio_file) == 'wav': with pytest.raises(RuntimeError, match=error_msg): af.bit_depth(non_audio_file) else: assert af.bit_depth(non_audio_file) is None with pytest.raises(RuntimeError, match=error_msg): af.channels(non_audio_file) with pytest.raises(RuntimeError, match=error_msg): af.duration(non_audio_file) with pytest.raises(RuntimeError, match=error_msg): af.samples(non_audio_file) with pytest.raises(RuntimeError, match=error_msg): af.sampling_rate(non_audio_file)
def test_file_type(tmpdir, file_type, magnitude, sampling_rate, channels): use_sox = True file = str(tmpdir.join('signal.' + file_type)) signal = sine(magnitude=magnitude, sampling_rate=sampling_rate, channels=channels) # Skip unallowed combination if file_type == 'flac' and channels > 8: return 0 # Windows runners sox does not support flac if sys.platform == 'win32' and file_type in ['flac', 'ogg']: use_sox = False # Allowed combinations bit_depth = 16 sig, fs = write_and_read(file, signal, sampling_rate, bit_depth=bit_depth) # Test file type assert audeer.file_extension(file) == file_type # Test magnitude assert_allclose(_magnitude(sig), magnitude, rtol=0, atol=tolerance(16)) # Test sampling rate assert fs == sampling_rate if use_sox: assert sox.file_info.sample_rate(file) == sampling_rate # Test channels assert _channels(sig) == channels if use_sox: assert sox.file_info.channels(file) == channels # Test samples assert _samples(sig) == _samples(signal) if use_sox: assert sox.file_info.num_samples(file) == _samples(signal) # Test bit depth if use_sox: bit_depth = sox.file_info.bitdepth(file) elif file_type == 'ogg': bit_depth = None assert af.bit_depth(file) == bit_depth
def file_extension(path): """Lower case file extension.""" return audeer.file_extension(path).lower()
def __call__( self, src_path: str, dst_path: str, *, src_bit_depth: int = None, src_channels: int = None, src_sampling_rate: int = None, ): r"""Convert file to flavor. If ``bit_depth``, ``channels`` or ``sampling_rate`` of source signal are known, they can be provided. Otherwise, they will be computed using :mod:`audiofile`. Args: src_path: path to input file dst_path: path to output file src_bit_depth: bit depth src_channels: number of channels src_sampling_rate: sampling rate in Hz Raises: ValueError: if extension of output file does not match the format of the flavor RuntimeError: if a conversion is requested, but no output format is specified, and the input format is not WAV or FLAC """ src_path = audeer.safe_path(src_path) dst_path = audeer.safe_path(dst_path) # verify that extension matches the output format src_ext = audeer.file_extension(src_path).lower() dst_ext = audeer.file_extension(dst_path).lower() expected_ext = self.format or src_ext if expected_ext != dst_ext: raise ValueError( f"Extension of output file is '{dst_ext}', " f"but should be '{expected_ext}' " "to match the format of the converted file." ) if not self._check_convert( src_path, src_bit_depth, src_channels, src_sampling_rate ): # file already satisfies flavor if src_path != dst_path: shutil.copy(src_path, dst_path) else: # convert file to flavor signal, sampling_rate = audiofile.read(src_path, always_2d=True) signal = self._remix(signal) signal, sampling_rate = self._resample(signal, sampling_rate) bit_depth = ( self.bit_depth or src_bit_depth or audiofile.bit_depth(src_path) or 16 ) audiofile.write( dst_path, signal, sampling_rate, bit_depth=bit_depth, )
def test_file_extension(path, extension): ext = audeer.file_extension(path) assert ext == extension assert type(ext) is str