Class: Wavify::Codecs::Registry

Inherits:
Object
  • Object
show all
Defined in:
lib/wavify/codecs/registry.rb,
sig/codecs.rbs

Overview

Selects a codec implementation by extension and/or magic bytes.

Constant Summary collapse

MAGIC_PROBES =

Probe lambdas used to match container signatures from leading bytes.

{
  "RIFF" => lambda do |bytes|
    bytes.bytesize >= 12 && bytes.start_with?("RIFF") && bytes[8, 4] == "WAVE"
  end,
  "fLaC" => ->(bytes) { bytes.start_with?("fLaC") },
  "OggS" => ->(bytes) { bytes.start_with?("OggS") },
  "FORM" => lambda do |bytes|
    bytes.bytesize >= 12 && bytes.start_with?("FORM") && %w[AIFF AIFC].include?(bytes[8, 4])
  end
}.freeze
EXTENSIONS =

Extension-to-codec mapping for path-based detection.

{
  ".wav" => Wav,
  ".wave" => Wav,
  ".flac" => Flac,
  ".ogg" => OggVorbis,
  ".oga" => OggVorbis,
  ".aiff" => Aiff,
  ".aif" => Aiff,
  ".aifc" => Aiff,
  ".raw" => Raw,
  ".pcm" => Raw
}.freeze
MAGIC_CODEC_ORDER =

Probe order for magic-byte detection.

[
  ["RIFF", Wav],
  ["fLaC", Flac],
  ["OggS", OggVorbis],
  ["FORM", Aiff]
].freeze
MAX_MAGIC_PROBE_SIZE =

Maximum leading-byte window allowed for a custom magic probe.

65_536
BUILTIN_MAGIC_ENTRIES =
MAGIC_CODEC_ORDER.each_with_index.map do |(magic_key, codec), index|
  {
    extension: nil,
    codec: codec,
    probe: MAGIC_PROBES.fetch(magic_key),
    probe_size: 12,
    priority: 0,
    sequence: index,
    custom: false
  }.freeze
end.freeze
CODEC_POSITIONAL_ARITY =
{
  read: 1,
  write: 2,
  stream_read: 1,
  stream_write: 1,
  metadata: 1
}.freeze

Class Method Summary collapse

Class Method Details

.available_formatsArray<String>

Returns extension names whose codec dependencies are available.

Returns:

  • (Array<String>)

    extension names whose codec dependencies are available



175
176
177
178
179
180
181
# File 'lib/wavify/codecs/registry.rb', line 175

def available_formats
  extension_snapshot.filter_map do |extension, codec|
    next if codec.respond_to?(:available?) && !codec.available?

    extension.delete_prefix(".")
  end.uniq.sort.freeze
end

.detect(io_or_path, strict: false, filename: nil) ⇒ Class

Detects the codec for a path or IO object. Read detection prefers magic bytes when available.

Parameters:

  • io_or_path (String, IO)
  • filename (String, nil) (defaults to: nil)

    optional filename hint for IO inputs

  • strict: (Boolean) (defaults to: false)
  • filename: (String, nil) (defaults to: nil)

Returns:

  • (Class)

    codec class



71
72
73
# File 'lib/wavify/codecs/registry.rb', line 71

def detect(io_or_path, strict: false, filename: nil)
  detect_for_read(io_or_path, strict: strict, filename: filename)
end

.detect_for_read(io_or_path, strict: false, filename: nil) ⇒ Class

Detects the codec for reading.

Parameters:

  • io_or_path (String, IO)
  • strict (Boolean) (defaults to: false)

    raise when extension and magic bytes disagree

  • filename (String, nil) (defaults to: nil)

    optional filename hint for IO inputs

  • strict: (Boolean) (defaults to: false)
  • filename: (String, nil) (defaults to: nil)

Returns:

  • (Class)

    codec class



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/wavify/codecs/registry.rb', line 81

def detect_for_read(io_or_path, strict: false, filename: nil)
  extension_codec = detect_by_extension(io_or_path, filename: filename)
  magic_codec = if non_rewindable_io?(io_or_path)
                  if strict
                    raise InvalidParameterError, "strict codec detection requires rewindable IO"
                  end
                  unless extension_codec
                    raise InvalidParameterError,
                          "codec detection requires rewindable IO; pass filename: as a codec hint"
                  end
                else
                  detect_by_magic(io_or_path)
                end
  if strict && extension_codec && magic_codec && extension_codec != magic_codec
    raise InvalidFormatError,
          "codec mismatch: extension implies #{extension_codec.name}, magic bytes imply #{magic_codec.name}"
  end

  magic_codec || extension_codec || raise_not_found(filename || io_or_path)
end

.detect_for_write(io_or_path, filename: nil) ⇒ Class

Detects the codec for writing. Write detection intentionally prefers the target filename extension.

Parameters:

  • io_or_path (String, IO)
  • filename: (String, nil) (defaults to: nil)

Returns:

  • (Class)

    codec class



107
108
109
# File 'lib/wavify/codecs/registry.rb', line 107

def detect_for_write(io_or_path, filename: nil)
  detect_by_extension(io_or_path, filename: filename) || detect_by_magic(io_or_path) || raise_not_found(filename || io_or_path)
end

.register(extension, codec, magic: nil, priority: 0, probe_size: 12) ⇒ Class

Registers or replaces a codec for a filename extension.

Parameters:

  • extension (String)
  • codec (Class)
  • magic: (String, untyped) (defaults to: nil)
  • priority: (Integer) (defaults to: 0)
  • probe_size: (Integer) (defaults to: 12)

Returns:

  • (Class)

    codec



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/wavify/codecs/registry.rb', line 128

def register(extension, codec, magic: nil, priority: 0, probe_size: 12)
  normalized_extension = normalize_extension(extension)
  validate_codec!(codec)
  magic_probe, normalized_probe_size = normalize_magic_probe(magic, probe_size)
  unless priority.is_a?(Integer)
    raise InvalidParameterError, "priority must be an Integer"
  end

  extensions_mutex.synchronize do
    extensions[normalized_extension] = codec
    magic_entries.delete_if { |entry| entry[:custom] && entry[:extension] == normalized_extension }
    if magic_probe
      magic_entries << {
        extension: normalized_extension,
        codec: codec,
        probe: magic_probe,
        probe_size: normalized_probe_size,
        priority: priority,
        sequence: next_magic_sequence,
        custom: true
      }.freeze
    end
  end
  codec
end

.resolve(codec) ⇒ singleton(Base)

Resolves an explicit codec class or registered extension name.

Parameters:

  • codec (String, Symbol, singleton(Base))

Returns:



112
113
114
115
116
117
118
119
120
121
# File 'lib/wavify/codecs/registry.rb', line 112

def resolve(codec)
  if codec.respond_to?(:read) && codec.respond_to?(:write)
    validate_codec!(codec)
    return codec
  end

  key = codec.to_s
  normalized = normalize_extension(key)
  extensions_mutex.synchronize { extensions[normalized] } || raise_not_found(codec)
end

.supported_formatsArray<String>

Returns supported extension names without leading dots.

Returns:

  • (Array<String>)

    supported extension names without leading dots



170
171
172
# File 'lib/wavify/codecs/registry.rb', line 170

def supported_formats
  extension_snapshot.keys.map { |extension| extension.delete_prefix(".") }.uniq.sort.freeze
end

.unregister(extension) ⇒ singleton(Base)?

Removes a custom codec mapping or restores the built-in mapping.

Parameters:

  • extension (String)

Returns:

  • (singleton(Base), nil)


155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/wavify/codecs/registry.rb', line 155

def unregister(extension)
  normalized_extension = normalize_extension(extension)
  extensions_mutex.synchronize do
    previous = extensions[normalized_extension]
    if EXTENSIONS.key?(normalized_extension)
      extensions[normalized_extension] = EXTENSIONS.fetch(normalized_extension)
    else
      extensions.delete(normalized_extension)
    end
    magic_entries.delete_if { |entry| entry[:custom] && entry[:extension] == normalized_extension }
    previous
  end
end