Class: FormatParser::MPEGParser
- Inherits:
-
Object
- Object
- FormatParser::MPEGParser
- Extended by:
- IOUtils
- Defined in:
- lib/parsers/mpeg_parser.rb
Overview
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/
Constant Summary collapse
- ASPECT_RATIOS =
{ 1 => '1:1', 2 => '4:3', 3 => '16:9', 4 => '2.21:1' }
- FRAME_RATES =
{ 1 => '23.976', 2 => '24', 3 => '25', 4 => '29.97', 5 => '30', 6 => '50', 7 => '59.94', 8 => '60' }
- PACK_HEADER_START_CODE =
[0x00, 0x00, 0x01, 0xBA].pack('C*')
- SEQUENCE_HEADER_START_CODE =
[0xB3].pack('C*')
- MAX_BLOCK_READS =
32
- BYTES_TO_READ_PER_READ =
1024
Class Method Summary collapse
- .call(io) ⇒ Object
- .convert_3_bytes_to_bits(bytes) ⇒ Object
- .file_info(width_px, height_px, ratio_code, rate_code) ⇒ Object
-
.find_next_header_code_pos(io) ⇒ Object
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.
- .likely_match?(filename) ⇒ Boolean
-
.matches_mpeg_header?(io) ⇒ Boolean
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.
-
.parse_image_size(io) ⇒ Object
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.
-
.parse_rate_information(io) ⇒ Object
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.
- .read_first_12_bits(bits) ⇒ Object
- .read_first_4_bits(byte) ⇒ Object
- .read_last_12_bits(bits) ⇒ Object
- .read_last_4_bits(byte) ⇒ Object
- .valid_aspect_ratio_code?(ratio_code) ⇒ Boolean
- .valid_frame_rate_code?(rate_code) ⇒ Boolean
Methods included from IOUtils
Class Method Details
.call(io) ⇒ Object
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# 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 |
.convert_3_bytes_to_bits(bytes) ⇒ Object
105 106 107 108 |
# 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_info(width_px, height_px, ratio_code, rate_code) ⇒ Object
56 57 58 59 60 61 62 63 64 65 66 |
# 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 |
.find_next_header_code_pos(io) ⇒ Object
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.
93 94 95 96 97 98 |
# 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 |
.likely_match?(filename) ⇒ Boolean
32 33 34 |
# File 'lib/parsers/mpeg_parser.rb', line 32 def self.likely_match?(filename) filename =~ /\.(mpg|mpeg)$/i end |
.matches_mpeg_header?(io) ⇒ Boolean
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.
101 102 103 |
# File 'lib/parsers/mpeg_parser.rb', line 101 def self.matches_mpeg_header?(io) safe_read(io, 4) == PACK_HEADER_START_CODE end |
.parse_image_size(io) ⇒ Object
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
70 71 72 73 |
# 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 |
.parse_rate_information(io) ⇒ Object
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
77 78 79 80 |
# 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 |
.read_first_12_bits(bits) ⇒ Object
110 111 112 |
# File 'lib/parsers/mpeg_parser.rb', line 110 def self.read_first_12_bits(bits) bits >> 12 & 0x0fff end |
.read_first_4_bits(byte) ⇒ Object
118 119 120 |
# File 'lib/parsers/mpeg_parser.rb', line 118 def self.read_first_4_bits(byte) byte >> 4 end |
.read_last_12_bits(bits) ⇒ Object
114 115 116 |
# File 'lib/parsers/mpeg_parser.rb', line 114 def self.read_last_12_bits(bits) bits & 0x0fff end |
.read_last_4_bits(byte) ⇒ Object
122 123 124 |
# File 'lib/parsers/mpeg_parser.rb', line 122 def self.read_last_4_bits(byte) byte & 0x0F end |
.valid_aspect_ratio_code?(ratio_code) ⇒ Boolean
82 83 84 |
# File 'lib/parsers/mpeg_parser.rb', line 82 def self.valid_aspect_ratio_code?(ratio_code) ASPECT_RATIOS.include?(ratio_code) end |
.valid_frame_rate_code?(rate_code) ⇒ Boolean
86 87 88 |
# File 'lib/parsers/mpeg_parser.rb', line 86 def self.valid_frame_rate_code?(rate_code) FRAME_RATES.include?(rate_code) end |