Orchestrating video transcoding in Ruby: A story
Michał Matyas
@nerdblogpl
https://nerdblog.pl
You can watch the slides live on your phone/laptop
https://tiny.cc/transcoding
I will avoid talking about the business side of the project.
Please don't ask, thank you!
Introduction
There was once a project...
Lesson #1
Hindsight 20/20
It's easy to look back and think "this should've been obvious" but you usually lack all the knowledge and experience at the time.
class Video < ActiveRecord::Base
mount_uploader VideoUploader
process_in_background UploadWorker
before_save
before_processing
Video.status = processing
carrierwave-video (ffmpeg)
after_processing_success
after_processing_failure
Video.status = ready
Video.status = failed
CALLBACK ALL THINGS!!!
(please don't)
Lesson #2
Avoid callbacks
Callbacks make your code harder to reason and harder to isolate, slowly turning everything into a tightly coupled co-dependent mess.
Carrierwave versions
include CarrierWave::Video
include ::CarrierWave::Backgrounder::Delay
storage :file
version :mp4 do
process :encode_video => [:mp4]
end
version :webm do
process :encode_video => [:webm]
end
Carrierwave versions
support_format :mp4_1080p, {
resolution: '1920x1080',
progress: :on_progress_1080p,
if: :allow_1080p?
}
support_format :mp4_720p, {
resolution: '1920x1080',
progress: :on_progress_720p
}
support_format :mp4_480p, {
resolution: '852x480',
custom: '-preset ultrafast -g 5',
progress: :on_progress_480p
}
Lesson #3
Don't use Carrierwave versions
Or at least don't use them for anything more complex. They are good for other things (probably).
Consider using Shrine instead.
Video
kind: "720p"
Version
kind: "480p"
Version
kind: nil
Version
kind: "720p"
special: true
Version
kind: "480p"
special: true
Version
kind: nil
special: true
Version
class Video < ActiveRecord::Base
mount_uploader VideoUploader
process_in_background UploadWorker
before_save
before_processing
Video.status = processing
carrierwave-video (ffmpeg)
after_processing_success
after_processing_failure
Video.status = ready
Video.status = failed
class Version < ActiveRecord::Base
mount_uploader VersionUploader
process_in_background VersionUploadWorker
before_save
before_processing
Version.status = processing
carrierwave-video (ffmpeg)
after_processing_success
after_processing_failure
Version.status = ready
Version.status = failed
class Version < ActiveRecord::Base
mount_uploader VersionUploader
process_in_background VersionUploadWorker
before_save
before_processing
Version.status = processing
carrierwave-video (ffmpeg)
after_processing_success
after_processing_failure
Version.status = ready
Version.status = failed
class Version < ActiveRecord::Base
mount_uploader VersionUploader
process_in_background VersionUploadWorker
before_save
before_processing
Version.status = processing
carrierwave-video (ffmpeg)
after_processing_success
after_processing_failure
Version.status = ready
Version.status = failed
class Video < ActiveRecord::Base
Video.status = processing
Video.status = ready
Video
original version gets uploaded
ProcessingWorker
checks for existing versions
store in local cache
creates missing ones
starts processing based on the priority
(low quality video - higher priority because it's gonna be ready fastest)
uploads to S3
(in a separate worker once the version is ready)
processing uses streamio-ffmpeg directly to pass custom arguments to ffmpeg based on the video format and allows transcoding from other versions rather than original file (much much faster)
Lesson #4
Simplest solutions are often best solutions
We would have avoided a lot of pain if we didn't try to do things The Rails Way™ but went with the simplest solution instead.
You live and learn!
But why didn't you use...
- AWS Lambda - 15 minute of max execution time
- Zencoder, Amazon Elastic Transcoder etc. - expensive
- Docker - not that popular (and well supported in production) at the time
Let's talk about MP4
- FTYP - File identification
- MDAT - media data (streams)
- MOOV - metadata
Tip #1
Copy streams!
When transcoding things, the best approach is to copy streams and match codecs as much as possible because it's way way faster.
MOOV IS MADE OF ATOMS
- Fixed parameters of the file
- Specific pointers for each frame of data and audio
MOOV IS MADE OF ATOMS
- TRKH - file times, duration, speed etc.
- MDIA - Media Data
- STCO - start of each video/audio chunk
- STSC - Sample to chunk atom
- STSZ - Sample size atom
Tip #2
Optimize for streaming
ffmpeg has arguments that make video better at streaming.
The most important:
-movflags +faststart
Tip #3
Bring Your Own Arguments
When using streamio-ffmpeg just ignore all the DSL they have and go for direct passing of as many arguments as you can.
Tip #4
There are no universal solutions
When transcoding videos from network, sometimes it's faster to download-then-process, sometimes to process on the fly. It heavily depends on the codec though.
Tip #5
Use presets
ffmpeg already has presets for most common configuration options but it's a balance between speed and quality. Use fast presets for low resolution videos.
Tip #6
Use profiles
H.264 has profiles (sets of codec features) but not every device will support all of them.
Go with the highest one you can afford.
Tip #7
Always convert to YUV420
There are different pixel formats that define how color information is stored. Use -pix_fmt yuv420 for max compatibility in browsers.
Tip #8
Collect metadata
It is always a good idea to collect metadata from ffprobe for both the incoming video and processed versions to look for clues when things break
Tip #9
Trust but verify
Sometimes file can be cut during transcoding and still technically be "valid", even if incomplete.
BUT...
Tip #10
Trust but verify manually
Be super-careful about invalidating the video or audio based on the duration given from ffprobe. It's sometimes approximated without a warning.
That's it, folks!
@nerdblogpl
https://nerdblog.pl
sources:
- https://trac.ffmpeg.org/wiki/Encode/H.264
- https://www.dfrws.org/sites/default/files/session-files/pres-video_authentication_using_file_structure_and_metadata.pdf
- http://xhelmboyx.tripod.com/formats/mp4-layout.txt
- https://www.cnwrecovery.com/manual/MP4FileStructure.html
Transcoding videos in Ruby: A story
By Michał Matyas
Transcoding videos in Ruby: A story
- 2,620