id3taginator 0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.idea/.gitignore +8 -0
  4. data/.idea/ID3Taginator.iml +73 -0
  5. data/.idea/misc.xml +4 -0
  6. data/.idea/modules.xml +8 -0
  7. data/.idea/vcs.xml +6 -0
  8. data/.rspec +3 -0
  9. data/.rubocop.yml +26 -0
  10. data/CHANGELOG.md +5 -0
  11. data/CODE_OF_CONDUCT.md +84 -0
  12. data/Gemfile +12 -0
  13. data/LICENSE.txt +21 -0
  14. data/README.md +408 -0
  15. data/Rakefile +12 -0
  16. data/bin/console +15 -0
  17. data/bin/setup +8 -0
  18. data/id3taginator.gemspec +37 -0
  19. data/lib/id3taginator/audio_file.rb +136 -0
  20. data/lib/id3taginator/errors/id3_tag_error.rb +12 -0
  21. data/lib/id3taginator/extensions/argument_check.rb +88 -0
  22. data/lib/id3taginator/extensions/comparable.rb +28 -0
  23. data/lib/id3taginator/extensions/encodable.rb +215 -0
  24. data/lib/id3taginator/extensions/optionable.rb +73 -0
  25. data/lib/id3taginator/frames/buffer/entities/buffer.rb +26 -0
  26. data/lib/id3taginator/frames/buffer/rbuf_recommended_buffer_size_frame.rb +59 -0
  27. data/lib/id3taginator/frames/buffer_frames.rb +32 -0
  28. data/lib/id3taginator/frames/comment/comm_comment_frame.rb +56 -0
  29. data/lib/id3taginator/frames/comment/entities/comment.rb +26 -0
  30. data/lib/id3taginator/frames/comment_frames.rb +42 -0
  31. data/lib/id3taginator/frames/count/entities/popularimeter.rb +26 -0
  32. data/lib/id3taginator/frames/count/pcnt_play_counter_frame.rb +40 -0
  33. data/lib/id3taginator/frames/count/popm_popularimeter_frame.rb +52 -0
  34. data/lib/id3taginator/frames/count_frames.rb +51 -0
  35. data/lib/id3taginator/frames/custom_frame.rb +39 -0
  36. data/lib/id3taginator/frames/custom_frames.rb +54 -0
  37. data/lib/id3taginator/frames/encryption/aenc_audio_encryption.rb +54 -0
  38. data/lib/id3taginator/frames/encryption/encr_encryption_method_frame.rb +49 -0
  39. data/lib/id3taginator/frames/encryption/entities/audio_encryption.rb +28 -0
  40. data/lib/id3taginator/frames/encryption/entities/encryption_method.rb +26 -0
  41. data/lib/id3taginator/frames/encryption_frames.rb +77 -0
  42. data/lib/id3taginator/frames/frameable.rb +75 -0
  43. data/lib/id3taginator/frames/geo/entities/encapsulated_object.rb +28 -0
  44. data/lib/id3taginator/frames/geo/geob_general_encapsulated_object_frame.rb +59 -0
  45. data/lib/id3taginator/frames/geo_frames.rb +41 -0
  46. data/lib/id3taginator/frames/grouping/entities/group_identification.rb +26 -0
  47. data/lib/id3taginator/frames/grouping/grid_group_identification_frame.rb +49 -0
  48. data/lib/id3taginator/frames/grouping/grp1_grouping_frame.rb +40 -0
  49. data/lib/id3taginator/frames/grouping_frames.rb +61 -0
  50. data/lib/id3taginator/frames/has_id.rb +51 -0
  51. data/lib/id3taginator/frames/id3v23_frame_flags.rb +64 -0
  52. data/lib/id3taginator/frames/id3v24_frame_flags.rb +80 -0
  53. data/lib/id3taginator/frames/id3v2_frame.rb +249 -0
  54. data/lib/id3taginator/frames/id3v2_frame_factory.rb +98 -0
  55. data/lib/id3taginator/frames/ipl/entities/involved_person.rb +24 -0
  56. data/lib/id3taginator/frames/ipl/ipls_involved_people_frame.rb +48 -0
  57. data/lib/id3taginator/frames/ipl_frames.rb +31 -0
  58. data/lib/id3taginator/frames/lyrics/entities/unsync_lyrics.rb +26 -0
  59. data/lib/id3taginator/frames/lyrics/uslt_unsync_lyrics_frame.rb +56 -0
  60. data/lib/id3taginator/frames/lyrics_frames.rb +42 -0
  61. data/lib/id3taginator/frames/mcdi/mcdi_music_cd_identifier_frame.rb +40 -0
  62. data/lib/id3taginator/frames/mcdi_frames.rb +28 -0
  63. data/lib/id3taginator/frames/picture/apic_picture_frame.rb +106 -0
  64. data/lib/id3taginator/frames/picture/entities/picture.rb +32 -0
  65. data/lib/id3taginator/frames/picture_frames.rb +51 -0
  66. data/lib/id3taginator/frames/private/entities/private_frame.rb +24 -0
  67. data/lib/id3taginator/frames/private/priv_private_frame.rb +45 -0
  68. data/lib/id3taginator/frames/private_frames.rb +40 -0
  69. data/lib/id3taginator/frames/text/entities/copyright.rb +24 -0
  70. data/lib/id3taginator/frames/text/entities/date.rb +24 -0
  71. data/lib/id3taginator/frames/text/entities/part_of_set.rb +24 -0
  72. data/lib/id3taginator/frames/text/entities/time.rb +24 -0
  73. data/lib/id3taginator/frames/text/entities/track_number.rb +24 -0
  74. data/lib/id3taginator/frames/text/entities/user_info.rb +24 -0
  75. data/lib/id3taginator/frames/text/talb_album_frame.rb +40 -0
  76. data/lib/id3taginator/frames/text/tbpm_bpm_frame.rb +40 -0
  77. data/lib/id3taginator/frames/text/tcom_composer_frame.rb +42 -0
  78. data/lib/id3taginator/frames/text/tcon_genre_frame.rb +104 -0
  79. data/lib/id3taginator/frames/text/tcop_copyright_frame.rb +55 -0
  80. data/lib/id3taginator/frames/text/tdat_date_frame.rb +60 -0
  81. data/lib/id3taginator/frames/text/tdly_playlist_delay_frame.rb +40 -0
  82. data/lib/id3taginator/frames/text/tenc_encoded_by_frame.rb +40 -0
  83. data/lib/id3taginator/frames/text/text_writers_frame.rb +43 -0
  84. data/lib/id3taginator/frames/text/tflt_file_type_frame.rb +71 -0
  85. data/lib/id3taginator/frames/text/time_time_frame.rb +60 -0
  86. data/lib/id3taginator/frames/text/tit1_content_group_description_frame.rb +40 -0
  87. data/lib/id3taginator/frames/text/tit2_title_frame.rb +40 -0
  88. data/lib/id3taginator/frames/text/tit3_subtitle_frame.rb +40 -0
  89. data/lib/id3taginator/frames/text/tkey_initial_key_frame.rb +40 -0
  90. data/lib/id3taginator/frames/text/tlan_language_frame.rb +48 -0
  91. data/lib/id3taginator/frames/text/tlen_length_frame.rb +40 -0
  92. data/lib/id3taginator/frames/text/tmed_media_type_frame.rb +40 -0
  93. data/lib/id3taginator/frames/text/toal_original_album_frame.rb +40 -0
  94. data/lib/id3taginator/frames/text/tofn_original_filename_frame.rb +40 -0
  95. data/lib/id3taginator/frames/text/toly_original_writers_frame.rb +43 -0
  96. data/lib/id3taginator/frames/text/tope_original_artists_frame.rb +43 -0
  97. data/lib/id3taginator/frames/text/tory_original_release_year_frame.rb +42 -0
  98. data/lib/id3taginator/frames/text/town_file_owner_frame.rb +40 -0
  99. data/lib/id3taginator/frames/text/tpe1_artist_frame.rb +43 -0
  100. data/lib/id3taginator/frames/text/tpe2_album_artist_frame.rb +40 -0
  101. data/lib/id3taginator/frames/text/tpe3_conductor_frame.rb +40 -0
  102. data/lib/id3taginator/frames/text/tpe4_modified_by_frame.rb +40 -0
  103. data/lib/id3taginator/frames/text/tpos_part_of_set_frame.rb +50 -0
  104. data/lib/id3taginator/frames/text/tpub_publisher_frame.rb +40 -0
  105. data/lib/id3taginator/frames/text/trck_track_number_frame.rb +50 -0
  106. data/lib/id3taginator/frames/text/trda_recording_dates_frame.rb +42 -0
  107. data/lib/id3taginator/frames/text/trsn_internet_radio_station_frame.rb +40 -0
  108. data/lib/id3taginator/frames/text/tsiz_size_frame.rb +41 -0
  109. data/lib/id3taginator/frames/text/tsoa_album_sort_order_frame.rb +40 -0
  110. data/lib/id3taginator/frames/text/tsop_performer_sort_order_frame.rb +40 -0
  111. data/lib/id3taginator/frames/text/tsot_title_sort_order_frame.rb +40 -0
  112. data/lib/id3taginator/frames/text/tsrc_isrc_frame.rb +40 -0
  113. data/lib/id3taginator/frames/text/tsse_encoder_frame.rb +40 -0
  114. data/lib/id3taginator/frames/text/txxx_user_text_info_frame.rb +51 -0
  115. data/lib/id3taginator/frames/text/tyer_year_frame.rb +42 -0
  116. data/lib/id3taginator/frames/text_frames.rb +840 -0
  117. data/lib/id3taginator/frames/tos/entities/ownership.rb +26 -0
  118. data/lib/id3taginator/frames/tos/entities/terms_of_use.rb +24 -0
  119. data/lib/id3taginator/frames/tos/owne_ownership_frame.rb +53 -0
  120. data/lib/id3taginator/frames/tos/user_terms_of_use_frame.rb +49 -0
  121. data/lib/id3taginator/frames/tos_frames.rb +54 -0
  122. data/lib/id3taginator/frames/ufid/entities/ufid_info.rb +24 -0
  123. data/lib/id3taginator/frames/ufid/ufid_unique_file_identifier_frame.rb +47 -0
  124. data/lib/id3taginator/frames/ufid_frames.rb +40 -0
  125. data/lib/id3taginator/frames/url/entities/user_info.rb +24 -0
  126. data/lib/id3taginator/frames/url/wcom_commercial_url_frame.rb +40 -0
  127. data/lib/id3taginator/frames/url/wcop_copyright_url_frame.rb +40 -0
  128. data/lib/id3taginator/frames/url/woaf_official_file_webpage_frame.rb +40 -0
  129. data/lib/id3taginator/frames/url/woar_official_artist_webpage_frame.rb +40 -0
  130. data/lib/id3taginator/frames/url/woas_official_source_webpage_frame.rb +40 -0
  131. data/lib/id3taginator/frames/url/wors_official_radio_station_homepage_frame.rb +40 -0
  132. data/lib/id3taginator/frames/url/wpay_payment_url_frame.rb +40 -0
  133. data/lib/id3taginator/frames/url/wpub_official_publisher_webpage_frame.rb +40 -0
  134. data/lib/id3taginator/frames/url/wxxx_user_url_link_frame.rb +50 -0
  135. data/lib/id3taginator/frames/url_frames.rb +195 -0
  136. data/lib/id3taginator/genres.rb +168 -0
  137. data/lib/id3taginator/header/id3v23_extended_header.rb +37 -0
  138. data/lib/id3taginator/header/id3v24_extended_header.rb +100 -0
  139. data/lib/id3taginator/header/id3v2_flags.rb +64 -0
  140. data/lib/id3taginator/id3v1_tag.rb +156 -0
  141. data/lib/id3taginator/id3v22_tag.rb +30 -0
  142. data/lib/id3taginator/id3v23_tag.rb +63 -0
  143. data/lib/id3taginator/id3v24_tag.rb +75 -0
  144. data/lib/id3taginator/id3v2_tag.rb +241 -0
  145. data/lib/id3taginator/options/options.rb +33 -0
  146. data/lib/id3taginator/util/compress_util.rb +25 -0
  147. data/lib/id3taginator/util/math_util.rb +68 -0
  148. data/lib/id3taginator/util/sync_util.rb +65 -0
  149. data/lib/id3taginator.rb +449 -0
  150. metadata +198 -0
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Extensions
5
+ module ArgumentCheck
6
+ def self.included(base)
7
+ base.extend(self)
8
+ end
9
+
10
+ # raises an ArgumentError if the given argument is nil
11
+ #
12
+ # @param arg [Object] the argument to check
13
+ # @param arg_name [String] the argument name to use in the Error Message
14
+ def argument_not_nil(arg, arg_name)
15
+ raise ArgumentError, "#{arg_name} can't be nil." if arg.nil?
16
+ end
17
+
18
+ # raises an ArgumentError if the given argument no Symbol
19
+ #
20
+ # @param arg [Object] the argument to check
21
+ # @param arg_name [String] the argument name to use in the Error Message
22
+ def argument_sym(arg, arg_name)
23
+ raise ArgumentError, "#{arg_name} must be sym." unless arg.is_a?(Symbol)
24
+ end
25
+
26
+ # raises an ArgumentError if the given argument is no Boolean
27
+ #
28
+ # @param arg [Object] the argument to check
29
+ # @param arg_name [String] the argument name to use in the Error Message
30
+ def argument_boolean(arg, arg_name)
31
+ raise ArgumentError, "#{arg_name} must be boolean." unless !!arg == arg
32
+ end
33
+
34
+ # raises an ArgumentError if the given argument is no array or empty
35
+ #
36
+ # @param arg [Object] the argument to check
37
+ # @param arg_name [String] the argument name to use in the Error Message
38
+ def argument_not_empty(arg, arg_name)
39
+ raise ArgumentError, "#{arg_name} can't be empty." if arg.nil? || (arg.is_a?(Array) && arg.empty?)
40
+ end
41
+
42
+ # raises an ArgumentError if the given argument has not the expected number of chars
43
+ #
44
+ # @param arg [String] the argument to check
45
+ # @param arg_name [String] the argument name to use in the Error Message
46
+ # @param num_chars [Integer] number of chars to check against
47
+ def argument_exactly_chars(arg, arg_name, num_chars)
48
+ raise ArgumentError, "#{arg_name} must have #{num_chars} characters." if arg.nil? || arg.length != num_chars
49
+ end
50
+
51
+ # rubocop:disable Style/GuardClause
52
+ # raises an ArgumentError if the given argument has not the more than number of chars
53
+ #
54
+ # @param arg [String] the argument to check
55
+ # @param arg_name [String] the argument name to use in the Error Message
56
+ # @param num_chars [Integer] number of chars to check against
57
+ def argument_less_than_chars(arg, arg_name, num_chars)
58
+ if arg.nil? || arg.length > num_chars
59
+ raise ArgumentError, "#{arg_name} must have less than #{num_chars} characters."
60
+ end
61
+ end
62
+
63
+ # raises an ArgumentError if the given argument has not the more less number of chars
64
+ #
65
+ # @param arg [String] the argument to check
66
+ # @param arg_name [String] the argument name to use in the Error Message
67
+ # @param num_chars [Integer] number of chars to check against
68
+ def argument_more_than_chars(arg, arg_name, num_chars)
69
+ if arg.nil? || arg.length < num_chars
70
+ raise ArgumentError, "#{arg_name} must have more than #{num_chars} characters."
71
+ end
72
+ end
73
+
74
+ # raises an ArgumentError if the given arguments number of chars is not in the given range
75
+ #
76
+ # @param arg [String] the argument to check
77
+ # @param arg_name [String] the argument name to use in the Error Message
78
+ # @param bigger_than [Integer] string must have more than chars
79
+ # @param smaller_than [Integer] string must have less than chars
80
+ def argument_between_num(arg, arg_name, bigger_than, smaller_than)
81
+ if arg.nil? || arg > smaller_than || arg < bigger_than
82
+ raise ArgumentError, "#{arg_name} must have be between #{bigger_than} and #{smaller_than}."
83
+ end
84
+ end
85
+ # rubocop:enable Style/GuardClause
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Extensions
5
+ # offers compare methods
6
+ module Comparable
7
+ # compares the instance variables of 2 objects for equality
8
+ # @param obj1 object 1
9
+ # @param obj2 object 2
10
+ # @return [Boolean] true if both instance variables are the same, else false
11
+ def compare(obj1, obj2)
12
+ is_same = true
13
+ obj1.instance_variables.each do |it|
14
+ is_same = false if obj1.instance_variable_get(it) != obj2.instance_variable_get(it)
15
+ end
16
+
17
+ is_same
18
+ end
19
+
20
+ # compares all Comparables using the defined compare method
21
+ # @param other [Object] the object to compare with
22
+ # @return [Boolean] true if equal, else false
23
+ def ==(other)
24
+ compare(self, other)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Extensions
5
+ module Encodable
6
+ def self.included(base)
7
+ base.extend(self)
8
+ end
9
+
10
+ ZERO = "\x00"
11
+ UNICODE_ZERO = "\x00\x00"
12
+
13
+ ISO8859_1 = 0x00
14
+ UTF_16 = 0x01
15
+ UTF_16BE = 0x02
16
+ UTF_8 = 0x03
17
+
18
+ # returns the ZERO byte depending of the given encoding, so 0x00 0x00 for Unicode, else 0x00
19
+ #
20
+ # @param encoding [Encoding] the encoding to determine the zero byte(s)
21
+ #
22
+ # @return [String] the zero byte(s) String
23
+ def zero_byte(encoding = @options.default_encode_dest)
24
+ encoding == Encoding::ISO8859_1 ? ZERO : UNICODE_ZERO
25
+ end
26
+
27
+ # finds the encoding byte for the given encoding
28
+ #
29
+ # @param encoding [Encoding] the encoding
30
+ #
31
+ # @return [Integer] the encoding byte for this encoding
32
+ def find_encoding_byte(encoding)
33
+ case encoding
34
+ when Encoding::ISO8859_1
35
+ ISO8859_1
36
+ when Encoding::UTF_8
37
+ UTF_8
38
+ when Encoding::UTF_16
39
+ UTF_16
40
+ when Encoding::UTF_16BE
41
+ UTF_16BE
42
+ else
43
+ raise Errors::Id3TagError, "#{encoding} is not a valid encoding."
44
+ end
45
+ end
46
+
47
+ # finds the encoding for the given encoding Byte
48
+ #
49
+ # @param encoding_byte [Integer] the encoding byte
50
+ #
51
+ # @return [Encoding] the encoding for this encoding byte
52
+ def find_encoding(encoding_byte)
53
+ case encoding_byte
54
+ when ISO8859_1
55
+ Encoding::ISO8859_1
56
+ when UTF_8
57
+ Encoding::UTF_8
58
+ when UTF_16
59
+ Encoding::UTF_16
60
+ when UTF_16BE
61
+ Encoding::UTF_16BE
62
+ else
63
+ raise Errors::Id3TagError, "#{encoding_byte} is not a valid encoding bit."
64
+ end
65
+ end
66
+
67
+ # returns the default destination encoding byte
68
+ #
69
+ # @return [String] the String representation of the default destination encoding byte
70
+ def default_encoding_destination_byte
71
+ [find_encoding_byte(@options.default_encode_dest)].pack('C*')
72
+ end
73
+
74
+ # reads the given stream until the byte combination is found
75
+ #
76
+ # @param stream [StringIO, IO, File] the stream
77
+ # @param until_bytes [String] read stream until this combination is found
78
+ #
79
+ # @return [String] String representation of the bytes array that is read
80
+ def read_stream_until(stream, until_bytes)
81
+ result = []
82
+ size = until_bytes.length
83
+ until (read = stream.read(size)).nil?
84
+ break if read == until_bytes
85
+
86
+ result << read&.bytes
87
+ end
88
+
89
+ result.flatten.pack('C*')
90
+ end
91
+
92
+ # merges the given string representing a byte array to a String representation of the merged byte arrays
93
+ #
94
+ # @param str [Array<String>] byte arrays represented as a String to merge
95
+ #
96
+ # @return [String] String representation of the merged byte arrays (str.bytes)
97
+ def merge(*str)
98
+ str.inject([]) { |sum, x| sum + x.bytes }.pack('C*')
99
+ end
100
+
101
+ # decodes the given string using the encoding byte (first byte of the string)
102
+ #
103
+ # @param str [String] String to decode using the first byte to determine the encoding
104
+ #
105
+ # @return [String] the decoded String
106
+ def decode_using_encoding_byte(str)
107
+ encoding_byte = str[0]&.bytes&.first
108
+ raise Errors::Id3TagError, "Could not find encoding byte for String: #{str}" if encoding_byte.nil?
109
+
110
+ encoding = find_encoding(encoding_byte)
111
+
112
+ # noinspection RubyMismatchedParameterType
113
+ decode(str[1..-1], encoding)
114
+ end
115
+
116
+ # encode the given String from source encoding to destination encoding and adds the destination encoding
117
+ # byte as the first byte
118
+ #
119
+ # @param str [String] the String to encode
120
+ # @param source_encoding [Encoding] the source encoding
121
+ # @param dest_encoding [Encoding] the destination encoding
122
+ #
123
+ # @return [String] the encoded String with the encoding byte as first byte
124
+ def encode_and_add_encoding_byte(str, source_encoding = @options.default_decode_dest,
125
+ dest_encoding = @options.default_encode_dest)
126
+ encoding_byte = find_encoding_byte(dest_encoding)
127
+ result = encode_string(str, source_encoding, dest_encoding)
128
+ result = remove_trailing_zeros(result)
129
+ bytes = result.bytes
130
+ bytes.unshift(encoding_byte)
131
+
132
+ bytes.flatten.pack('C*')
133
+ end
134
+
135
+ # decodes the given String from the default destination decoding to the given source encoding
136
+ #
137
+ # @param str [String] the String to decode
138
+ # @param source_encoding [Encoding] the result encoding
139
+ #
140
+ # @return [String] the decoded String
141
+ def decode(str, source_encoding = @options.default_encode_dest)
142
+ result = encode_string(str, source_encoding, @options.default_decode_dest)
143
+ remove_trailing_zeros(result)
144
+ end
145
+
146
+ # encodes the given String from the default destination encoding to the given source encoding
147
+ #
148
+ # @param str [String] the String to decode
149
+ # @param source_encoding [Encoding] the result encoding
150
+ #
151
+ # @return [String] the encoded String
152
+ def encode(str, source_encoding = @options.default_decode_dest, null_terminated: false)
153
+ encode_string(str, source_encoding, @options.default_encode_dest, null_terminated: null_terminated)
154
+ end
155
+
156
+ # encodes the given String from source to destination encoding and optionally adds a null termination byte(s)
157
+ #
158
+ # @param str [String] the String to encode
159
+ # @param source_encoding [Encoding] source encoding
160
+ # @param dest_encoding [Encoding] destination encoding
161
+ # @param null_terminated [Boolean] true, if null termination byte(s) should be added
162
+ #
163
+ # @return [String] the encoded String and if requested, added null termination byte(s)
164
+ def encode_string(str, source_encoding, dest_encoding, null_terminated: false)
165
+ if str.empty? && null_terminated && dest_encoding != Encoding::ISO8859_1
166
+ return [0xFE, 0xFF, 0x00, 0x00].pack('C*')
167
+ end
168
+
169
+ res = str.encode(dest_encoding, source_encoding)
170
+ null_terminated ? merge(res, zero_byte(dest_encoding)) : res
171
+ end
172
+
173
+ # removes trailing zero bytes if present
174
+ #
175
+ # @param str [String] the string to remove the trailing zeros if present
176
+ #
177
+ # @return [String] the String without trailing zeros
178
+ def remove_trailing_zeros(str)
179
+ return str if str.empty?
180
+
181
+ last = str.bytes.last
182
+ while last&.zero?
183
+ str.chop!
184
+ last = str.bytes.last
185
+ end
186
+
187
+ str
188
+ end
189
+
190
+ # pads the given String with X chars to the left
191
+ #
192
+ # @param str [String] the String to pad
193
+ # @param padding_to [Integer] pad until length of
194
+ # @param char [String] the char to use to pad
195
+ #
196
+ # @return [String] the padded String
197
+ def pad_left(str, padding_to, char = 0.chr)
198
+ str ||= ''
199
+ str.ljust(padding_to, char)
200
+ end
201
+
202
+ # pads the given String with X chars to the right
203
+ #
204
+ # @param str [String] the String to pad
205
+ # @param padding_to [Integer] pad until length of
206
+ # @param char [String] the char to use to pad
207
+ #
208
+ # @return [String] the padded String
209
+ def pad_right(str, padding_to, char = 0.chr)
210
+ str ||= ''
211
+ str.rjust(padding_to, char)
212
+ end
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Extensions
5
+ module Optionable
6
+ # sets the default encoding for destination
7
+ #
8
+ # @param encoding [Encoding] the encoding to set
9
+ #
10
+ # @return [Self] returns self for builder style
11
+ def default_encode_for_destination(encoding)
12
+ raise Errors::Id3TagError, 'Encoding can\'t be nil' if encoding.nil?
13
+
14
+ @options.default_encode_dest = encoding
15
+ self
16
+ end
17
+
18
+ # sets the default decoding for destination
19
+ #
20
+ # @param encoding [Encoding] the encoding to set
21
+ #
22
+ # @return [Self] returns self for builder style
23
+ def default_decode_for_destination(encoding)
24
+ raise Errors::Id3TagError, 'Encoding can\'t be nil' if encoding.nil?
25
+
26
+ @options.default_decode_dest = encoding
27
+ self
28
+ end
29
+
30
+ # sets the padding
31
+ #
32
+ # @param number_bytes [Integer] number of bytes to pad
33
+ #
34
+ # @return [Self] returns self for builder style
35
+ def tag_padding(number_bytes)
36
+ number_bytes = 0 if number_bytes.nil?
37
+ @options.padding_bytes = number_bytes
38
+ self
39
+ end
40
+
41
+ # if true, does not throw an error if a frame is added that is not supported for 2.3 e.g. Sort Order Frames
42
+ #
43
+ # @param ignore [Boolean] true to ignore errors, else false
44
+ #
45
+ # @return [Self] returns self for builder style
46
+ def ignore_v23_frame_error(ignore = true)
47
+ @options.ignore_v23_frame_error = ignore
48
+ self
49
+ end
50
+
51
+ # if true, does not throw an error if a frame is added that is not supported for 2.4 e.g. Size Frame
52
+ #
53
+ # @param ignore [Boolean] true to ignore errors, else false
54
+ #
55
+ # @return [Self] returns self for builder style
56
+ def ignore_v24_frame_error(ignore = true)
57
+ @options.ignore_v24_frame_error = ignore
58
+ self
59
+ end
60
+
61
+ # if true, the size frame TSIZ will be added automatically to v2.3 if not present. Will be added to v2.4 if
62
+ # ignore_v24_frame_error is true
63
+
64
+ # @param add_frame [Boolean] true to add the frame automatically if not present, else false
65
+ #
66
+ # @return [Self] returns self for builder style
67
+ def add_size_frame(add_frame = true)
68
+ @options.add_size_frame = add_frame
69
+ self
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Buffer
6
+ module Entities
7
+ class Buffer
8
+ include Extensions::Comparable
9
+
10
+ attr_accessor :buffer_size, :embedded_info_flag, :offset_next_tag
11
+
12
+ # constructor
13
+ #
14
+ # @param buffer_size [Integer] the buffer size
15
+ # @param embedded_info_flag [Boolean] true if infos are embedded
16
+ # @param offset_next_tag [Integer, nil] offset till next tag starts in bytes
17
+ def initialize(buffer_size, embedded_info_flag, offset_next_tag)
18
+ @buffer_size = buffer_size
19
+ @embedded_info_flag = embedded_info_flag
20
+ @offset_next_tag = offset_next_tag
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Buffer
6
+ class RecommendedBufferSizeFrame < Id3v2Frame
7
+ include HasId
8
+
9
+ frame_info :BUF, :RBUF, :RBUF
10
+
11
+ attr_accessor :buffer_size, :embedded_info_flag, :offset_next_tag
12
+
13
+ # builds the recommended buffer size frame
14
+ #
15
+ # @param buffer_size [Integer] the buffer size
16
+ # @param embedded_info_flag [Boolean] true if infos are embedded
17
+ # @param offset_next_tag [Integer] offset till next tag starts in bytes
18
+ # @param options [Options::Options] options to use
19
+ # @param id3_version [Integer] the id3 version to build the frame for
20
+ #
21
+ # @return [Id3v2Frame] the resulting id3v2 frame
22
+ def self.build_frame(buffer_size, embedded_info_flag, offset_next_tag, options = nil, id3_version: 3)
23
+ supported?('RBUF', id3_version, options)
24
+
25
+ argument_not_nil(buffer_size, 'buffer_size')
26
+ argument_not_nil(embedded_info_flag, 'embedded_into_flag')
27
+ argument_boolean(embedded_info_flag, 'embedded_into_flag')
28
+
29
+ frame = new(frame_id(id3_version, options), 0, build_id3_flags(id3_version), '')
30
+ frame.buffer_size = buffer_size
31
+ frame.embedded_info_flag = embedded_info_flag
32
+ frame.offset_next_tag = offset_next_tag
33
+ frame
34
+ end
35
+
36
+ def process_content(content)
37
+ stream = StringIO.new(content)
38
+ # noinspection RubyNilAnalysis
39
+ @buffer_size = Util::MathUtil.to_number(stream.read(3).bytes)
40
+ @embedded_info_flag = (stream.readbyte & 0b00000001) == 0b00000001
41
+ offset_nxt_tag = stream.read(4)
42
+ @offset_next_tag = offset_nxt_tag.nil? ? nil : offset_nxt_tag.bytes.pack('C*').unpack1('N')
43
+ end
44
+
45
+ def content_to_bytes
46
+ buffer_size = @buffer_size.to_s(16).rjust(6, '0').scan(/../).map { |x| x.hex.chr }.join
47
+ embedded_into = @embedded_info_flag ? "\x01" : "\x00"
48
+ offset_next_tag = if @offset_next_tag.nil?
49
+ nil
50
+ else
51
+ @offset_next_tag.to_s(16).rjust(8, '0').scan(/../).map { |x| x.hex.chr }.join
52
+ end
53
+
54
+ offset_next_tag.nil? ? merge(buffer_size, embedded_into) : merge(buffer_size, embedded_into, offset_next_tag)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module BufferFrames
6
+ include Frames::Frameable
7
+
8
+ # extracts the recommended buffer size (RBUF/BUF)
9
+ #
10
+ # @return [Frames::Buffer::Entities::Buffer, nil] returns the Buffer
11
+ def recommended_buffer_size
12
+ frame = find_frame(Buffer::RecommendedBufferSizeFrame.frame_id(@major_version, @options))
13
+ return nil if frame.nil?
14
+
15
+ Frames::Buffer::Entities::Buffer.new(frame.buffer_size, frame.embedded_info_flag, frame.offset_next_tag)
16
+ end
17
+
18
+ # sets the recommended buffer size (RBUF/BUF)
19
+ #
20
+ # @param buffer [Frames::Buffer::Entities::Buffer] the buffer
21
+ def recommended_buffer_size=(buffer)
22
+ set_frame_fields(Buffer::RecommendedBufferSizeFrame, %i[@buffer_size @embedded_info_flag @offset_next_tag],
23
+ buffer.buffer_size, buffer.embedded_info_flag, buffer.offset_next_tag)
24
+ end
25
+
26
+ # removes the recommended buffer size frame
27
+ def remove_recommended_buffer_size
28
+ @frames.delete_if { |f| f.frame_id == Buffer::RecommendedBufferSizeFrame.frame_id(@major_version, @options) }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Comment
6
+ class CommentFrame < Id3v2Frame
7
+ include HasId
8
+
9
+ frame_info :COM, :COMM, :COMM
10
+
11
+ attr_accessor :language, :descriptor, :text
12
+
13
+ # builds the comment frame
14
+ #
15
+ # @param language [String] the language of the comment (3 characters)
16
+ # @param descriptor [String] description
17
+ # @param text [String] the comment text
18
+ # @param options [Options::Options] options to use
19
+ # @param id3_version [Integer] the id3 version to build the frame for
20
+ #
21
+ # @return [Id3v2Frame] the resulting id3v2 frame
22
+ def self.build_frame(language, descriptor, text, options = nil, id3_version: 3)
23
+ supported?('COMM', id3_version, options)
24
+
25
+ argument_not_nil(language, 'language')
26
+ argument_exactly_chars(language, 'language', 3)
27
+ argument_not_nil(text, 'text')
28
+ descriptor ||= ''
29
+
30
+ frame = new(frame_id(id3_version, options), 0, build_id3_flags(id3_version), '')
31
+ frame.language = language
32
+ frame.descriptor = descriptor
33
+ frame.text = text
34
+ frame
35
+ end
36
+
37
+ def process_content(content)
38
+ stream = StringIO.new(content)
39
+ encoding = find_encoding(stream.readbyte)
40
+
41
+ @language = stream.read(3)
42
+ encoded_descriptor = read_stream_until(stream, zero_byte(encoding))
43
+ @descriptor = decode(encoded_descriptor, encoding)
44
+ encoded_text = stream.read
45
+ @text = decode(encoded_text, encoding)
46
+ end
47
+
48
+ def content_to_bytes
49
+ encoded_descriptor = encode(@descriptor, null_terminated: true)
50
+ encoded_text = encode(@text)
51
+ merge(default_encoding_destination_byte, @language, encoded_descriptor, encoded_text)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Comment
6
+ module Entities
7
+ class Comment
8
+ include Extensions::Comparable
9
+
10
+ attr_accessor :language, :descriptor, :text
11
+
12
+ # constructor
13
+ #
14
+ # @param language [String] the language of the comment (3 characters)
15
+ # @param descriptor [String] description
16
+ # @param text [String] the comment text
17
+ def initialize(language, descriptor, text)
18
+ @language = language
19
+ @descriptor = descriptor
20
+ @text = text
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module CommentFrames
6
+ include Frames::Frameable
7
+
8
+ # extracts the comments (COMM/COM)
9
+ #
10
+ # @return [Array<Frames::Comment::Entities::Comment>] returns the Comments
11
+ def comments
12
+ frame = find_frames(Comment::CommentFrame.frame_id(@major_version, @options))
13
+ return [] if frame.nil? || frame.empty?
14
+
15
+ frame.map { |f| Comment::Entities::Comment.new(f.language, f.descriptor, f.text) }
16
+ end
17
+
18
+ # adds a comment (COMM/COM)
19
+ # Multiple ones can be added, as long as they have different language/descriptor
20
+ #
21
+ # @param comment [Frames::Comment::Entities::Comment] the comment to set
22
+ def comment=(comment)
23
+ set_frame_fields_by_selector(Comment::CommentFrame, %i[@language @descriptor @text],
24
+ ->(f) { f.language == comment.language && f.descriptor == comment.descriptor },
25
+ comment.language, comment.descriptor, comment.text)
26
+ end
27
+
28
+ alias add_comment comment=
29
+
30
+ # removes a comment for the specific language and descriptor
31
+ #
32
+ # @param language [String] the language
33
+ # @param descriptor [String] the descriptor
34
+ def remove_comment(language, descriptor)
35
+ @frames.delete_if do |f|
36
+ f.frame_id == Comment::CommentFrame.frame_id(@major_version, @options) && f.language == language &&
37
+ f.descriptor == descriptor
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Count
6
+ module Entities
7
+ class Popularimeter
8
+ include Extensions::Comparable
9
+
10
+ attr_accessor :email, :rating, :counter
11
+
12
+ # constructor
13
+ #
14
+ # @param email [String] email of the user
15
+ # @param rating [Integer] the rating between 0 and 255
16
+ # @param counter [Integer] the counter, default 32 bit integer, but can be higher too
17
+ def initialize(email, rating, counter)
18
+ @email = email
19
+ @rating = rating
20
+ @counter = counter
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end