Class: TagLib::MediaFile
- Inherits:
-
Object
- Object
- TagLib::MediaFile
- Extended by:
- Forwardable
- Includes:
- Enumerable
- Defined in:
- lib/taglib_simple/media_file.rb
Overview
Attribute and Hash like semantics unifying various TagLib concepts into a single interface:
Retrieve Method | Attribute access | Hash Key | Example Key | Value Type | TagLib Type |
---|---|---|---|---|---|
#audio_properties | read only | Symbol (r) | :bitrate | Integer | AudioProperties |
#tag | read/write | Symbol (rw) | :title | String or Integer | Tag |
#properties | read/write (dynamic) | String (rw) | "COMPOSER" | Array[String] | PropertyMap |
#complex_properties | read/write (dynamic) | String (rw) | "PICTURE" | Array[Hash[String]] | List[VariantMap] |
TagLib requires #audio_properties to be specifically requested at #initialize while the other components can be lazily loaded as required.
For read-only usage MediaFile.read can be used to #retrieve the required components from TagLib, #close the file, and continue working with the read-only result.
Audio Properties (delegated) collapse
-
#audio_length ⇒ Integer
readonly
The length of the audio in milliseconds if available.
- #audio_properties ⇒ AudioProperties? readonly
-
#bitrate ⇒ Integer
readonly
The bitrate of the audio in kb/s if available.
-
#channels ⇒ Integer
readonly
The number of audio channels.
-
#sample_rate ⇒ Integer
readonly
The sample rate in Hz if available.
Tag Attributes (delegated) collapse
-
#album ⇒ String?
The album name if available.
-
#artist ⇒ String?
The artist name if available.
-
#comment ⇒ String?
Additional comments about the track if available.
-
#genre ⇒ String?
The genre of the track if available.
- #tag ⇒ AudioTag? readonly
-
#title ⇒ String?
The title of the track if available.
-
#track ⇒ Integer?
The track number if available.
-
#year ⇒ Integer?
The release year if available.
General Properties collapse
-
#complex_properties ⇒ Hash<String>
readonly
A hash that lazily pulls complex properties from TagLib.
-
#complex_property_keys ⇒ Array<String>
readonly
Set of keys that represent complex properties.
- #properties ⇒ Hash<String, Array<String>>? readonly
Hash Semantics collapse
-
#[](key, all: false, saved: false) ⇒ Integer, ...
Get a property.
-
#[]=(key, *values) ⇒ Array<String>, ...
Set (replace) a key, these are stored in memory and only sent to taglib on #save!.
-
#delete(key) ⇒ String, ...
Deletes the entry for the given key and returns its previously associated value.
-
#each {|key, values| ... } ⇒ Enumerator, self
Iterates over each key-value pair in the media file's properties.
-
#fetch(key, *default, saved: false) {|key| ... } ⇒ Integer, ...
Fetch the first available value for a property from the media file.
-
#fetch_all(key, *default, saved: false, lazy: !closed?,) {|key| ... } ⇒ Integer, ...
Fetch a potentially multi-value property from the media file.
- #include?(key, saved: false, lazy: !closed?)) ⇒ Boolean (also: #key?, #member?)
-
#keys(lazy: !closed?)) ⇒ Array<String,Symbol>
The list of available property keys.
Dynamic Property Methods collapse
-
#method_missing(method, *args) ⇒ String, ...
Provide read/write accessor like semantics for properties.
Class Method Summary collapse
-
.open(filename, **init) {|media_file| ... } ⇒ MediaFile, Object
Open a file with TagLib.
-
.read(filename, properties: true, tag: true, **init) ⇒ MediaFile
Read information from TagLib, close the file, returning the MediaFile in a read-only state.
Instance Method Summary collapse
-
#clear! ⇒ self
Remove all existing properties from the file.
-
#close ⇒ self
Close this file - releasing memory , file descriptors etc...
-
#closed? ⇒ Boolean
True if the file or IO is closed in TagLib.
-
#initialize(file, all: false, audio_properties: all && :average, **retrieve) ⇒ MediaFile
constructor
A new instance of MediaFile.
-
#modifications ⇒ Object
return [Hash
] accumulated, unsaved properties (frozen). -
#modified? ⇒ Boolean
If any properties been updated and not yet saved.
-
#retrieve(all: false, tag: all, properties: all, complex_property_keys: (all && :all) || nil) ⇒ self
Retrieve and cache specific property types rom TagLib.
-
#save!(replace_all: false) ⇒ self
Save accumulated property changes back to the file.
-
#writable? ⇒ Boolean
True if the file is open and writable.
Constructor Details
#initialize(file, all: false, audio_properties: all && :average, **retrieve) ⇒ MediaFile
Returns a new instance of MediaFile.
102 103 104 105 106 107 108 109 |
# File 'lib/taglib_simple/media_file.rb', line 102 def initialize(file, all: false, audio_properties: all && :average, **retrieve) @fr = file.respond_to?(:valid?) ? file : Simple::FileRef.new(file, audio_properties) raise Error, "TagLib could not open #{file}" unless @fr.valid? @audio_properties = (audio_properties && @fr.audio_properties) || nil reset self.retrieve(all:, **retrieve) end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method, *args) ⇒ String, ...
If the file is open this will lazily retrieve all necessary data from TagLib, otherwise only data retrieved before the file was closed will be available.
Provide read/write accessor like semantics for properties
Method names are converted to tag keys and sent to #[] (readers) or #[]= (writers).
Reader methods prefixed with 'all_' will return a list, otherwise the first available value
Tag keys are generally uppercase and without underscores between words so these are removed. A double-underscore in a method name will be retained as a single underscore in the tag key.
Keys with spaces or other non-method matching characters cannot be accessed dynamically.
460 461 462 463 464 465 466 467 468 469 470 471 472 473 |
# File 'lib/taglib_simple/media_file.rb', line 460 def method_missing(method, *args, &) if (match = method.match(DYNAMIC_METHOD_MATCHER)) key = match[:key].gsub('__', '~').delete('_').upcase.gsub('~', '_') if match[:setter] public_send(:[]=, key, *args) else raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0)" unless args.empty? public_send(:[], key, all: match[:all]) end else super end end |
Instance Attribute Details
#album ⇒ String?
Returns The album name if available.
|
# File 'lib/taglib_simple/media_file.rb', line 207
|
#artist ⇒ String?
Returns The artist name if available.
|
# File 'lib/taglib_simple/media_file.rb', line 204
|
#audio_length ⇒ Integer (readonly)
Returns The length of the audio in milliseconds if available.
|
# File 'lib/taglib_simple/media_file.rb', line 171
|
#audio_properties ⇒ AudioProperties? (readonly)
169 170 171 |
# File 'lib/taglib_simple/media_file.rb', line 169 def audio_properties @audio_properties end |
#bitrate ⇒ Integer (readonly)
Returns The bitrate of the audio in kb/s if available.
|
# File 'lib/taglib_simple/media_file.rb', line 174
|
#channels ⇒ Integer (readonly)
Returns The number of audio channels.
183 |
# File 'lib/taglib_simple/media_file.rb', line 183 def_delegators :audio_properties, *AudioProperties.members |
#comment ⇒ String?
Returns Additional comments about the track if available.
222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
# File 'lib/taglib_simple/media_file.rb', line 222 AudioTag.members.each do |tag_member| define_method tag_member do return @mutated[tag_member] if @mutated.key?(tag_member) # if the file is closed then try and use #properties if we don't have #tag # probably won't work for track and year tag_value = tag ? tag.public_send(tag_member) : properties.fetch(tag_member.to_s.upcase, [])&.first tag_value && (%i[year track].include?(tag_member) ? tag_value.to_i : tag_value) end define_method :"#{tag_member}=" do |value| write_property(tag_member, AudioTag.check_value(tag_member, value)) end end |
#complex_properties ⇒ Hash<String> (readonly)
Returns a hash that lazily pulls complex properties from TagLib.
252 253 254 |
# File 'lib/taglib_simple/media_file.rb', line 252 def complex_properties @complex_properties end |
#complex_property_keys ⇒ Array<String> (readonly)
If the file is open this will lazily retrieve all necessary data from TagLib, otherwise only data retrieved before the file was closed will be available.
Set of keys that represent complex properties.
Used to determine whether ##complex_properties or ##properties is used to find a given key.
- Any keys already retrieved into #complex_properties are always included.
- If no keys were provided to #retrieve the list of keys will be lazily fetched from TagLib if possible.
264 265 266 |
# File 'lib/taglib_simple/media_file.rb', line 264 def complex_property_keys(lazy: !closed?) @complex_properties.keys | ((lazy && (@complex_property_keys ||= @fr.complex_property_keys)) || []) end |
#genre ⇒ String?
Returns The genre of the track if available.
|
# File 'lib/taglib_simple/media_file.rb', line 210
|
#properties ⇒ Hash<String, Array<String>>? (readonly)
If the file is open this will lazily retrieve all necessary data from TagLib, otherwise only data retrieved before the file was closed will be available.
246 247 248 |
# File 'lib/taglib_simple/media_file.rb', line 246 def properties(lazy: !closed?) @properties ||= (lazy || nil) && @fr.properties end |
#sample_rate ⇒ Integer (readonly)
Returns The sample rate in Hz if available.
|
# File 'lib/taglib_simple/media_file.rb', line 177
|
#tag ⇒ AudioTag? (readonly)
If the file is open this will lazily retrieve all necessary data from TagLib, otherwise only data retrieved before the file was closed will be available.
197 198 199 |
# File 'lib/taglib_simple/media_file.rb', line 197 def tag(lazy: !closed?) @tag ||= (lazy || nil) && @fr.tag end |
#title ⇒ String?
Returns The title of the track if available.
|
# File 'lib/taglib_simple/media_file.rb', line 201
|
#track ⇒ Integer?
Returns The track number if available.
|
# File 'lib/taglib_simple/media_file.rb', line 216
|
#year ⇒ Integer?
Returns The release year if available.
|
# File 'lib/taglib_simple/media_file.rb', line 213
|
Class Method Details
.open(filename, **init) {|media_file| ... } ⇒ MediaFile, Object
Open a file with TagLib
69 70 71 72 73 74 75 76 77 78 |
# File 'lib/taglib_simple/media_file.rb', line 69 def open(filename, **init) f = new(filename, **init) return f unless block_given? begin yield(f).tap { f.save! if f.modified? } ensure f.close end end |
.read(filename, properties: true, tag: true, **init) ⇒ MediaFile
Read information from TagLib, close the file, returning the MediaFile in a read-only state.
87 88 89 |
# File 'lib/taglib_simple/media_file.rb', line 87 def read(filename, properties: true, tag: true, **init) self.open(filename, properties:, tag:, **init, &:itself) end |
Instance Method Details
#[](key, all: false, saved: false) ⇒ Integer, ...
If the file is open this will lazily retrieve all necessary data from TagLib, otherwise only data retrieved before the file was closed will be available.
Get a property
282 283 284 |
# File 'lib/taglib_simple/media_file.rb', line 282 def [](key, all: false, saved: false) public_send(all ? :fetch_all : :fetch, key, nil, saved:) end |
#[]=(key, *values) ⇒ Array<String>, ...
Set (replace) a key, these are stored in memory and only sent to taglib on #save!
340 341 342 343 344 345 346 347 348 349 350 351 352 353 |
# File 'lib/taglib_simple/media_file.rb', line 340 def []=(key, *values) case key when String raise ArgumentError, 'expected 2.. arguments, received 1' if values.empty? write_string_property(key, values) when *AudioTag.members raise ArgumentError, "expected 2 arguments, receive #{values.size} + 1" unless values.size == 1 write_property(key, AudioTag.check_value(key, values.first)) else raise ArgumentError, "expected String or AudioTag member, received #{key}" end end |
#clear! ⇒ self
Remove all existing properties from the file. Any pending modifications will be also lost.
490 491 492 493 494 |
# File 'lib/taglib_simple/media_file.rb', line 490 def clear! @mutated.clear save!(replace_all: true) self end |
#close ⇒ self
Close this file - releasing memory , file descriptors etc... on the TagLib library side while retaining any previously retrieved data in a read-only state.
157 158 159 160 161 162 |
# File 'lib/taglib_simple/media_file.rb', line 157 def close warn "closing with unsaved properties #{@mutated.keys}" if @mutated&.any? self ensure @fr.close end |
#closed? ⇒ Boolean
145 146 147 |
# File 'lib/taglib_simple/media_file.rb', line 145 def closed? !valid? end |
#delete(key) ⇒ String, ...
Deletes the entry for the given key and returns its previously associated value.
359 360 361 362 363 |
# File 'lib/taglib_simple/media_file.rb', line 359 def delete(key, &) fetch(key, &).tap { self[key] = nil } rescue KeyError nil end |
#each {|key, values| ... } ⇒ Enumerator, self
If the file is open this will lazily retrieve all necessary data from TagLib, otherwise only data retrieved before the file was closed will be available.
Iterates over each key-value pair in the media file's properties
418 419 420 421 422 423 424 425 426 |
# File 'lib/taglib_simple/media_file.rb', line 418 def each return enum_for(:each) unless block_given? keys.each do |k| v = fetch_all(k, nil) yield k, v if v end self end |
#fetch(key, *default, saved: false) {|key| ... } ⇒ Integer, ...
If the file is open this will lazily retrieve all necessary data from TagLib, otherwise only data retrieved before the file was closed will be available.
Fetch the first available value for a property from the media file
299 300 301 302 |
# File 'lib/taglib_simple/media_file.rb', line 299 def fetch(key, *default, saved: false, &) result = fetch_all(key, *default, saved:, &) key.is_a?(String) && result.is_a?(Array) ? result.first : result end |
#fetch_all(key, *default, saved: false, lazy: !closed?,) {|key| ... } ⇒ Integer, ...
If the file is open this will lazily retrieve all necessary data from TagLib, otherwise only data retrieved before the file was closed will be available.
Fetch a potentially multi-value property from the media file
318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'lib/taglib_simple/media_file.rb', line 318 def fetch_all(key, *default, saved: false, lazy: !closed?, &) return @mutated[key] if !saved && @mutated.include?(key) case key when String fetch_property(key, *default, lazy:, &) when *AudioTag.members tag(lazy: lazy).to_h.fetch(key, *default, &) when *AudioProperties.members audio_properties.to_h.fetch(key, *default, &) else raise ArgumentError, "Invalid key: #{key}" end end |
#include?(key, saved: false, lazy: !closed?)) ⇒ Boolean Also known as: key?, member?
If the file is open this will lazily retrieve all necessary data from TagLib, otherwise only data retrieved before the file was closed will be available.
384 385 386 387 388 389 390 391 392 393 394 395 396 397 |
# File 'lib/taglib_simple/media_file.rb', line 384 def include?(key, saved: false, lazy: !closed?) return true if !saved && @mutated.keys.include?(key) case key when String complex_property_keys(lazy:).include?(key) || properties(lazy:).to_h.key?(key) when *AudioTag.members tag(lazy:).to_h.keys.include?(key) when *AudioProperties.members !!audio_properties else false end end |
#keys(lazy: !closed?)) ⇒ Array<String,Symbol>
If the file is open this will lazily retrieve all necessary data from TagLib, otherwise only data retrieved before the file was closed will be available.
Returns the list of available property keys.
367 368 369 370 371 372 373 374 375 |
# File 'lib/taglib_simple/media_file.rb', line 367 def keys(lazy: !closed?) [ @mutated.keys, audio_properties.to_h.keys, tag(lazy:).to_h.keys, properties(lazy:).to_h.keys, complex_property_keys(lazy:) ].compact.flatten.uniq end |
#modifications ⇒ Object
return [Hash
478 479 480 |
# File 'lib/taglib_simple/media_file.rb', line 478 def modifications @mutated.dup.freeze end |
#modified? ⇒ Boolean
Does not check if the values being set are different to their originals, only that something has been set
Returns if any properties been updated and not yet saved.
484 485 486 |
# File 'lib/taglib_simple/media_file.rb', line 484 def modified? @mutated.any? end |
#retrieve(all: false, tag: all, properties: all, complex_property_keys: (all && :all) || nil) ⇒ self
Retrieve and cache specific property types rom TagLib
Properties will be lazily loaded as long as the file is open so calling this method is generally not required.
Typically called from ##initialize but can be invoked directly, eg to force reload of data after #save!.
133 134 135 136 137 138 139 140 |
# File 'lib/taglib_simple/media_file.rb', line 133 def retrieve(all: false, tag: all, properties: all, complex_property_keys: (all && :all) || nil) self.properties if properties self.tag if tag retrieve_complex_property_keys(complex_property_keys) && fill_complex_properties self end |
#save!(replace_all: false) ⇒ self
all cached data is reset after saving. See #retrieve
Save accumulated property changes back to the file.
501 502 503 504 505 506 507 508 509 |
# File 'lib/taglib_simple/media_file.rb', line 501 def save!(replace_all: false) # raise error even if nothing written - you shouldn't be making this call raise IOError, 'cannot save, stream not writable' unless writable? update(replace_all) @fr.save reset self end |
#writable? ⇒ Boolean
Returns true if the file is open and writable.
150 151 152 |
# File 'lib/taglib_simple/media_file.rb', line 150 def writable? !closed? && !read_only? end |