loading
Generated 2025-09-25T07:34:13+00:00

All Files ( 90.16% covered at 6.35 hits/line )

5 files in total.
183 relevant lines, 165 lines covered and 18 lines missed. ( 90.16% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
lib/discid.rb 96.77 % 202 31 30 1 9.90
lib/discid/disc.rb 94.74 % 225 76 72 4 6.36
lib/discid/disc_error.rb 100.00 % 21 2 2 0 1.00
lib/discid/lib.rb 73.47 % 132 49 36 13 1.35
lib/discid/track.rb 100.00 % 135 25 25 0 12.16

lib/discid.rb

96.77% lines covered

31 relevant lines. 30 lines covered and 1 lines missed.
    
  1. # Copyright (C) 2013-2017, 2019, 2025 Philipp Wolfer
  2. #
  3. # This program is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU Lesser General Public License as published by
  5. # the Free Software Foundation, either version 3 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU Lesser General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU Lesser General Public License
  14. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. 1 require 'discid/lib'
  16. 1 require 'discid/disc'
  17. 1 require 'discid/version'
  18. # The DiscId module allows calculating DiscIDs (MusicBrainz and freedb)
  19. # for Audio CDs. Additionally the library can extract the MCN/UPC/EAN and
  20. # the ISRCs from disc.
  21. #
  22. # The main interface for using this module are the {read} and {put}
  23. # methods which both return an instance of {Disc}. {read} allows you to
  24. # read the data from an actual CD drive while {put} allows you to
  25. # calculate the DiscID for a previously read CD TOC.
  26. #
  27. # Depending on the version of libdiscid and the operating system used
  28. # additional features like reading the MCN or ISRCs from the disc
  29. # might be available. You can check for supported features with {feature?}.
  30. #
  31. # @see http://musicbrainz.org/doc/libdiscid#Feature_Matrix
  32. #
  33. # @example Read the TOC, MCN and ISRCs
  34. # require 'discid'
  35. #
  36. # device = "/dev/cdrom"
  37. # disc = DiscId.read(device, :mcn, :isrc)
  38. #
  39. # # Print information about the disc:
  40. # puts "DiscID : #{disc.id}"
  41. # puts "FreeDB ID : #{disc.freedb_id}"
  42. # puts "Total length: #{disc.seconds} seconds"
  43. # puts "MCN : #{disc.mcn}"
  44. #
  45. # # Print information about individual tracks:
  46. # disc.tracks do |track|
  47. # puts "Track ##{track.number}"
  48. # puts " Length: %02d:%02d (%i sectors)" %
  49. # [track.seconds / 60, track.seconds % 60, track.sectors]
  50. # puts " ISRC : %s" % track.isrc
  51. # end
  52. #
  53. # @example Get the DiscID for an existing TOC
  54. # require 'discid'
  55. #
  56. # first_track = 1
  57. # sectors = 82255
  58. # offsets = [150, 16157, 35932, 57527]
  59. # disc = DiscId.put(first_track, sectors, offsets)
  60. # puts disc.id # Output: E5VLOkhodzhvsMlK8LSNVioYOgY-
  61. #
  62. # @example Check for supported MCN feature
  63. # disc = DiscId.read(nil, :mcn)
  64. # puts "MCN: #{disc.mcn}" if DiscId.feature?(:mcn)
  65. 1 module DiscId
  66. # Read the disc in the given CD-ROM/DVD-ROM drive extracting only the
  67. # TOC and additionally specified features.
  68. #
  69. # This function reads the disc in the drive specified by the given device
  70. # identifier. If the device is `nil`, the default device, as returned by
  71. # {default_device}, is used.
  72. #
  73. # This function will always read the TOC, but additional features like `:mcn`
  74. # and `:isrc` can be set using the features parameter. You can set multiple
  75. # features.
  76. #
  77. # @example Read only the TOC:
  78. # disc = DiscId.read(device)
  79. #
  80. # @example Read the TOC, MCN and ISRCs:
  81. # disc = DiscId.read(device, :mcn, :isrc)
  82. #
  83. # @note libdiscid >= 0.5.0 is required for the feature selection to work.
  84. # Older versions will allways read MCN and ISRCs when supported. See
  85. # {http://musicbrainz.org/doc/libdiscid#Feature_Matrix} for a list of
  86. # supported features by version and platform.
  87. #
  88. # @raise [TypeError] `device` can not be converted to a String.
  89. # @raise [DiscError] Error reading from `device`. `Exception#message` contains
  90. # error details.
  91. # @param device [String] The device identifier. If set to `nil` {default_device}
  92. # will be used.
  93. # @param features [:mcn, :isrc] List of features to use.
  94. # `:read` is always implied.
  95. # @return [Disc]
  96. 1 def self.read(device = nil, *features)
  97. 3 disc = Disc.new
  98. 3 disc.read device, *features
  99. disc
  100. end
  101. # Provides the TOC of a known CD.
  102. #
  103. # This function may be used if the TOC has been read earlier and you want to
  104. # calculate the disc ID afterwards, without accessing the disc drive.
  105. #
  106. # @raise [DiscError] The TOC could not be set. `Exception#message`contains
  107. # error details.
  108. # @param first_track [Integer] The number of the first audio track on the
  109. # disc (usually one).
  110. # @param sectors [Integer] The total number of sectors on the disc.
  111. # @param offsets [Array] An array with track offsets (sectors) for each track.
  112. # @return [Disc]
  113. 1 def self.put(first_track, sectors, offsets)
  114. 10 disc = Disc.new
  115. 10 disc.put first_track, sectors, offsets
  116. 7 disc
  117. end
  118. # Parses a TOC string and returns a [Disc] instance for it.
  119. #
  120. # This function can be used if you already have a TOC string like e.g.
  121. # `1 11 242457 150 44942 61305 72755 96360 130485 147315 164275 190702 205412 220437`
  122. #
  123. # @raise [DiscError] The TOC string was invalid and could not be parsed.
  124. # @param toc [String] A TOC string in the format `1 11 242457 150 44942 61305 72755 96360 130485 147315 164275 190702 205412 220437`.
  125. # @return [Disc]
  126. 1 def self.parse(toc)
  127. 8 raise DiscError, "Invalid TOC #{toc.inspect}" if toc.nil? || toc.empty?
  128. begin
  129. 6 parts = toc.split(' ')
  130. 6 first_track = Integer(parts[0])
  131. 6 sectors = Integer(parts[2])
  132. 29 offsets = parts[3..-1].map{|i| Integer(i)}
  133. rescue ArgumentError, TypeError => e
  134. 2 raise DiscError, e
  135. end
  136. 4 put(first_track, sectors, offsets)
  137. end
  138. # Return the name of the default disc drive for this operating system.
  139. #
  140. # @return [String] An operating system dependent device identifier
  141. 1 def self.default_device
  142. 1 Lib.default_device
  143. end
  144. # Check if a certain feature is implemented on the current platform.
  145. #
  146. # You can obtain a list of supported features with {feature_list}.
  147. #
  148. # @note libdiscid >= 0.5.0 required. Older versions will return `true`
  149. # for `:read` and `false` for anything else.
  150. #
  151. # @param feature [:read, :mcn, :isrc]
  152. # @return [Boolean] True if the feature is implemented and false if not.
  153. 1 def self.feature?(feature)
  154. 8 feature = feature.to_sym if feature.respond_to? :to_sym
  155. 8 feature_list.include? feature
  156. end
  157. # Provide has_feature? for backward compatibility
  158. 1 singleton_class.alias_method :has_feature?, :feature?
  159. # A list of features supported by the current platform.
  160. #
  161. # Currently the following features are available:
  162. #
  163. # * :read
  164. # * :mcn
  165. # * :isrc
  166. #
  167. # @note libdiscid >= 0.5.0 required. Older versions will return only [:read].
  168. #
  169. # @return [Array<Symbol>]
  170. 1 def self.feature_list
  171. 36 Lib::Features.symbols.select {|f| Lib.has_feature(f) == 1}
  172. end
  173. # Converts sectors to seconds.
  174. #
  175. # According to the red book standard 75 sectors are one second.
  176. #
  177. # @private
  178. # @param sectors [Integer] Number of sectors
  179. # @return [Integer] The seconds
  180. 1 def self.sectors_to_seconds(sectors)
  181. 147 (sectors.to_f / 75).truncate
  182. end
  183. # The libdiscid version.
  184. #
  185. # @note This will only give meaningful results for libdiscid 0.4.0
  186. # and higher. For lower versions this constant will always have
  187. # the value "libdiscid < 0.4.0".
  188. 1 LIBDISCID_VERSION = Lib.get_version_string
  189. end

lib/discid/disc.rb

94.74% lines covered

76 relevant lines. 72 lines covered and 4 lines missed.
    
  1. # Copyright (C) 2013-2014, 2021, 2025 Philipp Wolfer
  2. #
  3. # This program is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU Lesser General Public License as published by
  5. # the Free Software Foundation, either version 3 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU Lesser General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU Lesser General Public License
  14. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. 1 require 'ffi'
  16. 1 require 'discid/disc_error'
  17. 1 require 'discid/lib'
  18. 1 require 'discid/track'
  19. #
  20. 1 module DiscId
  21. # This class holds information about a disc (TOC, MCN, ISRCs).
  22. #
  23. # Use {DiscId#read} or {DiscId#put} to initialize an instance of {Disc}.
  24. 1 class Disc
  25. # The device from which this disc object was read.
  26. 1 attr_reader :device
  27. # @private
  28. 1 def initialize
  29. 15 pointer = Lib.new
  30. 15 @handle = FFI::AutoPointer.new(pointer, Lib.method(:free))
  31. 15 @read = false
  32. 15 @tracks = nil
  33. end
  34. # @private
  35. 1 def read(device, *features)
  36. 3 @read = false
  37. 3 device = DiscId.default_device if device.nil?
  38. 3 unless device.respond_to? :to_s
  39. 1 raise TypeError, 'wrong argument type (expected String)'
  40. end
  41. 2 @device = device.to_s
  42. 2 flags = Lib.features_to_int features
  43. 2 result = Lib.read @handle, @device, flags
  44. 2 raise DiscError, Lib.get_error_msg(@handle) if result == 0
  45. @read = true
  46. end
  47. # @private
  48. 1 def put(first_track, sectors, offsets)
  49. 10 @read = false
  50. 10 @device = nil
  51. 10 last_track = first_track - 1 + offsets.length
  52. # discid_put expects always an offsets array with exactly 100 elements.
  53. 10 FFI::MemoryPointer.new(:int, 100) do |p|
  54. 10 if last_track > offsets.length
  55. 2 offsets = Array.new(last_track - offsets.length, 0) + offsets
  56. end
  57. 10 p.write_array_of_int(([sectors] + offsets)[0, 100])
  58. 10 result = Lib.put @handle, first_track, last_track, p
  59. 10 raise DiscError, Lib.get_error_msg(@handle) if result == 0
  60. 7 @read = true
  61. end
  62. end
  63. # The MusicBrainz DiscID.
  64. #
  65. # @return [String] The DiscID or `nil` if no ID was yet read.
  66. 1 def id
  67. 10 Lib.get_id @handle if @read
  68. end
  69. # The FreeDB DiscID.
  70. #
  71. # @return [String] The DiscID or `nil` if no ID was yet read.
  72. 1 def freedb_id
  73. 1 Lib.get_freedb_id @handle if @read
  74. end
  75. # The number of the first track on this disc.
  76. #
  77. # @return [Integer] The number of the first track or `nil` if no ID was yet read.
  78. 1 def first_track_number
  79. 7 Lib.get_first_track_num @handle if @read
  80. end
  81. # The number of the last track on this disc.
  82. #
  83. # @return [Integer] The number of the last track or `nil` if no ID was yet read.
  84. 1 def last_track_number
  85. 39 Lib.get_last_track_num @handle if @read
  86. end
  87. # The length of the disc in sectors.
  88. #
  89. # @return [Integer] Sectors or `nil` if no ID was yet read.
  90. 1 def sectors
  91. 4 Lib.get_sectors @handle if @read
  92. end
  93. # The length of the disc in seconds.
  94. #
  95. # @return [Integer] Seconds or `nil` if no ID was yet read.
  96. 1 def seconds
  97. 3 DiscId.sectors_to_seconds(sectors) if @read
  98. end
  99. # The media catalogue number on the disc, if present.
  100. #
  101. # Requires libdiscid >= 0.5. If not supported this method will always
  102. # return `nil`.
  103. #
  104. # @note libdiscid >= 0.3.0 required. Older versions will always return nil.
  105. # Not available on all platforms, see
  106. # {http://musicbrainz.org/doc/libdiscid#Feature_Matrix}.
  107. #
  108. # @return [String] MCN or `nil` if no ID was yet read.
  109. 1 def mcn
  110. 1 Lib.get_mcn @handle if @read
  111. end
  112. # Return a string representing CD Table Of Contents (TOC).
  113. #
  114. # The TOC suitable as a value of the toc parameter when accessing the
  115. # MusicBrainz Web Service. This enables fuzzy searching when the actual
  116. # DiscID is not found.
  117. #
  118. # Note that this is the unencoded value, which still contains spaces.
  119. #
  120. # For libdiscid >= 0.6 the native implementation will be used. For older
  121. # versions falls back to extract the TOC from {Disc#submission_url}.
  122. #
  123. # @since 1.3
  124. #
  125. # @raise [RuntimeError] get_toc_string is unsupported by libdiscid and
  126. # could not get extracted from {Disc#submission_url}
  127. # @return [String] The TOC string or `nil` if no ID was yet read.
  128. 1 def toc_string
  129. 4 return nil unless @read
  130. 4 result = Lib.get_toc_string @handle
  131. 4 unless result
  132. # probably an old version of libdiscid (< 0.6.0)
  133. # gather toc string from submission_url
  134. match = /toc=([0-9+]+)/.match submission_url
  135. raise "can't get toc string from submission url" unless match
  136. result = match[1].tr('+', ' ')
  137. end
  138. 4 result
  139. end
  140. # An URL for submitting the DiscID to MusicBrainz.
  141. #
  142. # The URL leads to an interactive disc submission wizard that guides the
  143. # user through the process of associating this disc's DiscID with a release
  144. # in the MusicBrainz database.
  145. #
  146. # @return [String] Submission URL
  147. 1 def submission_url
  148. 1 Lib.get_submission_url @handle if @read
  149. end
  150. # DiscID to String conversion. Same as calling the method {#id} but guaranteed
  151. # to return a string.
  152. #
  153. # @return [String] The disc ID as a string or an empty string if no ID
  154. # was yet read.
  155. 1 def to_s
  156. 3 id.to_s
  157. end
  158. # Returns an array of {Track} objects. Each Track object contains
  159. # detailed information about the track.
  160. #
  161. # Returns always `nil` if no ID was yet read. The block won't be
  162. # called in this case.
  163. #
  164. # @yield [Track] If a block is given this method returns `nil` and
  165. # instead iterates over the block calling the block with one argument.
  166. # @yieldreturn [nil]
  167. # @return [Array<Track>] Array of {Track} objects.
  168. 1 def tracks(&block)
  169. 7 return unless @read
  170. 5 read_tracks if @tracks.nil?
  171. 5 return @tracks unless block
  172. 1 @tracks.each(&block)
  173. 1 nil
  174. end
  175. 1 private
  176. 1 def read_tracks
  177. 3 track_number = first_track_number - 1
  178. 3 @tracks = []
  179. 3 while track_number < last_track_number
  180. 30 track_number += 1
  181. 30 isrc = Lib.get_track_isrc(@handle, track_number)
  182. 30 offset = Lib.get_track_offset(@handle, track_number)
  183. 30 length = Lib.get_track_length(@handle, track_number)
  184. 30 track_info = Track.new(track_number, offset, length, isrc)
  185. 30 @tracks << track_info
  186. end
  187. end
  188. end
  189. end

lib/discid/disc_error.rb

100.0% lines covered

2 relevant lines. 2 lines covered and 0 lines missed.
    
  1. # Copyright (C) 2013-2014 Philipp Wolfer
  2. #
  3. # This program is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU Lesser General Public License as published by
  5. # the Free Software Foundation, either version 3 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU Lesser General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU Lesser General Public License
  14. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. #
  16. 1 module DiscId
  17. # This exception is thrown on errors reading the disc or setting the TOC.
  18. 1 class DiscError < StandardError
  19. end
  20. end

lib/discid/lib.rb

73.47% lines covered

49 relevant lines. 36 lines covered and 13 lines missed.
    
  1. # Copyright (C) 2013, 2025 Philipp Wolfer
  2. #
  3. # This program is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU Lesser General Public License as published by
  5. # the Free Software Foundation, either version 3 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU Lesser General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU Lesser General Public License
  14. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. 1 require "ffi"
  16. #
  17. 1 module DiscId
  18. # This module encapsulates the C interface for libdiscid using FFI.
  19. # The Lib module is intended for internal use only and should be
  20. # considered private.
  21. #
  22. # @private
  23. 1 module Lib
  24. 1 extend FFI::Library
  25. 1 ffi_lib %w[discid libdiscid.so.0 libdiscid.0.dylib]
  26. 1 attach_function :new, :discid_new, [], :pointer
  27. 1 attach_function :free, :discid_free, [:pointer], :void
  28. begin
  29. 1 attach_function :read, :discid_read_sparse, %i[pointer string uint], :int
  30. rescue FFI::NotFoundError
  31. attach_function :legacy_read, :discid_read, %i[pointer string], :int
  32. def self.read(handle, device, _features)
  33. legacy_read(handle, device)
  34. end
  35. end
  36. 1 attach_function :put, :discid_put, %i[pointer int int pointer], :int
  37. 1 attach_function :get_error_msg, :discid_get_error_msg, [:pointer], :string
  38. 1 attach_function :get_id, :discid_get_id, [:pointer], :string
  39. 1 attach_function :get_freedb_id, :discid_get_freedb_id, [:pointer], :string
  40. 1 attach_function :get_submission_url, :discid_get_submission_url, [:pointer], :string
  41. 1 attach_function :default_device, :discid_get_default_device, [], :string
  42. 1 attach_function :get_first_track_num, :discid_get_first_track_num, [:pointer], :int
  43. 1 attach_function :get_last_track_num, :discid_get_last_track_num, [:pointer], :int
  44. 1 attach_function :get_sectors, :discid_get_sectors, [:pointer], :int
  45. 1 attach_function :get_track_offset, :discid_get_track_offset, %i[pointer int], :int
  46. 1 attach_function :get_track_length, :discid_get_track_length, %i[pointer int], :int
  47. begin
  48. 1 attach_function :get_toc_string, :discid_get_toc_string, [:pointer], :string
  49. rescue FFI::NotFoundError
  50. def self.get_toc_string
  51. nil
  52. end
  53. end
  54. begin
  55. 1 attach_function :get_mcn, :discid_get_mcn, [:pointer], :string
  56. rescue FFI::NotFoundError
  57. def self.get_mcn(_handle)
  58. nil
  59. end
  60. end
  61. begin
  62. 1 attach_function :get_track_isrc, :discid_get_track_isrc, %i[pointer int], :string
  63. rescue FFI::NotFoundError
  64. def self.get_track_isrc(_handle, _track)
  65. nil
  66. end
  67. end
  68. 1 Features = enum(:feature, [:read, 1 << 0,
  69. :mcn, 1 << 1,
  70. :isrc, 1 << 2])
  71. begin
  72. 1 attach_function :has_feature, :discid_has_feature, [:feature], :int
  73. rescue FFI::NotFoundError
  74. def self.has_feature(feature)
  75. feature.to_sym == :read ? 1 : 0
  76. end
  77. end
  78. # attach_function :get_feature_list, :discid_get_feature_list, [:pointer], :void
  79. begin
  80. 1 attach_function :get_version_string, :discid_get_version_string, [], :string
  81. rescue FFI::NotFoundError
  82. def self.get_version_string
  83. 'libdiscid < 0.4.0'
  84. end
  85. end
  86. 1 def self.features_to_int(features)
  87. 6 feature_flags = 0
  88. 6 features.each do |feature|
  89. 4 if feature.respond_to? :to_sym
  90. 4 feature = feature.to_sym
  91. 4 feature_flags = add_feature_to_flags(feature_flags, feature)
  92. end
  93. end
  94. 6 feature_flags
  95. end
  96. 1 def self.add_feature_to_flags(flags, feature)
  97. flags |= self::Features[feature] if
  98. 4 self::Features.symbols.include?(feature) && has_feature(feature)
  99. 4 flags
  100. end
  101. 1 private_class_method :add_feature_to_flags
  102. end
  103. end

lib/discid/track.rb

100.0% lines covered

25 relevant lines. 25 lines covered and 0 lines missed.
    
  1. # Copyright (C) 2008-2023, 2025 Philipp Wolfer
  2. #
  3. # This program is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU Lesser General Public License as published by
  5. # the Free Software Foundation, either version 3 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU Lesser General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU Lesser General Public License
  14. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. #
  16. 1 module DiscId
  17. # This class holds information about a single track.
  18. #
  19. # Currently this includes the following fields:
  20. #
  21. # * number: The number of the track on the disc.
  22. # * sectors: Length of the track in sectors.
  23. # * offset: Start position of the track on the disc in sectors.
  24. # * end_sector: End position of the track on the disc in sectors.
  25. # * seconds: Length of the track in seconds.
  26. # * start_time: Start position of the track on the disc in seconds.
  27. # * end_time: End position of the track on the disc in seconds.
  28. # * isrc: The track's ISRC (International Standard Recordings Code)
  29. # if available.
  30. #
  31. # You can access all fields either directly or with the square bracket
  32. # notation:
  33. #
  34. # track = Track.new(1, 150, 16007)
  35. # puts track.sectors # 16007
  36. # puts track[:sectors] # 16007
  37. #
  38. # @see DiscId::Disc#tracks
  39. 1 class Track
  40. # The number of the track on the disc.
  41. #
  42. # @return [Integer]
  43. 1 attr_reader :number
  44. # Length of the track in sectors.
  45. #
  46. # @return [Integer]
  47. 1 attr_reader :sectors
  48. # Start position of the track on the disc in sectors.
  49. #
  50. # @return [Integer]
  51. 1 attr_reader :offset
  52. # ISRC number of the track.
  53. #
  54. # @note libdiscid >= 0.3.0 required. Older versions will always return nil.
  55. # Not available on all platforms, see
  56. # {http://musicbrainz.org/doc/libdiscid#Feature_Matrix}.
  57. #
  58. # @return [String]
  59. 1 attr_reader :isrc
  60. # Initializes a new Track object.
  61. 1 def initialize(number, offset, sectors, isrc)
  62. 34 @number = number
  63. 34 @offset = offset
  64. 34 @sectors = sectors
  65. 34 @isrc = isrc
  66. end
  67. # End position of the track on the disc in sectors.
  68. #
  69. # @return [Integer]
  70. 1 def end_sector
  71. 54 offset + sectors
  72. end
  73. # Length of the track in seconds.
  74. #
  75. # @return [Integer]
  76. 1 def seconds
  77. 27 DiscId.sectors_to_seconds(sectors)
  78. end
  79. # Start position of the track on the disc in seconds.
  80. #
  81. # @return [Integer]
  82. 1 def start_time
  83. 27 DiscId.sectors_to_seconds(offset)
  84. end
  85. # End position of the track on the disc in seconds.
  86. #
  87. # @return [Integer]
  88. 1 def end_time
  89. 27 DiscId.sectors_to_seconds(end_sector)
  90. end
  91. # Allows access to all fields similar to accessing values in a hash.
  92. #
  93. # Example:
  94. #
  95. # track = Track.new(1, 150, 16007)
  96. # puts track.sectors # 16007
  97. # puts track[:sectors] # 16007
  98. 1 def [](key)
  99. 9 if %i[number sectors offset end_sector
  100. seconds start_time end_time isrc].include?(key.to_sym)
  101. 8 method(key).call
  102. end
  103. end
  104. # Converts the Track into a Hash.
  105. #
  106. # @return [Hash]
  107. 1 def to_h
  108. {
  109. 2 number: number,
  110. sectors: sectors,
  111. offset: offset,
  112. end_sector: end_sector,
  113. seconds: seconds,
  114. start_time: start_time,
  115. end_time: end_time,
  116. isrc: isrc
  117. }
  118. end
  119. 1 alias to_hash to_h
  120. end
  121. end