class FormatParser::MPEGParser
MPEG Headers documentation: dvd.sourceforge.net/dvdinfo/mpeghdrs.html#seq www.cs.columbia.edu/~delbert/docs/Dueck%20–%20MPEG-2%20Video%20Transcoding.pdf Useful tool to check the file information: www.metadata2go.com/
Constants
- ASPECT_RATIOS
- BYTES_TO_READ_PER_READ
- FRAME_RATES
- MAX_BLOCK_READS
- PACK_HEADER_START_CODE
- SEQUENCE_HEADER_START_CODE
Public Class Methods
# File lib/parsers/mpeg_parser.rb, line 36 def self.call(io) return unless matches_mpeg_header?(io) # We are looping though the stream because there can be several sequence headers and some of them are not useful. # If we detect that the header is not useful, then we look for the next one for SEEK_FOR_SEQUENCE_HEADER_TIMES_LIMIT # If we reach the EOF, then the mpg is likely to be corrupted and we return nil MAX_BLOCK_READS.times do next unless pos = find_next_header_code_pos(io) io.seek(pos + 1) horizontal_size, vertical_size = parse_image_size(io) ratio_code, rate_code = parse_rate_information(io) if valid_aspect_ratio_code?(ratio_code) && valid_frame_rate_code?(rate_code) return file_info(horizontal_size, vertical_size, ratio_code, rate_code) end end nil # otherwise the return value of Integer#times will be returned rescue FormatParser::IOUtils::InvalidRead nil end
# File lib/parsers/mpeg_parser.rb, line 105 def self.convert_3_bytes_to_bits(bytes) bytes = bytes.unpack('CCC') (bytes[0] << 16) | (bytes[1] << 8) | (bytes[2]) end
# File lib/parsers/mpeg_parser.rb, line 56 def self.file_info(width_px, height_px, ratio_code, rate_code) FormatParser::Video.new( format: :mpg, width_px: width_px, height_px: height_px, intrinsics: { aspect_ratio: ASPECT_RATIOS.fetch(ratio_code), frame_rate: FRAME_RATES.fetch(rate_code) }, ) end
Returns the position of the next sequence package content in the stream This method will read BYTES_TO_READ_PER_TIME in each loop for a maximum amount of SEEK_FOR_SEQUENCE_HEADER_START_CODE_TIMES_LIMIT times If the package is not found, then it returns nil.
# File lib/parsers/mpeg_parser.rb, line 93 def self.find_next_header_code_pos(io) pos_before_read = io.pos bin_str = io.read(BYTES_TO_READ_PER_READ) # bin_str might be nil if we are at EOF header_relative_index = bin_str && bin_str.index(SEQUENCE_HEADER_START_CODE) return pos_before_read + header_relative_index if header_relative_index end
# File lib/parsers/mpeg_parser.rb, line 32 def self.likely_match?(filename) filename =~ /\.(mpg|mpeg)$/i end
If the first 4 bytes of the stream are equal to 00 00 01 BA, the pack start code for the Pack Header, then it's an MPEG file.
# File lib/parsers/mpeg_parser.rb, line 101 def self.matches_mpeg_header?(io) safe_read(io, 4) == PACK_HEADER_START_CODE end
The following 3 bytes after the sequence header code, gives us information about the px size 1.5 bytes (12 bits) for horizontal size and 1.5 bytes for vertical size
# File lib/parsers/mpeg_parser.rb, line 70 def self.parse_image_size(io) image_size = convert_3_bytes_to_bits(safe_read(io, 3)) [read_first_12_bits(image_size), read_last_12_bits(image_size)] end
The following byte gives us information about the aspect ratio and frame rate 4 bits corresponds to the aspect ratio and 4 bits to the frame rate code
# File lib/parsers/mpeg_parser.rb, line 77 def self.parse_rate_information(io) rate_information = safe_read(io, 1).unpack('C').first [read_first_4_bits(rate_information), read_last_4_bits(rate_information)] end
# File lib/parsers/mpeg_parser.rb, line 110 def self.read_first_12_bits(bits) bits >> 12 & 0x0fff end
# File lib/parsers/mpeg_parser.rb, line 118 def self.read_first_4_bits(byte) byte >> 4 end
# File lib/parsers/mpeg_parser.rb, line 114 def self.read_last_12_bits(bits) bits & 0x0fff end
# File lib/parsers/mpeg_parser.rb, line 122 def self.read_last_4_bits(byte) byte & 0x0F end
# File lib/parsers/mpeg_parser.rb, line 82 def self.valid_aspect_ratio_code?(ratio_code) ASPECT_RATIOS.include?(ratio_code) end
# File lib/parsers/mpeg_parser.rb, line 86 def self.valid_frame_rate_code?(rate_code) FRAME_RATES.include?(rate_code) end