audio_tag 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/.rubocop.yml +78 -0
- data/.ruby-version +1 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +127 -0
- data/LICENSE.txt +21 -0
- data/README.md +38 -0
- data/Rakefile +10 -0
- data/Steepfile +12 -0
- data/audio_tag.gemspec +24 -0
- data/lib/audio_tag/id3/header.rb +36 -0
- data/lib/audio_tag/id3/stream_reader.rb +98 -0
- data/lib/audio_tag/id3/stream_section.rb +57 -0
- data/lib/audio_tag/id3/synchsafe.rb +19 -0
- data/lib/audio_tag/id3/v2/frame.rb +43 -0
- data/lib/audio_tag/id3/v2/frame_header.rb +35 -0
- data/lib/audio_tag/id3/v2/frame_merger.rb +30 -0
- data/lib/audio_tag/id3/v2/frame_type.rb +81 -0
- data/lib/audio_tag/id3/v2/frames/comment_frame.rb +32 -0
- data/lib/audio_tag/id3/v2/frames/picture_frame.rb +63 -0
- data/lib/audio_tag/id3/v2/frames/user_frame.rb +29 -0
- data/lib/audio_tag/id3/v2/tag.rb +39 -0
- data/lib/audio_tag/id3/v2/tag_header.rb +71 -0
- data/lib/audio_tag/id3/v2/text_decoder.rb +33 -0
- data/lib/audio_tag/version.rb +3 -0
- data/lib/audio_tag.rb +7 -0
- data/sig/audio_tag/id3/header.rbs +17 -0
- data/sig/audio_tag/id3/stream_reader.rbs +33 -0
- data/sig/audio_tag/id3/stream_section.rbs +16 -0
- data/sig/audio_tag/id3/synchsafe.rbs +12 -0
- data/sig/audio_tag/id3/v2/frame.rbs +24 -0
- data/sig/audio_tag/id3/v2/frame_header.rbs +18 -0
- data/sig/audio_tag/id3/v2/frame_merger.rbs +20 -0
- data/sig/audio_tag/id3/v2/frame_type.rbs +22 -0
- data/sig/audio_tag/id3/v2/frames/comment_frame.rbs +23 -0
- data/sig/audio_tag/id3/v2/frames/picture_frame.rbs +27 -0
- data/sig/audio_tag/id3/v2/frames/user_frame.rbs +22 -0
- data/sig/audio_tag/id3/v2/tag.rbs +20 -0
- data/sig/audio_tag/id3/v2/tag_header.rbs +36 -0
- data/sig/audio_tag/id3/v2/text_decoder.rbs +13 -0
- data/sig/audio_tag.rbs +5 -0
- data/sig/zeitwerk/gem_inflector.rbs +7 -0
- data/sig/zeitwerk/loader.rbs +13 -0
- metadata +100 -0
@@ -0,0 +1,81 @@
|
|
1
|
+
module AudioTag
|
2
|
+
module ID3
|
3
|
+
module V2
|
4
|
+
class FrameType
|
5
|
+
LOOKUP = {
|
6
|
+
# 2.3.0
|
7
|
+
APIC: { name: :picture, class: Frames::PictureFrame },
|
8
|
+
COMM: { name: :comments, class: Frames::CommentFrame },
|
9
|
+
TALB: { name: :album },
|
10
|
+
TBPM: { name: :tempo },
|
11
|
+
TCOM: { name: :composer },
|
12
|
+
TCON: { name: :genre },
|
13
|
+
TCOP: { name: :copyright },
|
14
|
+
TPE1: { name: :artist },
|
15
|
+
TKEY: { name: :key },
|
16
|
+
TXXX: { name: :user_frames, class: Frames::UserFrame },
|
17
|
+
TDAT: { name: :date },
|
18
|
+
TDLY: { name: :playlist_delay },
|
19
|
+
TENC: { name: :encoded_by },
|
20
|
+
TEXT: { name: :text },
|
21
|
+
TFLT: { name: :custom },
|
22
|
+
TIME: { name: :time },
|
23
|
+
TIT1: { name: :content_group_description },
|
24
|
+
TIT2: { name: :title },
|
25
|
+
TIT3: { name: :subtitle_description_refinement },
|
26
|
+
TLAN: { name: :language },
|
27
|
+
TLEN: { name: :length },
|
28
|
+
TMED: { name: :custom },
|
29
|
+
TOAL: { name: :original_album },
|
30
|
+
TOFN: { name: :original_filename },
|
31
|
+
TOLY: { name: :original_writer },
|
32
|
+
TOPE: { name: :original_artist },
|
33
|
+
TOWN: { name: :file_owner },
|
34
|
+
TPE2: { name: :album_artist },
|
35
|
+
TPE3: { name: :conductor },
|
36
|
+
TPE4: { name: :other_artist },
|
37
|
+
TPOS: { name: :part_in_set },
|
38
|
+
TPUB: { name: :label },
|
39
|
+
TRCK: { name: :track },
|
40
|
+
TRDA: { name: :recording_date },
|
41
|
+
TRSN: { name: :radio_station_name },
|
42
|
+
TRSO: { name: :radio_station_owner },
|
43
|
+
TSIZ: { name: :size },
|
44
|
+
TSRC: { name: :isrc },
|
45
|
+
TSSE: { name: :encoder },
|
46
|
+
|
47
|
+
# 2.4.0
|
48
|
+
TDEN: { name: :encoded_at },
|
49
|
+
TDOR: { name: :originally_released_at },
|
50
|
+
TDRC: { name: :date },
|
51
|
+
TDRL: { name: :released_at },
|
52
|
+
TDTG: { name: :tagged_at },
|
53
|
+
TIPL: { name: :involved_people },
|
54
|
+
TMCL: { name: :musician_credits },
|
55
|
+
TMOO: { name: :mood },
|
56
|
+
TPRO: { name: :produced_notice },
|
57
|
+
TSOA: { name: :album_sort },
|
58
|
+
TSOP: { name: :performer_sort },
|
59
|
+
TSOT: { name: :title_sort },
|
60
|
+
TSST: { name: :set_subtitle },
|
61
|
+
TYER: { name: :year }
|
62
|
+
}.freeze
|
63
|
+
|
64
|
+
DEFAULT_FRAME_CLASS = Frame
|
65
|
+
|
66
|
+
attr_reader :name, :frame_class
|
67
|
+
|
68
|
+
def initialize(**attributes)
|
69
|
+
@name = attributes[:name]
|
70
|
+
@frame_class = attributes[:class] || DEFAULT_FRAME_CLASS
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.find(key)
|
74
|
+
attributes = LOOKUP[key] || { name: key }
|
75
|
+
|
76
|
+
new(**attributes)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module AudioTag
|
2
|
+
module ID3
|
3
|
+
module V2
|
4
|
+
module Frames
|
5
|
+
class CommentFrame < Frame
|
6
|
+
def data
|
7
|
+
{ language => { description => text } }
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
attr_reader :encoding, :text
|
13
|
+
|
14
|
+
def description
|
15
|
+
@description.empty? ? :_ : @description.to_sym
|
16
|
+
end
|
17
|
+
|
18
|
+
def language
|
19
|
+
@language.to_sym
|
20
|
+
end
|
21
|
+
|
22
|
+
def extract!
|
23
|
+
@encoding = read_byte!
|
24
|
+
@language = read!(3)
|
25
|
+
@description = read_until!(0x00)
|
26
|
+
@text = read_until!(0x00)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module AudioTag
|
2
|
+
module ID3
|
3
|
+
module V2
|
4
|
+
module Frames
|
5
|
+
class PictureFrame < Frame
|
6
|
+
ENCODINGS = %i[
|
7
|
+
ascii
|
8
|
+
utf_16
|
9
|
+
utf_16be
|
10
|
+
utf_8
|
11
|
+
].freeze
|
12
|
+
|
13
|
+
TYPES = %i[
|
14
|
+
other
|
15
|
+
thumbnail
|
16
|
+
other_thumbnail
|
17
|
+
front_cover
|
18
|
+
back_cover
|
19
|
+
leaflet_page
|
20
|
+
media_page
|
21
|
+
lead_artist
|
22
|
+
artist
|
23
|
+
conductor
|
24
|
+
band
|
25
|
+
composer
|
26
|
+
lyricist
|
27
|
+
recording_location
|
28
|
+
during_recording
|
29
|
+
during_performance
|
30
|
+
movie_capture
|
31
|
+
bright_colored_fish
|
32
|
+
illustration
|
33
|
+
band_logo
|
34
|
+
publisher_logo
|
35
|
+
].freeze
|
36
|
+
|
37
|
+
def data
|
38
|
+
{ encoding:, mime:, type:, description: }
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
attr_reader :mime, :description
|
44
|
+
|
45
|
+
def encoding
|
46
|
+
ENCODINGS[@encoding]
|
47
|
+
end
|
48
|
+
|
49
|
+
def type
|
50
|
+
TYPES[@type]
|
51
|
+
end
|
52
|
+
|
53
|
+
def extract!
|
54
|
+
@encoding = read_byte!
|
55
|
+
@mime = read_until!(0x00)
|
56
|
+
@type = read_byte!
|
57
|
+
@description = read_until!(0x00)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module AudioTag
|
2
|
+
module ID3
|
3
|
+
module V2
|
4
|
+
module Frames
|
5
|
+
class UserFrame < Frame
|
6
|
+
DESCRIPTIONS = {}.freeze
|
7
|
+
|
8
|
+
def data
|
9
|
+
{ description => value }
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
attr_reader :encoding, :value
|
15
|
+
|
16
|
+
def description
|
17
|
+
DESCRIPTIONS[@description.to_sym] || @description.downcase.to_sym
|
18
|
+
end
|
19
|
+
|
20
|
+
def extract!
|
21
|
+
@encoding = read_byte!
|
22
|
+
@description = read_until!(0x00)
|
23
|
+
@value = read_until!(0x00)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module AudioTag
|
2
|
+
module ID3
|
3
|
+
module V2
|
4
|
+
class Tag < StreamSection
|
5
|
+
attr_reader :header, :frames
|
6
|
+
|
7
|
+
def initialize(stream)
|
8
|
+
@header = TagHeader.new(stream)
|
9
|
+
|
10
|
+
super(stream)
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_h
|
14
|
+
FrameMerger.new(frames).merge
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.from_file(path)
|
18
|
+
File.open(path) { |file| new(file) }
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def end_of_tag?
|
24
|
+
stream.pos >= last_pos || read_byte.zero?
|
25
|
+
end
|
26
|
+
|
27
|
+
def size
|
28
|
+
header.tag_size
|
29
|
+
end
|
30
|
+
|
31
|
+
def extract!
|
32
|
+
@frames = [].tap do |frames|
|
33
|
+
frames << Frame.build(stream) until end_of_tag?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module AudioTag
|
2
|
+
module ID3
|
3
|
+
module V2
|
4
|
+
class TagHeader < Header
|
5
|
+
STRUCTURE = %i[
|
6
|
+
identifier identifier identifier
|
7
|
+
version
|
8
|
+
revision
|
9
|
+
flags
|
10
|
+
size size size size
|
11
|
+
].freeze
|
12
|
+
|
13
|
+
IDENTIFIER = "ID3".freeze
|
14
|
+
|
15
|
+
INVALID_FLAG_MASK = 0x8F
|
16
|
+
EXPERIMENTAL_MASK = 0x10
|
17
|
+
EXTENDED_MASK = 0x20
|
18
|
+
UNSYNCHRONISED_MASK = 0x40
|
19
|
+
|
20
|
+
def initialize(stream)
|
21
|
+
super
|
22
|
+
|
23
|
+
validate_identifier!
|
24
|
+
validate_flags!
|
25
|
+
end
|
26
|
+
|
27
|
+
def experimental?
|
28
|
+
(flags & EXPERIMENTAL_MASK).positive?
|
29
|
+
end
|
30
|
+
|
31
|
+
def extended?
|
32
|
+
(flags & EXTENDED_MASK).positive?
|
33
|
+
end
|
34
|
+
|
35
|
+
def unsynchronised?
|
36
|
+
(flags & UNSYNCHRONISED_MASK).positive?
|
37
|
+
end
|
38
|
+
|
39
|
+
def tag_size
|
40
|
+
@tag_size ||= Synchsafe.parse(*bytes_for(:size))
|
41
|
+
end
|
42
|
+
|
43
|
+
def total_size
|
44
|
+
@total_size ||= tag_size + size
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def identifier
|
50
|
+
@identifier ||= bytes_for(:identifier).pack("c*")
|
51
|
+
end
|
52
|
+
|
53
|
+
def flags
|
54
|
+
@flags ||= bytes_for(:flags).first || raise
|
55
|
+
end
|
56
|
+
|
57
|
+
def validate_identifier!
|
58
|
+
return if identifier == IDENTIFIER
|
59
|
+
|
60
|
+
raise "invalid ID3 tag identifier: #{identifier}"
|
61
|
+
end
|
62
|
+
|
63
|
+
def validate_flags!
|
64
|
+
return if (flags & INVALID_FLAG_MASK).zero?
|
65
|
+
|
66
|
+
raise "invalid flags set"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module AudioTag
|
2
|
+
module ID3
|
3
|
+
module V2
|
4
|
+
class TextDecoder
|
5
|
+
ENCODINGS = {
|
6
|
+
ascii: /^\x00(.*?)(\x00)?$/,
|
7
|
+
utf_16: /^\x01(.*?)(\x00\x00)?$/,
|
8
|
+
utf_16be: /^\x02(.*?)(\x00\x00)?$/,
|
9
|
+
utf_8: /^\x03(.*?)(\x00)?$/
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
def self.encoding(string)
|
13
|
+
case string
|
14
|
+
when ENCODINGS[:ascii] then :ascii
|
15
|
+
when ENCODINGS[:utf_16] then :utf_16
|
16
|
+
when ENCODINGS[:utf_16be] then :utf_16be
|
17
|
+
when ENCODINGS[:utf_8] then :utf_8
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.decode(string)
|
22
|
+
case encoding(string)
|
23
|
+
when :ascii then string.gsub(ENCODINGS[:ascii], "\\1")
|
24
|
+
when :utf_16 then string.gsub(ENCODINGS[:utf_16], "\\1")
|
25
|
+
when :utf_16be then string.gsub(ENCODINGS[:utf_16be], "\\1")
|
26
|
+
when :utf_8 then string.gsub(ENCODINGS[:utf_8], "\\1")
|
27
|
+
else string
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/audio_tag.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module AudioTag
|
2
|
+
module ID3
|
3
|
+
class Header < StreamSection
|
4
|
+
attr_reader bytes: bytes
|
5
|
+
|
6
|
+
def bytes_for: (Symbol) -> bytes
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
UNEXTENDED_SIZE: Integer
|
11
|
+
|
12
|
+
def structure: -> Array[Symbol]
|
13
|
+
|
14
|
+
def structure_defined?: -> bool
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module AudioTag
|
2
|
+
module ID3
|
3
|
+
class StreamReader
|
4
|
+
attr_reader stream: IO
|
5
|
+
|
6
|
+
def initialize: (IO) -> void
|
7
|
+
|
8
|
+
def read!: (Integer) -> String
|
9
|
+
|
10
|
+
def read: (Integer) -> String
|
11
|
+
|
12
|
+
def read_bytes!: (Integer) -> bytes
|
13
|
+
|
14
|
+
def read_bytes: (Integer) -> bytes
|
15
|
+
|
16
|
+
def read_byte!: -> Integer
|
17
|
+
|
18
|
+
def read_byte: -> Integer
|
19
|
+
|
20
|
+
def read_until!: (Integer, ?consume_delimiter: bool) -> String
|
21
|
+
|
22
|
+
def read_until: (Integer) -> String
|
23
|
+
|
24
|
+
def read_bytes_until!: (Integer, ?consume_delimiter: bool) -> bytes
|
25
|
+
|
26
|
+
def read_bytes_until: (Integer) -> bytes
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def end_of_stream?: -> bool
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module AudioTag
|
2
|
+
module ID3
|
3
|
+
module V2
|
4
|
+
class Frame < StreamSection
|
5
|
+
attr_reader header: FrameHeader
|
6
|
+
attr_reader raw_data: String
|
7
|
+
|
8
|
+
def initialize: (IO, ?header: FrameHeader) -> void
|
9
|
+
|
10
|
+
def key: -> Symbol
|
11
|
+
|
12
|
+
def data: -> untyped
|
13
|
+
|
14
|
+
def to_h: -> Hash[Symbol, untyped]
|
15
|
+
|
16
|
+
def self.build: (IO) -> Frame
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def size: -> Integer
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module AudioTag
|
2
|
+
module ID3
|
3
|
+
module V2
|
4
|
+
class FrameHeader < Header
|
5
|
+
STRUCTURE: Array[Symbol]
|
6
|
+
|
7
|
+
attr_reader id: Symbol
|
8
|
+
attr_reader frame_size: Integer
|
9
|
+
attr_reader key: Symbol
|
10
|
+
attr_reader frame_class: singleton(Frame)
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
attr_reader frame_type: FrameType
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module AudioTag
|
2
|
+
module ID3
|
3
|
+
module V2
|
4
|
+
class FrameMerger
|
5
|
+
|
6
|
+
type frames = Array[Frame]
|
7
|
+
|
8
|
+
attr_reader frames: frames
|
9
|
+
|
10
|
+
def initialize: (frames) -> void
|
11
|
+
|
12
|
+
def merge: -> Hash[Symbol, untyped]
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def pairs: -> Array[[Symbol, untyped]]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module AudioTag
|
2
|
+
module ID3
|
3
|
+
module V2
|
4
|
+
class FrameType
|
5
|
+
type frame_class = singleton(Frame)
|
6
|
+
|
7
|
+
type frame_attributes = Hash[Symbol, untyped]
|
8
|
+
|
9
|
+
LOOKUP: Hash[Symbol, frame_attributes]
|
10
|
+
|
11
|
+
DEFAULT_FRAME_CLASS: frame_class
|
12
|
+
|
13
|
+
attr_reader name: Symbol
|
14
|
+
attr_reader frame_class: frame_class
|
15
|
+
|
16
|
+
def initialize: (**untyped) -> void
|
17
|
+
|
18
|
+
def self.find: (Symbol) -> instance
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module AudioTag
|
2
|
+
module ID3
|
3
|
+
module V2
|
4
|
+
module Frames
|
5
|
+
class CommentFrame < Frame
|
6
|
+
def data: -> Hash[Symbol, Hash[Symbol, String]]
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
attr_reader encoding: Integer
|
11
|
+
attr_reader text: String
|
12
|
+
|
13
|
+
@description: String
|
14
|
+
@language: String
|
15
|
+
|
16
|
+
def description: -> Symbol
|
17
|
+
|
18
|
+
def language: -> Symbol
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module AudioTag
|
2
|
+
module ID3
|
3
|
+
module V2
|
4
|
+
module Frames
|
5
|
+
class PictureFrame < Frame
|
6
|
+
ENCODINGS: Array[Symbol]
|
7
|
+
|
8
|
+
TYPES: Array[Symbol]
|
9
|
+
|
10
|
+
def data: -> Hash[Symbol, untyped]
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
attr_reader mime: String
|
15
|
+
attr_reader description: String
|
16
|
+
|
17
|
+
@encoding: Integer
|
18
|
+
@type: Integer
|
19
|
+
|
20
|
+
def encoding: -> Symbol
|
21
|
+
|
22
|
+
def type: -> Symbol
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module AudioTag
|
2
|
+
module ID3
|
3
|
+
module V2
|
4
|
+
module Frames
|
5
|
+
class UserFrame < Frame
|
6
|
+
DESCRIPTIONS: Hash[Symbol, Symbol]
|
7
|
+
|
8
|
+
def data: -> Hash[Symbol, untyped]
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
attr_reader encoding: Integer
|
13
|
+
attr_reader value: String
|
14
|
+
|
15
|
+
@description: String
|
16
|
+
|
17
|
+
def description: -> Symbol
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module AudioTag
|
2
|
+
module ID3
|
3
|
+
module V2
|
4
|
+
class Tag < StreamSection
|
5
|
+
attr_reader header: TagHeader
|
6
|
+
attr_reader frames: Array[Frame]
|
7
|
+
|
8
|
+
def initialize: (IO) -> void
|
9
|
+
|
10
|
+
def to_h: -> Hash[Symbol, untyped]
|
11
|
+
|
12
|
+
def self.from_file: (String) -> Tag
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def end_of_tag?: -> bool
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module AudioTag
|
2
|
+
module ID3
|
3
|
+
module V2
|
4
|
+
class TagHeader < Header
|
5
|
+
STRUCTURE: Array[Symbol]
|
6
|
+
|
7
|
+
IDENTIFIER: String
|
8
|
+
|
9
|
+
INVALID_FLAG_MASK: Integer
|
10
|
+
EXPERIMENTAL_MASK: Integer
|
11
|
+
EXTENDED_MASK: Integer
|
12
|
+
UNSYNCHRONISED_MASK: Integer
|
13
|
+
|
14
|
+
def experimental?: -> bool
|
15
|
+
|
16
|
+
def extended?: -> bool
|
17
|
+
|
18
|
+
def unsynchronised?: -> bool
|
19
|
+
|
20
|
+
attr_reader tag_size: Integer
|
21
|
+
|
22
|
+
attr_reader total_size: Integer
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader identifier: String
|
27
|
+
|
28
|
+
attr_reader flags: Integer
|
29
|
+
|
30
|
+
def validate_identifier!: -> void
|
31
|
+
|
32
|
+
def validate_flags!: -> void
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|