Module: Shazamio::AudioLoader
- Defined in:
- lib/shazamio/audio_loader.rb
Overview
The Python project decodes arbitrary audio formats (mp3, ogg, m4a, ...)
via pydub, which itself just shells out to ffmpeg. Ruby has no
standard-library audio decoder either, so this does the same thing
directly: pipe the input through ffmpeg and ask for raw signed 16-bit
little-endian mono 16kHz PCM, which is exactly what the signature
algorithm expects.
Requires ffmpeg to be installed and on PATH (same real-world
requirement the Python library has via pydub).
Defined Under Namespace
Classes: DecodeError, FFmpegNotFound
Class Method Summary collapse
- .ensure_ffmpeg! ⇒ Object
-
.pcm_s16le_mono_16k(source) ⇒ Array<Integer>
Signed 16-bit mono samples at 16kHz.
- .resolve_path(source) ⇒ Object
Class Method Details
.ensure_ffmpeg! ⇒ Object
47 48 49 50 51 52 |
# File 'lib/shazamio/audio_loader.rb', line 47 def ensure_ffmpeg! return if system("ffmpeg", "-version", out: File::NULL, err: File::NULL) raise FFmpegNotFound, "ffmpeg was not found on PATH. Install it (e.g. `apt install ffmpeg` " \ "or `brew install ffmpeg`) to decode audio files." end |
.pcm_s16le_mono_16k(source) ⇒ Array<Integer>
Returns signed 16-bit mono samples at 16kHz.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/shazamio/audio_loader.rb', line 26 def pcm_s16le_mono_16k(source) ensure_ffmpeg! path, cleanup = resolve_path(source) begin stdout, stderr, status = Open3.capture3( "ffmpeg", "-v", "error", "-y", "-i", path, "-ac", "1", "-ar", "16000", "-f", "s16le", "pipe:1" ) raise DecodeError, "ffmpeg failed: #{stderr}" unless status.success? stdout.unpack("s<*") ensure cleanup&.call end end |
.resolve_path(source) ⇒ Object
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/shazamio/audio_loader.rb', line 54 def resolve_path(source) case source when String if File.exist?(source) [source, nil] else raise DecodeError, "No such file: #{source.inspect}. `recognize`/`pcm_s16le_mono_16k` treats a String " \ "as a file path. If you meant to pass raw audio bytes instead of a path, wrap them " \ "first, e.g. StringIO.new(bytes)." end else bytes = source.respond_to?(:read) ? source.read : source.to_s tmp = Tempfile.new(["shazamio", ".audio"]) tmp.binmode tmp.write(bytes) tmp.close [tmp.path, -> { tmp.unlink }] end end |