id3taginator 0.8

Sign up to get free protection for your applications and to get access to all the features.
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