id3taginator 0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.idea/.gitignore +8 -0
- data/.idea/ID3Taginator.iml +73 -0
- data/.idea/misc.xml +4 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/.rspec +3 -0
- data/.rubocop.yml +26 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +408 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/id3taginator.gemspec +37 -0
- data/lib/id3taginator/audio_file.rb +136 -0
- data/lib/id3taginator/errors/id3_tag_error.rb +12 -0
- data/lib/id3taginator/extensions/argument_check.rb +88 -0
- data/lib/id3taginator/extensions/comparable.rb +28 -0
- data/lib/id3taginator/extensions/encodable.rb +215 -0
- data/lib/id3taginator/extensions/optionable.rb +73 -0
- data/lib/id3taginator/frames/buffer/entities/buffer.rb +26 -0
- data/lib/id3taginator/frames/buffer/rbuf_recommended_buffer_size_frame.rb +59 -0
- data/lib/id3taginator/frames/buffer_frames.rb +32 -0
- data/lib/id3taginator/frames/comment/comm_comment_frame.rb +56 -0
- data/lib/id3taginator/frames/comment/entities/comment.rb +26 -0
- data/lib/id3taginator/frames/comment_frames.rb +42 -0
- data/lib/id3taginator/frames/count/entities/popularimeter.rb +26 -0
- data/lib/id3taginator/frames/count/pcnt_play_counter_frame.rb +40 -0
- data/lib/id3taginator/frames/count/popm_popularimeter_frame.rb +52 -0
- data/lib/id3taginator/frames/count_frames.rb +51 -0
- data/lib/id3taginator/frames/custom_frame.rb +39 -0
- data/lib/id3taginator/frames/custom_frames.rb +54 -0
- data/lib/id3taginator/frames/encryption/aenc_audio_encryption.rb +54 -0
- data/lib/id3taginator/frames/encryption/encr_encryption_method_frame.rb +49 -0
- data/lib/id3taginator/frames/encryption/entities/audio_encryption.rb +28 -0
- data/lib/id3taginator/frames/encryption/entities/encryption_method.rb +26 -0
- data/lib/id3taginator/frames/encryption_frames.rb +77 -0
- data/lib/id3taginator/frames/frameable.rb +75 -0
- data/lib/id3taginator/frames/geo/entities/encapsulated_object.rb +28 -0
- data/lib/id3taginator/frames/geo/geob_general_encapsulated_object_frame.rb +59 -0
- data/lib/id3taginator/frames/geo_frames.rb +41 -0
- data/lib/id3taginator/frames/grouping/entities/group_identification.rb +26 -0
- data/lib/id3taginator/frames/grouping/grid_group_identification_frame.rb +49 -0
- data/lib/id3taginator/frames/grouping/grp1_grouping_frame.rb +40 -0
- data/lib/id3taginator/frames/grouping_frames.rb +61 -0
- data/lib/id3taginator/frames/has_id.rb +51 -0
- data/lib/id3taginator/frames/id3v23_frame_flags.rb +64 -0
- data/lib/id3taginator/frames/id3v24_frame_flags.rb +80 -0
- data/lib/id3taginator/frames/id3v2_frame.rb +249 -0
- data/lib/id3taginator/frames/id3v2_frame_factory.rb +98 -0
- data/lib/id3taginator/frames/ipl/entities/involved_person.rb +24 -0
- data/lib/id3taginator/frames/ipl/ipls_involved_people_frame.rb +48 -0
- data/lib/id3taginator/frames/ipl_frames.rb +31 -0
- data/lib/id3taginator/frames/lyrics/entities/unsync_lyrics.rb +26 -0
- data/lib/id3taginator/frames/lyrics/uslt_unsync_lyrics_frame.rb +56 -0
- data/lib/id3taginator/frames/lyrics_frames.rb +42 -0
- data/lib/id3taginator/frames/mcdi/mcdi_music_cd_identifier_frame.rb +40 -0
- data/lib/id3taginator/frames/mcdi_frames.rb +28 -0
- data/lib/id3taginator/frames/picture/apic_picture_frame.rb +106 -0
- data/lib/id3taginator/frames/picture/entities/picture.rb +32 -0
- data/lib/id3taginator/frames/picture_frames.rb +51 -0
- data/lib/id3taginator/frames/private/entities/private_frame.rb +24 -0
- data/lib/id3taginator/frames/private/priv_private_frame.rb +45 -0
- data/lib/id3taginator/frames/private_frames.rb +40 -0
- data/lib/id3taginator/frames/text/entities/copyright.rb +24 -0
- data/lib/id3taginator/frames/text/entities/date.rb +24 -0
- data/lib/id3taginator/frames/text/entities/part_of_set.rb +24 -0
- data/lib/id3taginator/frames/text/entities/time.rb +24 -0
- data/lib/id3taginator/frames/text/entities/track_number.rb +24 -0
- data/lib/id3taginator/frames/text/entities/user_info.rb +24 -0
- data/lib/id3taginator/frames/text/talb_album_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tbpm_bpm_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tcom_composer_frame.rb +42 -0
- data/lib/id3taginator/frames/text/tcon_genre_frame.rb +104 -0
- data/lib/id3taginator/frames/text/tcop_copyright_frame.rb +55 -0
- data/lib/id3taginator/frames/text/tdat_date_frame.rb +60 -0
- data/lib/id3taginator/frames/text/tdly_playlist_delay_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tenc_encoded_by_frame.rb +40 -0
- data/lib/id3taginator/frames/text/text_writers_frame.rb +43 -0
- data/lib/id3taginator/frames/text/tflt_file_type_frame.rb +71 -0
- data/lib/id3taginator/frames/text/time_time_frame.rb +60 -0
- data/lib/id3taginator/frames/text/tit1_content_group_description_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tit2_title_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tit3_subtitle_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tkey_initial_key_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tlan_language_frame.rb +48 -0
- data/lib/id3taginator/frames/text/tlen_length_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tmed_media_type_frame.rb +40 -0
- data/lib/id3taginator/frames/text/toal_original_album_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tofn_original_filename_frame.rb +40 -0
- data/lib/id3taginator/frames/text/toly_original_writers_frame.rb +43 -0
- data/lib/id3taginator/frames/text/tope_original_artists_frame.rb +43 -0
- data/lib/id3taginator/frames/text/tory_original_release_year_frame.rb +42 -0
- data/lib/id3taginator/frames/text/town_file_owner_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tpe1_artist_frame.rb +43 -0
- data/lib/id3taginator/frames/text/tpe2_album_artist_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tpe3_conductor_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tpe4_modified_by_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tpos_part_of_set_frame.rb +50 -0
- data/lib/id3taginator/frames/text/tpub_publisher_frame.rb +40 -0
- data/lib/id3taginator/frames/text/trck_track_number_frame.rb +50 -0
- data/lib/id3taginator/frames/text/trda_recording_dates_frame.rb +42 -0
- data/lib/id3taginator/frames/text/trsn_internet_radio_station_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tsiz_size_frame.rb +41 -0
- data/lib/id3taginator/frames/text/tsoa_album_sort_order_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tsop_performer_sort_order_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tsot_title_sort_order_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tsrc_isrc_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tsse_encoder_frame.rb +40 -0
- data/lib/id3taginator/frames/text/txxx_user_text_info_frame.rb +51 -0
- data/lib/id3taginator/frames/text/tyer_year_frame.rb +42 -0
- data/lib/id3taginator/frames/text_frames.rb +840 -0
- data/lib/id3taginator/frames/tos/entities/ownership.rb +26 -0
- data/lib/id3taginator/frames/tos/entities/terms_of_use.rb +24 -0
- data/lib/id3taginator/frames/tos/owne_ownership_frame.rb +53 -0
- data/lib/id3taginator/frames/tos/user_terms_of_use_frame.rb +49 -0
- data/lib/id3taginator/frames/tos_frames.rb +54 -0
- data/lib/id3taginator/frames/ufid/entities/ufid_info.rb +24 -0
- data/lib/id3taginator/frames/ufid/ufid_unique_file_identifier_frame.rb +47 -0
- data/lib/id3taginator/frames/ufid_frames.rb +40 -0
- data/lib/id3taginator/frames/url/entities/user_info.rb +24 -0
- data/lib/id3taginator/frames/url/wcom_commercial_url_frame.rb +40 -0
- data/lib/id3taginator/frames/url/wcop_copyright_url_frame.rb +40 -0
- data/lib/id3taginator/frames/url/woaf_official_file_webpage_frame.rb +40 -0
- data/lib/id3taginator/frames/url/woar_official_artist_webpage_frame.rb +40 -0
- data/lib/id3taginator/frames/url/woas_official_source_webpage_frame.rb +40 -0
- data/lib/id3taginator/frames/url/wors_official_radio_station_homepage_frame.rb +40 -0
- data/lib/id3taginator/frames/url/wpay_payment_url_frame.rb +40 -0
- data/lib/id3taginator/frames/url/wpub_official_publisher_webpage_frame.rb +40 -0
- data/lib/id3taginator/frames/url/wxxx_user_url_link_frame.rb +50 -0
- data/lib/id3taginator/frames/url_frames.rb +195 -0
- data/lib/id3taginator/genres.rb +168 -0
- data/lib/id3taginator/header/id3v23_extended_header.rb +37 -0
- data/lib/id3taginator/header/id3v24_extended_header.rb +100 -0
- data/lib/id3taginator/header/id3v2_flags.rb +64 -0
- data/lib/id3taginator/id3v1_tag.rb +156 -0
- data/lib/id3taginator/id3v22_tag.rb +30 -0
- data/lib/id3taginator/id3v23_tag.rb +63 -0
- data/lib/id3taginator/id3v24_tag.rb +75 -0
- data/lib/id3taginator/id3v2_tag.rb +241 -0
- data/lib/id3taginator/options/options.rb +33 -0
- data/lib/id3taginator/util/compress_util.rb +25 -0
- data/lib/id3taginator/util/math_util.rb +68 -0
- data/lib/id3taginator/util/sync_util.rb +65 -0
- data/lib/id3taginator.rb +449 -0
- 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
|