Class: FormatParser::MP3Parser
- Inherits:
-
Object
- Object
- FormatParser::MP3Parser
- Defined in:
- lib/parsers/mp3_parser.rb
Defined Under Namespace
Modules: ID3V1, ID3V2 Classes: InvalidDeepFetch, MP3Info, MPEGFrame, VBRHeader
Constant Summary collapse
- MAX_FRAMES_TO_SCAN =
We limit the number of MPEG frames we scan to obtain our duration estimation
128
- SAMPLES_PER_FRAME =
Default frame size for mp3
1152
- ZIP_LOCAL_ENTRY_SIGNATURE =
For some edge cases
"PK\x03\x04\x14\x00".b
Instance Method Summary collapse
Instance Method Details
#call(io) ⇒ Object
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/parsers/mp3_parser.rb', line 29 def call(io) # Special case: some ZIPs (Office documents) did detect as MP3s. # To avoid having that happen, we check for the PKZIP signature - # local entry header signature - at the very start of the file return if io.read(6) == ZIP_LOCAL_ENTRY_SIGNATURE io.seek(0) # Read the last 128 bytes which might contain ID3v1 id3_v1 = ID3V1.attempt_id3_v1_extraction(io) # Read the header bytes that might contain ID3v1 id3_v2 = ID3V2.attempt_id3_v2_extraction(io) # Compute how many bytes are occupied by the actual MPEG frames ignore_bytes_at_tail = id3_v1 ? 128 : 0 ignore_bytes_at_head = id3_v2 ? io.pos : 0 bytes_used_by_frames = io.size - ignore_bytes_at_tail - ignore_bytes_at_tail io.seek(ignore_bytes_at_head) maybe_xing_header, initial_frames = parse_mpeg_frames(io) return if initial_frames.empty? first_frame = initial_frames.first file_info = FormatParser::Audio.new( format: :mp3, num_audio_channels: first_frame.channels, audio_sample_rate_hz: first_frame.sample_rate, # media_duration_frames is omitted because the frames # in MPEG are not the same thing as in a movie file - they # do not tell anything of substance intrinsics: { id3_v1: id3_v1 ? id3_v1.to_h : nil, id3_v2: id3_v2 ? id3_v2.map(&:to_h) : nil, xing_header: maybe_xing_header.to_h, initial_frames: initial_frames.map(&:to_h) } ) if maybe_xing_header duration = maybe_xing_header.frames * SAMPLES_PER_FRAME / first_frame.sample_rate.to_f _bit_rate = maybe_xing_header.byte_count * 8 / duration / 1000 file_info.media_duration_seconds = duration return file_info end # Estimate duration using the frames we did parse - to have an exact one # we would need to have all the frames and thus read most of the file _avg_bitrate = float_average_over(initial_frames, :frame_bitrate) avg_frame_size = float_average_over(initial_frames, :frame_length) avg_sample_rate = float_average_over(initial_frames, :sample_rate) est_frame_count = bytes_used_by_frames / avg_frame_size est_samples = est_frame_count * SAMPLES_PER_FRAME est_duration_seconds = est_samples / avg_sample_rate # Safeguard for i.e. some JPEGs being recognized as MP3 # to prevent ambiguous recognition return if est_duration_seconds == Float::INFINITY file_info.media_duration_seconds = est_duration_seconds file_info end |