"""Utils for checking and correcting wavforms.""" import mimetypes import os # basic os operations like paths from io import BytesIO import boto3 import soundfile as sf from common.audio_exceptions import AudioFileUploadFailure from common.audio_exceptions import AudioValidationFailure from config import get_current_logger from constants.audio import GOOD_SUBTYPES from constants.audio import PCM_24_SUBTYPE from constants.audio import VALID_EXTENSIONS from constants.audio import WAVE_PCM_FORMAT def validate_file_key(bucket, key): """Validate that a key being passed is an acceptable type.""" key_path = bucket + '/' + key file_root, file_ext = os.path.splitext(key) if file_root[0] == '.': msg = '{}: Hidden file. Skipping'.format(key_path) raise AudioValidationFailure(msg) if file_ext[1:].lower() not in VALID_EXTENSIONS: msg = '{}: Not a WAV/FLAC file.'.format(key_path) raise AudioValidationFailure(msg) def get_audio_stream_from_s3(bucket, key): """Open an s3 location as a file stream.""" s3_client = boto3.client('s3') file_byte_string = s3_client.get_object( Bucket=bucket, Key=key)['Body'].read() wav_bytes_file = BytesIO(file_byte_string) return wav_bytes_file def process_audio(wav_file, audio_format=None, subtype=None, channels=None, samplerate=None): """Convert a wav stream from S3 to the defined target type. Args: wav_file: (SoundFile) The SoundFile object containing the WAV stream. audio_format: (str) An audio format into which to transcode. subtype: (str) An audio subtype into which to transcode. channels: (int) The num of channels into which to transcode. samplerate: (int) The samplerate into which to transcode. """ out_file = BytesIO() if audio_format is None: audio_format = wav_file.format if subtype is None: subtype = wav_file.subtype if channels is None: channels = wav_file.channels if samplerate is None: samplerate = wav_file.samplerate sf.write( out_file, wav_file.read(), samplerate=samplerate, endian=wav_file.endian, format=audio_format, subtype=subtype ) wav_file.close() out_file.seek(0) return out_file def save_audio_to_s3(wav, target_bucket, target_key, bucket, key, correlation_id): """Save a wav stream object to an S3 target.""" logger = get_current_logger(correlation_id) file_name = os.path.split(key)[1] # Ensure metadata is retained metadata = {} s3_client = boto3.client('s3') response = s3_client.head_object(Bucket=bucket, Key=key) if response.get('Metadata'): metadata = response['Metadata'] if isinstance(wav, BytesIO): logger.info( f'Putting transcoded file stream to {target_bucket}/{target_key}') sent_data = s3_client.put_object( Body=wav.read(), Key=target_key, Bucket=target_bucket, ContentType=mimetypes.guess_type(file_name)[0], Metadata=metadata) else: logger.info(f'Copying original file to {target_bucket}/{key}') sent_data = s3_client.copy_object( Key=target_key, Bucket=target_bucket, CopySource={'Bucket': bucket, 'Key': key}, Metadata=metadata, MetadataDirective='REPLACE', TaggingDirective='REPLACE', ContentType=mimetypes.guess_type(file_name)[0] ) if sent_data['ResponseMetadata']['HTTPStatusCode'] != 200: print('Upload failure!') raise AudioFileUploadFailure( 'Failed to upload wav file {} to bucket {}'.format(key, bucket))