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,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Grouping
6
+ module Entities
7
+ class GroupIdentification
8
+ include Extensions::Comparable
9
+
10
+ attr_accessor :owner_id, :group_symbol, :group_dependant_data
11
+
12
+ # constructor
13
+ #
14
+ # @param owner_id [String] the owner id
15
+ # @param group_symbol [Integer] the group symbol, number between 0 and 255
16
+ # @param group_dependant_data [String] data bytes represented as a String (str.bytes)
17
+ def initialize(owner_id, group_symbol, group_dependant_data)
18
+ @owner_id = owner_id
19
+ @group_symbol = group_symbol
20
+ @group_dependant_data = group_dependant_data
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Grouping
6
+ class GroupIdentificationFrame < Id3v2Frame
7
+ include HasId
8
+
9
+ frame_info nil, :GRID, :GRID
10
+
11
+ attr_accessor :owner_id, :group_symbol, :group_dependant_data
12
+
13
+ # builds the group identification frame
14
+ #
15
+ # @param owner_id [String] the owner id
16
+ # @param group_symbol [Integer] the group symbol, number between 0 and 255
17
+ # @param group_dependant_data [String] data bytes represented as a String (str.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(owner_id, group_symbol, group_dependant_data, options = nil, id3_version: 3)
23
+ supported?('GRID', id3_version, options)
24
+
25
+ argument_not_nil(owner_id, 'owner_id')
26
+ argument_between_num(group_symbol, 'group_symbol', 0, 255)
27
+ argument_not_nil(group_dependant_data, 'group_dependant_data')
28
+
29
+ frame = new(frame_id(id3_version, options), 0, build_id3_flags(id3_version), '')
30
+ frame.owner_id = owner_id
31
+ frame.group_symbol = group_symbol
32
+ frame.group_dependant_data = group_dependant_data
33
+ frame
34
+ end
35
+
36
+ def process_content(content)
37
+ stream = StringIO.new(content)
38
+ @owner_id = read_stream_until(stream, ZERO)
39
+ @group_symbol = stream.readbyte
40
+ @group_dependant_data = stream.read
41
+ end
42
+
43
+ def content_to_bytes
44
+ merge(@owner_id, "\x00", @group_symbol.chr, @group_dependant_data)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Grouping
6
+ class GroupingFrame < Id3v2Frame
7
+ include HasId
8
+
9
+ frame_info nil, :GRP1, :GRP1
10
+
11
+ attr_accessor :grouping
12
+
13
+ # builds the group identification frame
14
+ #
15
+ # @param grouping [String] the Grouping Information
16
+ # @param options [Options::Options] options to use
17
+ # @param id3_version [Integer] the id3 version to build the frame for
18
+ #
19
+ # @return [Id3v2Frame] the resulting id3v2 frame
20
+ def self.build_frame(grouping, options = nil, id3_version: 3)
21
+ supported?('GRP1', id3_version, options)
22
+
23
+ argument_not_nil(grouping, 'grouping')
24
+
25
+ frame = new(frame_id(id3_version, options), 0, build_id3_flags(id3_version), '')
26
+ frame.grouping = grouping.to_s
27
+ frame
28
+ end
29
+
30
+ def process_content(content)
31
+ @grouping = decode_using_encoding_byte(content)
32
+ end
33
+
34
+ def content_to_bytes
35
+ encode_and_add_encoding_byte(@grouping)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module GroupingFrames
6
+ include Frames::Frameable
7
+
8
+ # extracts the group information (GRID)
9
+ #
10
+ # @return [Array<Frames::Grouping::Entities::GroupIdentification>] returns the grouping information
11
+ def group_identifications
12
+ frame = find_frames(Grouping::GroupIdentificationFrame.frame_id(@major_version, @options))
13
+ return [] if frame.nil? || frame.empty?
14
+
15
+ frame.map do |f|
16
+ Grouping::Entities::GroupIdentification.new(f.owner_id, f.group_symbol, f.group_dependant_data)
17
+ end
18
+ end
19
+
20
+ # adds a group identification (GRID)
21
+ #
22
+ # @param group [Frames::Grouping::Entities::GroupIdentification] the grouping information to add
23
+ def group_identifications=(group)
24
+ set_frame_fields_by_selector(Grouping::GroupIdentificationFrame, %i[@group_symbol @group_dependant_data],
25
+ ->(f) { f.owner_id == group.owner_id },
26
+ group.owner_id, group.group_symbol, group.group_dependant_data)
27
+ end
28
+
29
+ alias add_group_identification group_identifications=
30
+
31
+ # removes a group identification for the specific owner_id
32
+ #
33
+ # @param owner_id [String] the owner_id
34
+ def remove_group_identification(owner_id)
35
+ @frames.delete_if do |f|
36
+ f.frame_id == Grouping::GroupIdentificationFrame.frame_id(@major_version, @options) &&
37
+ f.owner_id == owner_id
38
+ end
39
+ end
40
+
41
+ # extracts the Grouping (GRP1)
42
+ #
43
+ # @return [String, nil] returns the Grouping information
44
+ def grouping
45
+ find_frame(Grouping::GroupingFrame.frame_id(@major_version, @options))&.grouping
46
+ end
47
+
48
+ # sets the grouping (GRP1)
49
+ #
50
+ # @param grouping [String] the Grouping
51
+ def grouping=(grouping)
52
+ set_frame_fields(Grouping::GroupingFrame, [:@grouping], grouping)
53
+ end
54
+
55
+ # removes the grouping frame
56
+ def remove_grouping
57
+ @frames.delete_if { |f| f.frame_id == Grouping::GroupingFrame.frame_id(@major_version, @options) }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module HasId
6
+ def self.included(base)
7
+ base.extend(self)
8
+ end
9
+
10
+ # creates a class method to return the frame id depending on the major id3tag version
11
+ # 3 and 4 return the 4 char Tag, 2 returns the version2 3 char Tag
12
+ # Additionally, a method to check if the version is supported is provided.
13
+ #
14
+ # @param version2 [Symbol, nil] the frame id for v2
15
+ # @param version3 [Symbol, nil] the frame id for v3
16
+ # @param version4 [Symbol, nil] the frame id for v4
17
+ def frame_info(version2, version3, version4)
18
+ define_singleton_method('frame_id') do |version = nil, options = nil|
19
+ result = nil
20
+ result = version3 if version.nil?
21
+
22
+ result = version2 if version == 2
23
+ result = version3 if version == 3
24
+ result = version4 if version == 4
25
+
26
+ result = version4 if version == 3 && options&.ignore_v23_frame_error && result.nil?
27
+ result = version3 if version == 4 && options&.ignore_v24_frame_error && result.nil?
28
+
29
+ result
30
+ end
31
+ end
32
+
33
+ def supported?(frame_name, version, options)
34
+ frame_id = frame_id(version)
35
+ return true unless frame_id.nil?
36
+
37
+ if version == 3 && options&.ignore_v23_frame_error
38
+ v4_frame_id = frame_id(4)
39
+ return true unless v4_frame_id.nil?
40
+ end
41
+
42
+ if version == 4 && options&.ignore_v24_frame_error
43
+ v3_frame_id = frame_id(3)
44
+ return true unless v3_frame_id.nil?
45
+ end
46
+
47
+ raise Errors::Id3TagError, "#{frame_name} not supported by Id3v2.#{version}"
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ class Id3v23FrameFlags
6
+
7
+ def initialize(flags)
8
+ @flags = flags
9
+ end
10
+
11
+ def to_bytes
12
+ @flags
13
+ end
14
+
15
+ # determined if the frame is alter preserved
16
+ #
17
+ # @return [Boolean] true if alter preserved, else false
18
+ def tag_alter_preservation?
19
+ flag = @flags.bytes[0]
20
+ (flag & 0b1000_0000) == 0b1000_0000
21
+ end
22
+
23
+ # determined if the file is alter preserved
24
+ #
25
+ # @return [Boolean] true if the file is alter preserved, else false
26
+ def file_alter_preservation?
27
+ flag = @flags.bytes[0]
28
+ (flag & 0b0100_0000) == 0b0100_0000
29
+ end
30
+
31
+ # determined if the file frame is read only
32
+ #
33
+ # @return [Boolean] true if frame is read only, else false
34
+ def read_only?
35
+ flag = @flags.bytes[0]
36
+ (flag & 0b0010_0000) == 0b0010_0000
37
+ end
38
+
39
+ # determined if the frame is compressed
40
+ #
41
+ # @return [Boolean] true if the frame is compressed, else false
42
+ def compression?
43
+ flag = @flags.bytes[1]
44
+ (flag & 0b1000_0000) == 0b1000_0000
45
+ end
46
+
47
+ # determined if the frame is encrypted
48
+ #
49
+ # @return [Boolean] true if the frame is encrypted, else false
50
+ def encryption?
51
+ flag = @flags.bytes[1]
52
+ (flag & 0b0100_0000) == 0b0100_0000
53
+ end
54
+
55
+ # determined if the frame has a group identity
56
+ #
57
+ # @return [Boolean] true if the frame has a group identity, else false
58
+ def group_identity?
59
+ flag = @flags.bytes[1]
60
+ (flag & 0b0010_0000) == 0b0010_0000
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ class Id3v24FrameFlags
6
+
7
+ def initialize(flags)
8
+ @flags = flags
9
+ end
10
+
11
+ def to_bytes
12
+ @flags
13
+ end
14
+
15
+ # determined if the frame is alter preserved
16
+ #
17
+ # @return [Boolean] true if alter preserved, else false
18
+ def tag_alter_preservation?
19
+ flag = @flags.bytes[0]
20
+ (flag & 0b0100_0000) == 0b0100_0000
21
+ end
22
+
23
+ # determined if the file is alter preserved
24
+ #
25
+ # @return [Boolean] true if the file is alter preserved, else false
26
+ def file_alter_preservation?
27
+ flag = @flags.bytes[0]
28
+ (flag & 0b0010_0000) == 0b0010_0000
29
+ end
30
+
31
+ # determined if the file frame is read only
32
+ #
33
+ # @return [Boolean] true if frame is read only, else false
34
+ def read_only?
35
+ flag = @flags.bytes[0]
36
+ (flag & 0b0001_0000) == 0b0001_0000
37
+ end
38
+
39
+ # determined if the frame is compressed
40
+ #
41
+ # @return [Boolean] true if the frame is compressed, else false
42
+ def compression?
43
+ flag = @flags.bytes[1]
44
+ (flag & 0b0000_1000) == 0b0000_1000
45
+ end
46
+
47
+ # determined if the frame is encrypted
48
+ #
49
+ # @return [Boolean] true if the frame is encrypted, else false
50
+ def encryption?
51
+ flag = @flags.bytes[1]
52
+ (flag & 0b0000_0100) == 0b0000_0100
53
+ end
54
+
55
+ # determined if the frame has a group identity
56
+ #
57
+ # @return [Boolean] true if the frame has a group identity, else false
58
+ def group_identity?
59
+ flag = @flags.bytes[1]
60
+ (flag & 0b0100_0000) == 0b0100_0000
61
+ end
62
+
63
+ # determined if the frame is unsynchronized
64
+ #
65
+ # @return [Boolean] true if the frame is unsynchronized, else false
66
+ def unsynchronisation?
67
+ flag = @flags.bytes[1]
68
+ (flag & 0b0000_0010) == 0b0000_0010
69
+ end
70
+
71
+ # determined if the frame data length indicator is present
72
+ #
73
+ # @return [Boolean] true if the frame data length indicator is present, else false
74
+ def data_length_indicator?
75
+ flag = @flags.bytes[1]
76
+ (flag & 0b0000_0001) == 0b0000_0001
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,249 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ class Id3v2Frame
6
+ include Extensions::Encodable
7
+ include Extensions::ArgumentCheck
8
+
9
+ HEADER_SIZE_V_3_4 = 10
10
+ HEADER_SIZE_V_2 = 6
11
+
12
+ attr_accessor :options
13
+ attr_reader :frame_id
14
+
15
+ # builds an id3v2.2 frame of the given frame id and the given data stream. The data stream(0) must be after
16
+ # the frame id
17
+ #
18
+ # @param frame_id [String] the frame id
19
+ # @param file [StringIO, IO, file] the data stream
20
+ # @param options [Options::Options] the options to use
21
+ #
22
+ # @return [Id3v2Frame] the build id3v2.2 frame
23
+ def self.build_v2_frame(frame_id, file, options)
24
+ payload_size = Util::MathUtil.to_number(file.read(3)&.bytes)
25
+ frame_payload = file.read(payload_size)
26
+
27
+ raise Errors::Id3TagError, "Could not find any Frame data for #{frame_id}." if frame_payload.nil?
28
+
29
+ instance = new(frame_id, payload_size, nil, frame_payload, HEADER_SIZE_V_2, nil)
30
+ instance.options = options
31
+ instance.process_content(frame_payload)
32
+ instance
33
+ end
34
+
35
+ # builds an id3v2.3 frame of the given frame id and the given data stream. The data stream(0) must be after
36
+ # the frame id
37
+ #
38
+ # @param frame_id [String] the frame id
39
+ # @param file [StringIO, IO, file] the data stream
40
+ # @param options [Options::Options] the options to use
41
+ #
42
+ # @return [Id3v2Frame] the build id3v2.3 frame
43
+ def self.build_v3_frame(frame_id, file, options)
44
+ payload_size = Util::MathUtil.to_number(file.read(4)&.bytes)
45
+ flags = Id3v23FrameFlags.new(file.read(2))
46
+ decompressed_size = Util::MathUtil.to_number(file.read(4)&.bytes) if flags.compression?
47
+
48
+ if flags.compression?
49
+ compressed_data = file.read(payload_size)
50
+ raise Errors::Id3TagError, "Could not find any Frame data for #{frame_id}." if compressed_data.nil?
51
+
52
+ frame_payload = Util::CompressUtil.decompress_data(compressed_data)
53
+ # noinspection RubyScope
54
+ payload_size = decompressed_size
55
+ else
56
+ frame_payload = file.read(payload_size)
57
+ end
58
+
59
+ group_identify = nil
60
+ if flags.group_identity?
61
+ group_identify = frame_payload[0]
62
+ frame_payload = frame_payload[1..-1]
63
+ end
64
+
65
+ raise Errors::Id3TagError, "Could not find any Frame data for #{frame_id}." if frame_payload.nil?
66
+
67
+ instance = new(frame_id, payload_size, flags, frame_payload, HEADER_SIZE_V_3_4, group_identify)
68
+ instance.options = options
69
+ instance.process_content(frame_payload)
70
+ instance
71
+ end
72
+
73
+ # builds an id3v2.4 frame of the given frame id and the given data stream. The data stream(0) must be after
74
+ # the frame id
75
+ #
76
+ # @param frame_id [String] the frame id
77
+ # @param file [StringIO, IO, file] the data stream
78
+ # @param options [Options::Options] the options to use
79
+ #
80
+ # @return [Id3v2Frame] the build id3v2.4 frame
81
+ def self.build_v4_frame(frame_id, file, options)
82
+ payload_size = Util::MathUtil.to_32_synchsafe_integer(file.read(4)&.bytes)
83
+ flags = Id3v24FrameFlags.new(file.read(2))
84
+
85
+ frame_payload = file.read(payload_size)
86
+ raise Errors::Id3TagError, "Could not find any Frame data for #{frame_id}." if frame_payload.nil?
87
+
88
+ frame_payload = Util::SyncUtil.undo_synchronization(StringIO.new(frame_payload)) if flags.unsynchronisation?
89
+ frame_payload = Util::CompressUtil.decompress_data(frame_payload) if flags.compression?
90
+
91
+ group_identify = nil
92
+ if flags.group_identity?
93
+ group_identify = frame_payload[0]
94
+ frame_payload = frame_payload[1..-1]
95
+ end
96
+
97
+ raise Errors::Id3TagError, "Could not find any Frame data for #{frame_id}." if frame_payload.nil?
98
+
99
+ instance = new(frame_id, payload_size, flags, frame_payload, HEADER_SIZE_V_3_4, group_identify)
100
+ instance.options = options
101
+ instance.process_content(frame_payload)
102
+ instance
103
+ end
104
+
105
+ def self.build_id3_flags(version, flags = "\x00\x00")
106
+ case version
107
+ when 2
108
+ nil
109
+ when 3
110
+ Id3v23FrameFlags.new(flags)
111
+ when 4
112
+ Id3v24FrameFlags.new(flags)
113
+ else
114
+ raise Errors::Id3TagError, "Id3v.2.#{version} is not supported."
115
+ end
116
+ end
117
+
118
+ # Constructor
119
+ #
120
+ # @param frame_id [String] the frame id
121
+ # @param payload_size [Integer] the payload size (excludes header)
122
+ # @param flags [Id3v23FrameFlags, Id3v24FrameFlags, nil] the frame flags
123
+ # @param frame_payload [String] the decompressed and unsynchronized payload
124
+ # @param header_size [Integer] the frame header size, 6 vor v2, 10 otherwise
125
+ # @param group_identify [String, nil] the group identify if present
126
+ def initialize(frame_id, payload_size, flags, frame_payload, header_size = 10, group_identify = nil)
127
+ @header_size = header_size
128
+ @frame_id = frame_id.to_sym
129
+ @payload_size = payload_size
130
+ @flags = flags
131
+ @frame_payload = frame_payload
132
+ @group_identity = group_identify
133
+ end
134
+
135
+ # processes the frame payload for the specific frame
136
+ #
137
+ # @param _content [String] the frame payload
138
+ def process_content(_content)
139
+ raise NotImplementedError, 'Implement this in a the actual frame class.'
140
+ end
141
+
142
+ # dumps the frame content to the byte representation as a String
143
+ #
144
+ # @return [String] the byte array as String
145
+ def content_to_bytes
146
+ raise NotImplementedError, 'Implement this in the actual frame class.'
147
+ end
148
+
149
+ # dumps the frame to a byte string. This dump already takes unsynchronization, padding and all other
150
+ # options into effect
151
+ #
152
+ # @return [String] frame dump as a String. tag.bytes represents the byte array
153
+ def to_bytes
154
+ size_padding = @frame_id.to_s.length == 3 ? 6 : 8
155
+ payload = content_to_bytes
156
+ payload_size_decompressed = Util::MathUtil.from_number(payload.length, size_padding)
157
+
158
+ result = @frame_id.to_s
159
+ if @flags&.compression?
160
+ payload_compressed = Util::CompressUtil.compress_data(payload)
161
+ payload_size_compressed = payload_compressed.size
162
+ result += Util::MathUtil.from_number(payload_size_compressed, size_padding)
163
+ else
164
+ result += payload_size_decompressed
165
+ end
166
+
167
+ result += @flags.to_bytes unless @flags.nil?
168
+ result += payload_size_decompressed if @flags&.compression?
169
+ result += @group_identity if @flags&.group_identity?
170
+
171
+ # noinspection RubyScope
172
+ result += @flags&.compression? ? payload_compressed : payload
173
+
174
+ result
175
+ end
176
+
177
+ # recalculates the payload size
178
+ def re_calc_payload_size
179
+ @payload_size = content_to_bytes.length
180
+ end
181
+
182
+ # calculates the frame size including header and payload, takes compression, group identity and everything
183
+ # into effect
184
+ #
185
+ # @return [Integer] the frame size in bytes
186
+ def frame_size
187
+ compression = compression? ? 4 : 0
188
+ group_identity = group_identity? ? 1 : 0
189
+
190
+ # noinspection RubyMismatchedReturnType
191
+ @header_size + @payload_size + compression + group_identity
192
+ end
193
+
194
+ # determined if the frame is alter preserved
195
+ #
196
+ # @return [Boolean] true if alter preserved, else false
197
+ def tag_alter_preservation?
198
+ return false if @flags.nil?
199
+
200
+ @flags.tag_alter_preservation?
201
+ end
202
+
203
+ # determined if the file is alter preserved
204
+ #
205
+ # @return [Boolean] true if the file is alter preserved, else false
206
+ def file_alter_preservation?
207
+ return false if @flags.nil?
208
+
209
+ @flags.file_alter_preservation?
210
+ end
211
+
212
+ # determined if the file frame is read only
213
+ #
214
+ # @return [Boolean] true if frame is read only, else false
215
+ def read_only?
216
+ return false if @flags.nil?
217
+
218
+ @flags.read_only?
219
+ end
220
+
221
+ # determined if the frame is compressed
222
+ #
223
+ # @return [Boolean] true if the frame is compressed, else false
224
+ def compression?
225
+ return false if @flags.nil?
226
+
227
+ @flags.compression?
228
+ end
229
+
230
+ # determined if the frame is encrypted
231
+ #
232
+ # @return [Boolean] true if the frame is encrypted, else false
233
+ def encryption?
234
+ return false if @flags.nil?
235
+
236
+ @flags.encryption?
237
+ end
238
+
239
+ # determined if the frame has a group identity
240
+ #
241
+ # @return [Boolean] true if the frame has a group identity, else false
242
+ def group_identity?
243
+ return false if @flags.nil?
244
+
245
+ @flags.group_identity?
246
+ end
247
+ end
248
+ end
249
+ end