Convert mp3 files to a streaming-ready format
with Ruby SDK for AWS MediaConvert
Search for a command to run...
with Ruby SDK for AWS MediaConvert
No comments yet. Be the first to comment.
In this series I discuss the issues that I encountered while working on audiobook streaming platform.
Yesterday I wrote a post about initializing AWS Elemental MediaConvert job. It's a common case that you initiate a task and have to wait some unspecified time for the result. All what you have is an external system job id. In such cases, I use a ve...
If you have been working on a multi-tenant application for a long time, there is a high chance that your Tenant model has grown to an enormous size. You probably started with id, name, and subdomain columns. Later there were things that had to be con...
Today, I came across the following method in my old code. (This comes from an application supporting calculations in the construction of concrete floors). def l_factor ((bending_stiffness * 10000000000) / k) ** 0.25 end This big number looks ...
Last weekend I went to the dynamIT conference that took place in Kraków. In his presentation, Andrzej Krzywda was speaking about DDD as Low-Code. He distilled 11 bounded contexts that are common in the different business applications. One of them is ...
Today's post is a continuation of what I wrote yesterday about securing Cloudfront stream with signed cookie. I encourage you to open this post especially to look at the UML diagram. There are some things you need to keep in mind to access a stream ...
In the context of audiobook streaming platform
In my yesterday's post, I described why I decided to take advantage of the HLS streaming in my new project which is a platform for audiobook streaming.
I explained why a file conversion is required. The simplest way, I discovered, to get this done, is using the AWS Elemental MediaConvert service. It was an obvious choice for me as I am a bit familiar with the AWS stack.
Sure! There will be a lot of code this time as kicking off a convert job requires passing extensive configuration hash. My implementation wraps the official MediaConvert SDK for Ruby. You can get it having aws-sdk-mediaconvert listed in your Gemfile.
class Converting::MediaConverterWrapper
protected
def client
Aws::MediaConvert::Client.new(
region: Rails.configuration.aws[:region],
access_key_id: Rails.application.credentials.aws[:access_key_id],
secret_access_key: Rails.application.credentials.aws[:secret_access_key],
endpoint: Rails.configuration.aws[:endpoint]
)
end
end
class Converting::MediaConverterJobInitializer < Converting::MediaConverterWrapper
def initialize source_s3_key
@source_s3_key = source_s3_key
end
def call
source_S3_bucket = Rails.configuration.aws[:source_S3_bucket]
source_S3 = "s3://#{source_S3_bucket}/#{@source_s3_key}"
destination_S3_bucket = "s3://#{Rails.configuration.aws[:destination_S3_bucket]}/"
queue = Rails.configuration.aws[:queue]
role = Rails.configuration.aws[:role]
# Kicking off a job
@response = client.create_job(create_job_request_body(source_S3, queue, role, destination_S3_bucket))
nil
end
def job_id
@response[:job][:id]
end
private
def create_job_request_body source_S3, queue, role, destination_S3
{
:queue => queue,
:user_metadata => {},
role: role,
:settings => {
:timecode_config => {
:source => 'ZEROBASED'
},
:output_groups => [
{
:name => 'Apple HLS',
:outputs => [
{
:container_settings => {
:container => 'M3U8',
:m3u_8_settings => {
:audio_frames_per_pes => 4,
:pcr_control => 'PCR_EVERY_PES_PACKET',
:pmt_pid => 480,
:private_metadata_pid => 503,
:program_number => 1,
:pat_interval => 0,
:pmt_interval => 0,
:scte_35_source => 'NONE',
:nielsen_id_3 => 'NONE',
:timed_metadata => 'NONE',
:video_pid => 481,
:audio_pids => [482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492],
:audio_duration => 'DEFAULT_CODEC_DURATION' }
},
:audio_descriptions => [
{
:audio_type_control => 'FOLLOW_INPUT',
:audio_source_name => 'Audio Selector 1',
:codec_settings => {
:codec => 'AAC',
:aac_settings => {
:audio_description_broadcaster_mix => 'NORMAL',
:bitrate => 96_000,
:rate_control_mode => 'CBR',
:codec_profile => 'LC',
:coding_mode => 'CODING_MODE_2_0',
:raw_format => 'NONE',
:sample_rate => 48_000,
:specification => 'MPEG4'
}
},
:language_code_control => 'FOLLOW_INPUT'
}
],
:output_settings => {
:hls_settings => {
:audio_group_id => 'program_audio',
:audio_only_container => 'AUTOMATIC',
:i_frame_only_manifest => 'EXCLUDE'
}
},
:name_modifier => '_converted'
}
],
:output_group_settings => {
:type => 'HLS_GROUP_SETTINGS',
:hls_group_settings => {
:manifest_duration_format => 'INTEGER',
:segment_length => 10,
:timed_metadata_id_3_period => 10,
:caption_language_setting => 'OMIT',
:destination => destination_S3,
:timed_metadata_id_3_frame => 'PRIV',
:codec_specification => 'RFC_4281',
:output_selection => 'MANIFESTS_AND_SEGMENTS',
:program_date_time_period => 600,
:min_segment_length => 0,
:min_final_segment_length => 0,
:directory_structure => 'SINGLE_DIRECTORY',
:program_date_time => 'EXCLUDE',
:segment_control => 'SEGMENTED_FILES',
:manifest_compression => 'NONE',
:client_cache => 'ENABLED',
:audio_only_header => 'INCLUDE',
:stream_inf_resolution => 'INCLUDE'
}
}
}
],
:ad_avail_offset => 0,
:inputs => [
{
:audio_selectors => {
'Audio Selector 1' => {
:offset => 0,
:default_selection => 'DEFAULT',
:program_selection => 1
}
},
:filter_enable => 'AUTO',
:psi_control => 'USE_PSI',
:filter_strength => 0,
:deblock_filter => 'DISABLED',
:denoise_filter => 'DISABLED',
:input_scan_type => 'AUTO',
:timecode_source => 'ZEROBASED',
:file_input => source_S3
}
]
},
:acceleration_settings => { :mode => 'DISABLED' },
:status_update_interval => 'SECONDS_60',
:priority => 0
}
end
end
Take note of several options for this advanced configuration:
queue passing this should be optional as there is such a thing as a "default queue"role identifier of AWS IAM role with permissions to access both: source and destination, S3 bucketsbitrate specifies output .aac files bitrate. You can specify multiple AudioDescriptions with different bitrates to have an adaptive streamsegment_length duration in seconds of .aac segment filesdestination identifier of S3 bucket for storing converted filesname_modifier specifies output file name addonfile_input S3 identifier of source fileRemember to save obtained job_id, to be able to check the status of initialized convert job later.
Of course, I also have a service class for this purpose.
class Converting::MediaConverterJobInspector < Converting::MediaConverterWrapper
def initialize media_convert_job_id
@media_convert_job_id = media_convert_job_id
end
def call
@response = client.get_job({ id: @media_convert_job_id })
nil
end
# returns job status
def status
@response[:job][:status]
end
# ...
end