# XMorph Morphs things from one kind to another. Transcodes, in local speak, for example.. # Features! - XMorph can get validate an asset against given set of validations (set of video and audio parameters). - Can choose a pre-defined proifile based on asset's mediainfo, and transcode using the profiles corresponding command. # Installation: ```sh $ gem install xmorph ``` > For a specific version: ```sh $ gem install xmorph -v 0.1.5 ``` # Usage Once you have written XMorph profile for a customer, test if it's working fine. Get base class object for a given customer ```ruby transcoder = XMorph::Base.get_transcode_template(HOST, ACCOUNT_DOMAIN, ACCOUNT_NAME, ASSET_PATH) transcoder = XMorph::Base.get_transcode_template("cinedigm", "Cinedigm", "amagi", "/home/tejaswini/volt/cinedigm/PRAYERLINK112718CC.mxf") ``` transcoder is object for a customer class, in case XMorph is able to find corresponding customers transcoding setup, else it'll raise TranscoderError exception. You can also pass extra param, which will return the filepath it loaded inorder to pick customer specific transcode setup. ```ruby transcoder, loaded_filepath = XMorph::Base.get_transcode_template("cinedigm", "Cinedigm", "amagi", "/home/tejaswini/volt/cinedigm/PRAYERLINK112718CC.mxf", true) ``` Here loaded_filepath will be ```ruby /home/tejaswini/src/xmorph/lib/xmorph/customers/cinedigm/Cinedigm/transcode.rb ``` Get mediainfo for the asset with: ```ruby transcoder.get_asset_details ``` If XMorph is not able to get mediainfo of the asset, it will raise exception. If it was success you can access mediainfo with, which will be a hash. ```ruby transcoder.mediainfo_output ``` Perform default validations on the asset once you get mediainfo. ```ruby transcoder.validate_asset ``` This function will validate the asset against the requirements mentioned in video_checks and audio_checks methods for a given customer. It will either return true or raise exception incase of failure. Once the asset is validated, try to get suitable profile for the same. ```ruby ffmpeg_command = transcoder.get_profile``` or ```profile_name, ffmpeg_cmd = transcoder.get_profile``` to get profile name as well. The ffmpeg command come with %{IN} and %{OUT} replace them with input filepath and output filepath and pass as parameter for, ```transcoder.transcode(updated_ffmpeg_command)``` # Execution ```sh $ cd xmorph $ ./bin/console $ transcoder = XMorph::Base.get_transcode_template("cinedigm", "Cinedigm", "amagi", "/home/tejaswini/volt/cbn/PRAYERLINK112718CC.mxf") $ transcoder, loaded_filepath = XMorph::Base.get_transcode_template("cinedigm", "Cinedigm", "amagi", "/home/tejaswini/volt/cbn/PRAYERLINK112718CC.mxf", true) $ transcoder.get_asset_details $ transcoder.validate_asset $ ffmpeg_cmd = transcoder.get_profile $ profile_name, ffmpeg_cmd = transcoder.get_profile(true) $ transcoder.transcode(ffmpeg_cmd % {IN: asset_path, OUT: tmp_file.path}) ``` # Adding new XMorph profile for a customer ```sh $ cd xmorph/lib/xmorph/customers $ mkdir -p HOST/ACCOUNT_DOMAIN/ $ cd HOST/ACCOUNT_DOMAIN/ $ vi transcode.rb ``` > Here, HOST is customer in customer.amagi.tv and ACCOUNT_DOMAIN is account.domain > EX: for cinedigm.amagi.tv HOST is cinedigm and account's domain is Cinedigm > Interfaces to be implemented in transcodee.rb, ```ruby class Transcode < XMorph::Base #define constants for set of profiles which says what the corresponding command does, or what the input asset is SCALE_TO_720x480_4_3 = "720x480_4_3" SCALE_TO_720x480_16_9 = "720x480_16_9" SCALE_TO_1920x1080_16_9 = "1920x1080_16_9" SCALE_TO_720x480_3_2 = "720x480_3_2" #set the profiles using the constants defined earlier def set_profiles self.profiles = { SCALE_TO_720x480_4_3 => "ffmpeg -y -i %{IN} $options to perform transcoding/encoding$ %{OUT} 2>&1", SCALE_TO_720x480_16_9 => "ffmpeg -y -i %{IN} $options to perform transcoding/encoding$ %{OUT} 2>&1", SCALE_TO_1920x1080_16_9 => "ffmpeg -y -i %{IN} $options to perform transcoding/encoding$ %{OUT} 2>&1", SCALE_TO_720x480_3_2 => "ffmpeg -y -i %{IN} $options to perform transcoding/encoding$ %{OUT} 2>&1", } end #values for the sollowing validations can be Range, Array or IGNORE(upcase) #Range - (n1..n2) - applicable if the values are numbers, validates if value x is between n1 and n2, it also includes fractions. ex 5: in (1..10) -> true and 29.97 in (25..30) -> true #Array - [n1,n2] - validates if value x is in the given array. ex: 5 in [1,10] -> false and 1 in [1,10] -> true #IGNORE will skip validation for the corresponding parameter. it will not even check if media has that parameter. i.e if mediainfo is not able to read parameter x for the asset, its validation will still return true. #These methods are to be implemented in the sub class, there's no defaut method defined in the base class, exception is raised if these methods are not found. def video_checks { ALLOWED_ASPECT_RATIO => ["4:3", "16:9"] || IGNORE, ALLOWED_HEIGHT => (400..1080) || [720, 1080, 546] || IGNORE, ALLOWED_WIDTH => (640..720) || [720, 1920] || IGNORE, ALLOWED_FRAME_RATE => (25..30) || [25, 29.970, 24] || IGNORE, ALLOWED_VIDEO_BIT_RATE => (8..32) || [8, 32] || IGNORE, ALLOWED_SCAN_TYPE => ['progressive', 'interlaced'] || IGNORE, } end def audio_checks { PRESENCE_OF_AUDIO_TRACK => IGNORE || VALIDATE, ALLOWED_NUMBER_OF_AUDIO_TRACKS => (1..16)|| [2, 4, 6, 8] || IGNORE, ALLOWED_AUDIO_CODECS => ['aac', 'ac-3', 'mp2', 'mp4', 'dolby e', 'mpeg audio'] || IGNORE, ALLOWED_AUDIO_BIT_RATE => (120..317) || [192, 317] || IGNORE, ALLOWED_NUMBER_OF_AUDIO_CHANNELS => (1..16) || [1, 2] || IGNORE, } end #Write summary as what are the combinations of profiles we get for this customer, and based on which parameters we choose the profile. # have a set of conditional statements to choose a profile defined above. def set_profile_name self.profile_name = nil self.error = nil mediainfo = self.mediainfo_output video_info = mediainfo["Video"] audio_tracks = mediainfo["Audio"] #Once you have videoinfo and audiotracks info, write a set of if else statements to choose a profile from the above defined #Assign profile name to self.profile_name #Assign any error messages you want to display on UI to self.error, make sure error messages are unambiguous #EX: self.error = "Got unexpected width-#{width} for video with AR-#{aspect_ratio}, expected width: 640 or 720" #Ensure there's only if..elseif statements rather than if..else XMorph::Base.logger.debug("XMorph#set_profile_name#Cinedigm: using profile #{self.profile_name}") unless self.profile_name.nil? return true end ```