Module: Audioproxy::Options
- Defined in:
- lib/audioproxy/options.rb
Overview
Renders the proxy's option grammar: +/+-separated key:value segments,
with colon-separated parts for the multi-part keys.
This layer renders, it does not validate (D1). Value domains and cross-key rules belong to the proxy, which versions them with the server and returns structured 422s. The exceptions are values this module cannot render faithfully — an unknown key, a number that does not fit the grammar, a value carrying a separator — because a mangled segment is a valid-looking URL for the wrong variant, and it fails at request time, far from here.
Constant Summary collapse
- KEYS =
The proxy's sixteen option keys, canonical short spellings.
%i[bd br cb ch dl exp f fade gain norm pk_bits pk_fmt pts q sr t].freeze
- REQUEST_KEYS =
The one request option in the grammar: signed as path bytes, but excluded from the proxy's canonical options string, its cache key and its ffmpeg args, so two URLs differing only here are one variant and one render. Nothing in this module treats it specially beyond D1b below; the distinction is the proxy's, and it is recorded here because it is the reason exp may be appended to a raw: string that variant options may not.
%i[exp].freeze
- ALIASES =
A spelled-out spelling for each canonical key, for call sites that would rather read than decode. Total over KEYS, so "does this key have an alias" never has two answers:
fadeandgainare already words and alias to themselves. The names are the proxy's own where it has one — its Options struct calls ptspeak_count, pk_fmtpeak_formatand pk_bitspeak_bits— so this is one vocabulary spelled twice, not a second vocabulary (D2). peak_bits is deliberately not bit_depth, which bd owns. { f: :format, br: :bitrate, q: :quality, sr: :sample_rate, ch: :channels, bd: :bit_depth, t: :trim, fade: :fade, gain: :gain, norm: :normalize, pts: :peak_count, pk_fmt: :peak_format, pk_bits: :peak_bits, dl: :download, cb: :cache_buster, exp: :expires_at }.freeze
- CANONICAL =
Every accepted spelling to the canonical key it renders as. Canonical keys map to themselves, so resolution is one lookup rather than a conditional.
ALIASES.each_with_object({}) { |(key, spelled), table| table[spelled] = key } .merge(KEYS.to_h { |key| [ key, key ] }) .freeze
- PEAKS_KEYS =
The keys that reach the proxy's peaks renderer (API v1 §3.3): the peaks keys themselves, and the ones peaks follow so a waveform matches the audio drawn under it. A positive list on purpose (D4): an ignored option still enters the proxy's cache key, so one that slips through buys a second render of byte-identical peaks, and a key added to the proxy later is refused here until someone checks it belongs.
%i[pts pk_fmt pk_bits ch t fade gain norm dl cb].freeze
- PEAKS_DEFAULT_KEYS =
The configured defaults that carry over to peaks (D10): the ones that change the samples, so a waveform matches the audio drawn above it, and the cache buster. Narrower than PEAKS_KEYS because a default was written for audio: ch:2 there means stereo output, but on peaks it means per-channel pairs instead of the mono downmix, and a dl: filename for the audio is never the right name for peaks JSON.
%i[t fade gain norm cb].freeze
- MULTI_PART_KEYS =
Keys whose grammar takes colon-separated parts:
t:START[:DURATION],fade:IN[:OUT],norm:ebu[:I[:TP[:LRA]]]. %i[t fade norm].freeze
- TIME_KEYS =
Keys whose values are a number of seconds, and so may be written as an ActiveSupport::Duration (D6).
%i[t fade].freeze
- OPAQUE_KEYS =
Keys the proxy treats as opaque payloads (download filename, cache buster), rendered verbatim rather than number-formatted.
%i[cb dl].freeze
- MAX_DECIMALS =
The proxy caps decimals at 3 places when it parses, and hashes the normalized options string into its cache key.
3- MAX_EXPIRES_AT =
The proxy's own bound on exp (@max_expires_at in its Options module): 9999-12-31T23:59:59Z. Past it, the proxy answers 422 invalid-option. The grammar fact lives here; UrlBuilder is what checks a caller's value against it, because that is where an out-of-range timestamp can still be reported against the keyword the caller actually wrote.
253_402_300_799- SEPARATORS =
Characters a rendered value may not carry. The builder supplies '/' and ':', so a value containing either silently invents a segment or a part. '?' and '#' end the path as far as a browser is concerned, which truncates what the proxy receives below what was signed: a 403 at request time, far from the call. Whitespace and control characters are not URL bytes at all.
%r{[/:?#\s]|[[:cntrl:]]}
Class Method Summary collapse
-
.format_number(value) ⇒ Object
The proxy's canonical minimal number spelling.
-
.render(options) ⇒ Object
Renders an ordered key => value Hash into an options segment.
-
.resolve(options) ⇒ Object
Rewrites a key => value Hash onto the canonical short keys, so that everything downstream — rendering, ordering, the defaults merge — sees one vocabulary and is unaware aliases exist (D1).
-
.segment(key, value) ⇒ Object
Renders one
key:valuesegment.
Class Method Details
.format_number(value) ⇒ Object
The proxy's canonical minimal number spelling. Strings and symbols pass through untouched — the caller opted out of formatting.
153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/audioproxy/options.rb', line 153 def format_number(value) case value when String then value when Symbol then value.to_s when Integer then value.to_s when Complex # Numeric, but Complex#round is undefined and Complex#to_r silently # drops a zero imaginary part. Neither is a number this grammar has. raise ArgumentError, "Audioproxy option values must be real numbers, got #{value.inspect}" when Numeric then format_decimal(value) else raise ArgumentError, "Audioproxy option values must be numbers, strings or symbols, got #{value.class}" end end |
.render(options) ⇒ Object
Renders an ordered key => value Hash into an options segment. Caller order is preserved (D4); normalization is the proxy's business.
107 108 109 |
# File 'lib/audioproxy/options.rb', line 107 def render() resolve().map { |key, value| segment(key, value) }.join("/") end |
.resolve(options) ⇒ Object
Rewrites a key => value Hash onto the canonical short keys, so that
everything downstream — rendering, ordering, the defaults merge — sees
one vocabulary and is unaware aliases exist (D1). Insertion order is
preserved, so an aliased key keeps its slot. Unrecognized keys pass
through untouched, to be reported by segment against the key table
rather than by a second, thinner error here.
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/audioproxy/options.rb', line 117 def resolve() spellings = {} .each_with_object({}) do |(key, value), resolved| canonical = CANONICAL.fetch(symbolize(key), key) # Ruby's keyword collection keeps both spellings, and picking a winner # by position would make the URL depend on argument order in a way # nothing else here does (D4). # inspect, not interpolation: the two spellings are often identical as # text — "fade" and :fade, or a self-aliasing key given both ways — # and "as fade and fade" tells the caller nothing. if (first = spellings[canonical]) raise ArgumentError, "Audioproxy option #{canonical} was given twice, as #{first.inspect} and #{key.inspect}; " \ "each option takes one spelling per call" end spellings[canonical] = key resolved[canonical] = value end end |
.segment(key, value) ⇒ Object
Renders one key:value segment. The key may be canonical or an alias.
141 142 143 144 145 146 147 148 149 |
# File 'lib/audioproxy/options.rb', line 141 def segment(key, value) key = CANONICAL.fetch(symbolize(key)) do |unknown| raise ArgumentError, "unknown Audioproxy option #{unknown.inspect}; known keys are #{KEYS.join(", ")}, " \ "each also accepted as its spelled-out alias (#{ALIASES[:br]}, #{ALIASES[:sr]}, #{ALIASES[:pk_fmt]}, …)" end "#{key}:#{render_value(key, value)}" end |