"""This module contain logic for transcoding of audio files for S3.""" import os import subprocess import ffmpy from transcoding import config from transcoding.connectors import logger from transcoding.constants import exceptions as transcoding_exceptions from transcoding.constants import transcoding from transcoding.logic import storage from transcoding.utils import file_utils from transcoding.utils import message_extractor def _generate_output_path(tmp_folder, filename): """Generate output path for a transcoded asset file. Args: tmp_folder (str): Temporary folder path. filename (str): Filename with extension. Returns: str: Generated output path. """ return transcoding.OUTPUT_FILENAME_PATTERN.format( output_folder=tmp_folder, filename=filename ) def get_ffmpeg_output_config(transcoding_job_details): """Get ffmpeg command line configuration for output file. Args: transcoding_job_details (dict): input transcoding job configuration. Returns: str: ffmpeg CLI output configuration. """ current_app_logger = logger.get_current_logger() primary_config_key = ( transcoding_job_details['codec'], transcoding_job_details['bit_depth'] ) primary_config = transcoding.CODEC_WITH_FREQUENCY_MAPPING[primary_config_key] current_app_logger.info( f'generating params for ffmpeg to create {primary_config_key}' ) current_app_logger.info( f'generating strip nonaudio streams of asset type {primary_config_key}' ) secondary_config = transcoding.FFMPEG_SECONDARY_OUTPUT_CONFIG_STRIP_NONAUDIO secondary_config_output = ( secondary_config.format( sample_rate=transcoding_job_details['sample_rate'], channels=transcoding_job_details['channels'], bit_rate=transcoding_job_details['bit_rate'] ) ) current_app_logger.info(f'the interpolated params are: {secondary_config_output}') return ' '.join([primary_config, secondary_config_output]) def transcode_asset(message, worker_id): """Transcode input audio file by transcoding job configuration. Args: message (sqs.Message): message pulled from SQS-queue. worker_id (int): Worker id. Returns: str: path to output files in local disk. Raises: ffmpy.FFRuntimeError or ffmpy.FFExecutableNotFoundError: Any error which occur during ffmpeg transcoding. """ transcoding_job = message_extractor.get_message_body(message) output_config = get_ffmpeg_output_config(transcoding_job) tmp_folder = os.path.join( config.WORKER_TMP_DIRECTORY_PATH.format(worker_id=worker_id)) output_path = _generate_output_path( tmp_folder, transcoding_job['output_key']) input_url = storage.generate_url_for_uploaded_asset( bucket=transcoding_job['input_bucket'], key=transcoding_job['input_key']) file_utils.clean_directory(tmp_folder) try: ff = ffmpy.FFmpeg( executable=config.FFMPEG_PATH, inputs={input_url: config.FFMPEG_INPUT_OPTIONS}, outputs={output_path: output_config} ) ff.run( stdout=subprocess.PIPE, stderr=subprocess.PIPE) return output_path except ffmpy.FFRuntimeError as e: raise transcoding_exceptions.TranscodingRetryableError(str(e)) except ffmpy.FFExecutableNotFoundError as e: raise transcoding_exceptions.TranscodingFatalError(str(e))