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,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Count
6
+ class PlayCounterFrame < Id3v2Frame
7
+ include HasId
8
+
9
+ frame_info :CNT, :PCNT, :PCNT
10
+
11
+ attr_accessor :counter
12
+
13
+ # builds the play counter frame
14
+ #
15
+ # @param counter [Integer] the counter, default a 32 bit integer, but can be higher too
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(counter, options = nil, id3_version: 3)
21
+ supported?('PCNT', id3_version, options)
22
+
23
+ argument_not_nil(counter, 'counter')
24
+
25
+ frame = new(frame_id(id3_version, options), 0, build_id3_flags(id3_version), '')
26
+ frame.counter = counter
27
+ frame
28
+ end
29
+
30
+ def process_content(content)
31
+ @counter = Util::MathUtil.to_number(content.bytes)
32
+ end
33
+
34
+ def content_to_bytes
35
+ Util::MathUtil.from_number(@counter, 8, '0')
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Count
6
+ class PopularityFrame < Id3v2Frame
7
+ include HasId
8
+
9
+ frame_info :POP, :POPM, :POPM
10
+
11
+ attr_accessor :email, :rating, :counter
12
+
13
+ # builds the popularimeter frame
14
+ #
15
+ # @param email [String] email of the user
16
+ # @param rating [Integer] the rating between 0 and 255
17
+ # @param counter [Integer] the counter, default 32 bit integer, but can be higher too
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(email, rating, counter, options = nil, id3_version: 3)
23
+ supported?('POPM', id3_version, options)
24
+
25
+ argument_not_nil(email, 'email')
26
+ argument_not_nil(counter, 'counter')
27
+
28
+ rating ||= 0
29
+ argument_between_num(rating, 'rating', 0, 255)
30
+
31
+ frame = new(frame_id(id3_version, options), 0, build_id3_flags(id3_version), '')
32
+ frame.email = email
33
+ frame.rating = rating
34
+ frame.counter = counter
35
+ frame
36
+ end
37
+
38
+ def process_content(content)
39
+ stream = StringIO.new(content)
40
+ @email = read_stream_until(stream, ZERO)
41
+ @rating = stream.readbyte
42
+ @counter = Util::MathUtil.to_number(stream.read&.bytes)
43
+ end
44
+
45
+ def content_to_bytes
46
+ counter = Util::MathUtil.from_number(@counter, 8, '0')
47
+ merge(@email, "\x00", @rating.chr, counter)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module CountFrames
6
+ include Frames::Frameable
7
+
8
+ # extracts the play count (PCNT/CNT)
9
+ #
10
+ # @return [Integer, nil] returns counter
11
+ def play_counter
12
+ find_frame(Count::PlayCounterFrame.frame_id(@major_version, @options))&.counter
13
+ end
14
+
15
+ # sets a counter (PCNT/CNT)
16
+ #
17
+ # @param counter [Integer] the counter to set
18
+ def play_counter=(counter)
19
+ set_frame_fields(Count::PlayCounterFrame, [:@counter], counter)
20
+ end
21
+
22
+ # removes the play counter frame
23
+ def remove_play_counter
24
+ @frames.delete_if { |f| f.frame_id == Count::PlayCounterFrame.frame_id(@major_version, @options) }
25
+ end
26
+
27
+ # extracts the popularity (POPM/POP)
28
+ #
29
+ # @return [Frames::Count::Entities::Popularimeter, nil] returns popularity
30
+ def popularity
31
+ frame = find_frame(Count::PopularityFrame.frame_id(@major_version, @options))
32
+ return nil if frame.nil?
33
+
34
+ Count::Entities::Popularimeter.new(frame.email, frame.rating, frame.counter)
35
+ end
36
+
37
+ # sets a counter (POPM/POP)
38
+ #
39
+ # @param popularity [Frames::Count::Entities::Popularimeter] the popularity to set
40
+ def popularity=(popularity)
41
+ set_frame_fields(Count::PopularityFrame, %i[@email @rating @counter],
42
+ popularity.email, popularity.rating, popularity.counter)
43
+ end
44
+
45
+ # removes the popularity frame
46
+ def remove_popularity
47
+ @frames.delete_if { |f| f.frame_id == Count::PopularityFrame.frame_id(@major_version, @options) }
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ class CustomFrame < Id3v2Frame
6
+
7
+ attr_accessor :content
8
+
9
+ # builds a custom frame
10
+ #
11
+ # @param content [String] the content
12
+ # @param options [Options::Options] options to use
13
+ # @param id3_version [Integer] the id3 version to build the frame for
14
+ #
15
+ # @return [Id3v2Frame] the resulting id3v2 frame
16
+ def self.build_frame(content, _options = nil, id3_version: 3)
17
+ argument_not_nil(content, 'content')
18
+
19
+ frame = new(frame_id, 0, build_id3_flags(id3_version), '')
20
+ frame.content = content
21
+ frame
22
+ end
23
+
24
+ # takes the content as it is, no encoding or other modifications are performed
25
+ #
26
+ # @param content [String] the content
27
+ def process_content(content)
28
+ @content = content
29
+ end
30
+
31
+ # returns the content as it is, no encoding or other modifications are performed
32
+ #
33
+ # @return [String] the content
34
+ def content_to_bytes
35
+ @content
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module CustomFrames
6
+ include Frames::Frameable
7
+
8
+ # extracts a custom frame for the given id
9
+ #
10
+ # @param frame_id [Symbol] the frame id
11
+ #
12
+ # @return [String, nil] returns the frame's content data bytes represented as a String (str.bytes)
13
+ def custom_frame(frame_id)
14
+ find_frame(frame_id)&.content
15
+ end
16
+
17
+ # adds a custom frame
18
+ # Multiple ones can be added, the selector must be given to determine if a frame should be updated or added
19
+ #
20
+ # @param frame_id [Symbol] the frame id
21
+ # @param content [String] the frame content's data bytes represented as a String (str.bytes)
22
+ # @param selector_lambda [Proc] the lambda to find matching frames to update
23
+ def add_custom_frame(frame_id, content, selector_lambda = nil)
24
+ if selector_lambda.nil?
25
+ existing_frame = find_frame(frame_id)
26
+ else
27
+ existing_frames = find_frames(frame_id)
28
+ existing_frame = existing_frames&.find { |f| selector_lambda.call(f) }
29
+ end
30
+
31
+ unless existing_frame.nil?
32
+ existing_frame.content = content
33
+ return
34
+ end
35
+
36
+ new_frame = CustomFrame.build_frame(content, @options, id3_version: @major_version)
37
+ new_frame.options = @options
38
+ @frames << new_frame
39
+ end
40
+
41
+ # removes the frame with the given id or a specific one with the given selector
42
+ #
43
+ # @param frame_id [String] the frame id to remove
44
+ # @param selector_lambda [Proc] the lambda to find matching frames to update
45
+ def remove_custom_frame(frame_id, selector_lambda = nil)
46
+ if selector_lambda.nil?
47
+ @frames.delete_if { |f| f.frame_id == frame_id }
48
+ else
49
+ @frames.delete_if { |f| f.frame_id == frame_id && selector_lambda.call(f) }
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Encryption
6
+ class AudioEncryptionFrame < Id3v2Frame
7
+ include HasId
8
+
9
+ frame_info :CRA, :AENC, :AENC
10
+
11
+ attr_accessor :owner_id, :preview_start, :preview_length, :encryption_info
12
+
13
+ # builds the audio encryption frame
14
+ #
15
+ # @param owner_id [String] the owner id
16
+ # @param preview_start [Integer] the start frame of the preview
17
+ # @param preview_length [Integer] the preview length in frames
18
+ # @param encryption_info [String] encryption info bytes represented as a String (str.bytes)
19
+ # @param options [Options::Options] options to use
20
+ # @param id3_version [Integer] the id3 version to build the frame for
21
+ #
22
+ # @return [Id3v2Frame] the resulting id3v2 frame
23
+ def self.build_frame(owner_id, preview_start, preview_length, encryption_info, options = nil, id3_version: 3)
24
+ supported?('AENC', id3_version, options)
25
+
26
+ argument_not_nil(owner_id, 'owner_id')
27
+ argument_not_nil(preview_start, 'preview_start')
28
+ argument_not_nil(preview_length, 'preview_length')
29
+ argument_not_nil(encryption_info, 'encryption_info')
30
+
31
+ frame = new(frame_id(id3_version, options), 0, build_id3_flags(id3_version), '')
32
+ frame.owner_id = owner_id
33
+ frame.preview_start = preview_start
34
+ frame.preview_length = preview_length
35
+ frame.encryption_info = encryption_info
36
+ frame
37
+ end
38
+
39
+ def process_content(content)
40
+ stream = StringIO.new(content)
41
+ @owner_id = read_stream_until(stream, ZERO)
42
+ @preview_start = Util::MathUtil.to_number(stream.read(2)&.bytes)
43
+ @preview_length = Util::MathUtil.to_number(stream.read(2)&.bytes)
44
+ @encryption_info = stream.read
45
+ end
46
+
47
+ def content_to_bytes
48
+ merge(@owner_id, "\x00", Util::MathUtil.from_number(@preview_start, 4),
49
+ Util::MathUtil.from_number(@preview_length, 4), @encryption_info)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Encryption
6
+ class EncryptionMethodFrame < Id3v2Frame
7
+ include HasId
8
+
9
+ frame_info nil, :ENCR, :ENCR
10
+
11
+ attr_accessor :owner_id, :method_symbol, :encryption_data
12
+
13
+ # builds the encryption method frame
14
+ #
15
+ # @param owner_id [String] the owner id
16
+ # @param method_symbol [Integer] method symbol between 0 and 255
17
+ # @param encryption_data [String] encryption 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, method_symbol, encryption_data, options = nil, id3_version: 3)
23
+ supported?('ENCR', id3_version, options)
24
+
25
+ argument_not_nil(owner_id, 'owner_id')
26
+ argument_between_num(method_symbol, 'method_symbol', 0, 255)
27
+ argument_not_nil(encryption_data, 'encryption_data')
28
+
29
+ frame = new(frame_id(id3_version, options), 0, build_id3_flags(id3_version), '')
30
+ frame.owner_id = owner_id
31
+ frame.method_symbol = method_symbol
32
+ frame.encryption_data = encryption_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
+ @method_symbol = stream.readbyte
40
+ @encryption_data = stream.read
41
+ end
42
+
43
+ def content_to_bytes
44
+ merge(@owner_id, "\x00", @method_symbol.chr, @encryption_data)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Encryption
6
+ module Entities
7
+ class AudioEncryption
8
+ include Extensions::Comparable
9
+
10
+ attr_accessor :owner_id, :preview_start, :preview_length, :encryption_info
11
+
12
+ # constructor
13
+ #
14
+ # @param owner_id [String] the owner id
15
+ # @param preview_start [Integer] the start frame of the preview
16
+ # @param preview_length [Integer] the preview length in frames
17
+ # @param encryption_info [String] encryption info represented as a String
18
+ def initialize(owner_id, preview_start, preview_length, encryption_info)
19
+ @owner_id = owner_id
20
+ @preview_start = preview_start
21
+ @preview_length = preview_length
22
+ @encryption_info = encryption_info
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Encryption
6
+ module Entities
7
+ class EncryptionMethod
8
+ include Extensions::Comparable
9
+
10
+ attr_accessor :owner_id, :method_symbol, :encryption_data
11
+
12
+ # constructor
13
+ #
14
+ # @param owner_id [String] the owner id
15
+ # @param method_symbol [Integer] method symbol between 0 and 255
16
+ # @param encryption_data [String] encryption data represented as a String
17
+ def initialize(owner_id, method_symbol, encryption_data)
18
+ @owner_id = owner_id
19
+ @method_symbol = method_symbol
20
+ @encryption_data = encryption_data
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module EncryptionFrames
6
+ include Frames::Frameable
7
+
8
+ # extracts the audio encryptions (AENC/CRA)
9
+ #
10
+ # @return [Array<Frames::Encryption::Entities::AudioEncryption>] returns the Audio Encryptions
11
+ def audio_encryptions
12
+ frame = find_frames(Encryption::AudioEncryptionFrame.frame_id(@major_version, @options))
13
+ return [] if frame.nil? || frame.empty?
14
+
15
+ frame.map do |f|
16
+ Encryption::Entities::AudioEncryption.new(f.owner_id, f.preview_start, f.preview_length, f.encryption_info)
17
+ end
18
+ end
19
+
20
+ # adds an audio encryption. (AENC/CRA)
21
+ # Multiple ones can be added, as long as they have different owner_ids
22
+ #
23
+ # @param enc [Frames::Encryption::Entities::AudioEncryption] the audio encryption
24
+ def audio_encryptions=(enc)
25
+ set_frame_fields_by_selector(Encryption::AudioEncryptionFrame,
26
+ %i[@preview_start @preview_length @encryption_info],
27
+ ->(f) { f.owner_id == enc.owner_id },
28
+ enc.owner_id, enc.preview_start, enc.preview_length, enc.encryption_info)
29
+ end
30
+
31
+ alias add_audio_encryption audio_encryptions=
32
+
33
+ # removes an audio encryption for the specific owner_id (AENC/CRA)
34
+ #
35
+ # @param owner_id [String] the owner_id
36
+ def remove_audio_encryption(owner_id)
37
+ @frames.delete_if do |f|
38
+ f.frame_id == Encryption::AudioEncryptionFrame.frame_id(@major_version, @options) && f.owner_id == owner_id
39
+ end
40
+ end
41
+
42
+ # extracts the encryption methods (ENCR)
43
+ #
44
+ # @return [Array<Frames::Encryption::Entities::EncryptionMethod>] returns the Encryption Methods
45
+ def encryption_methods
46
+ frame = find_frames(Encryption::EncryptionMethodFrame.frame_id(@major_version, @options))
47
+ return [] if frame.nil? || frame.empty?
48
+
49
+ frame.map do |f|
50
+ Encryption::Entities::EncryptionMethod.new(f.owner_id, f.method_symbol, f.encryption_data)
51
+ end
52
+ end
53
+
54
+ # adds an encryption method. (ENCR)
55
+ # Multiple ones can be added, as long as they have different owner_ids
56
+ #
57
+ # @param enc [Frames::Encryption::Entities::AudioEncryption] the audio encryption
58
+ def encryption_methods=(enc)
59
+ set_frame_fields_by_selector(Encryption::EncryptionMethodFrame,
60
+ %i[@owner_id @method_symbol @encryption_data],
61
+ ->(f) { f.owner_id == enc.owner_id },
62
+ enc.owner_id, enc.method_symbol, enc.encryption_data)
63
+ end
64
+
65
+ alias add_encryption_method encryption_methods=
66
+
67
+ # removes an encryption method for the specific owner_id (ENCR)
68
+ #
69
+ # @param owner_id [String] the owner_id
70
+ def remove_encryption_method(owner_id)
71
+ @frames.delete_if do |f|
72
+ f.frame_id == Encryption::EncryptionMethodFrame.frame_id(@major_version, @options) && f.owner_id == owner_id
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Frameable
6
+ # raises an error that the given frame is not available for the id3v2 version
7
+ def unsupported_frame(frame_name, version)
8
+ raise Errors::Id3TagError, "#{frame_name} is not supported in Id3v2.#{version}"
9
+ end
10
+
11
+ # finds the frame with the given frame id, or nil of not present
12
+ #
13
+ # @param frame_id [Symbol] the frame id to search for
14
+ #
15
+ # @return [Id3v2Frame, nil] the found frame or nil, if not present
16
+ def find_frame(frame_id)
17
+ @frames.find { |f| f.frame_id == frame_id }
18
+ end
19
+
20
+ # finds the frames with the given frame id, or nil of not present
21
+ #
22
+ # @param frame_id [Symbol] the frame id to search for
23
+ #
24
+ # @return [Array<Id3v2Frame>, nil] the found frames or nil, if not present
25
+ def find_frames(frame_id)
26
+ @frames.select { |f| f.frame_id == frame_id }
27
+ end
28
+
29
+ # sets the given arguments to the appropriate field of the given frame. The field order must be same as the
30
+ # argument order. So e.g. fields a, b, c and arguments a_v, b_v, c_v will lead to a = a_v, b = b_v and c = c_v
31
+ # If the frame already exists, it will be updated. Otherwise it will be created.
32
+ #
33
+ # @param frame [Class<Id3v2Frame>] the frame to set the fields too
34
+ # @param fields [Array<Symbol>] the fields to set, must be in the same order as the arguments
35
+ # @param arguments [Array<Object>] the values to set to the fields
36
+ def set_frame_fields(frame, fields, *arguments)
37
+ existing_frame = find_frame(frame.frame_id(@major_version, @options))
38
+ unless existing_frame.nil?
39
+ fields.each_with_index { |field, index| existing_frame.instance_variable_set(field, arguments[index]) }
40
+ return
41
+ end
42
+
43
+ new_frame = frame.build_frame(*arguments, @options, id3_version: @major_version)
44
+ new_frame.options = @options
45
+ @frames << new_frame
46
+ end
47
+
48
+ # sets the given arguments to the appropriate field of the given frame(s). The field order must be same as the
49
+ # argument order. So e.g. fields a, b, c and arguments a_v, b_v, c_v will lead to a = a_v, b = b_v and c = c_v
50
+ # If the frame already exists, it will be updated. Otherwise it will be created.
51
+ # The field will be determined by the given selector_lambda, so e.g.
52
+ # ->(f) { f.descriptor == 'a value' }
53
+ # will be updated, if the frame.descriptor is 'a value'
54
+ #
55
+ # @param frame [Class<Id3v2Frame>] the frame to set the fields too
56
+ # @param fields [Array<Symbol>] the fields to set, must be in the same order as the arguments
57
+ # @param selector_lambda [Proc] the lambda to find matching frames to update
58
+ # @param arguments [Array<Object>] the values to set to the fields
59
+ def set_frame_fields_by_selector(frame, fields, selector_lambda, *arguments)
60
+ existing_frames = find_frames(frame.frame_id(@major_version, @options))
61
+
62
+ existing_frame = existing_frames&.find { |f| selector_lambda.call(f) }
63
+
64
+ unless existing_frame.nil?
65
+ fields.each_with_index { |field, index| existing_frame.instance_variable_set(field, arguments[index]) }
66
+ return
67
+ end
68
+
69
+ new_frame = frame.build_frame(*arguments, @options, id3_version: @major_version)
70
+ new_frame.options = @options
71
+ @frames << new_frame
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Geo
6
+ module Entities
7
+ class EncapsulatedObject
8
+ include Extensions::Comparable
9
+
10
+ attr_accessor :mime_type, :filename, :descriptor, :object_data
11
+
12
+ # constructor
13
+ #
14
+ # @param mime_type [String] the mime type of the object e.g. image/png
15
+ # @param filename [String] the filename
16
+ # @param descriptor [String] the description
17
+ # @param object_data [String] the object data bytes represented as a String (str.bytes)
18
+ def initialize(mime_type, filename, descriptor, object_data)
19
+ @mime_type = mime_type
20
+ @filename = filename
21
+ @descriptor = descriptor
22
+ @object_data = object_data
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Geo
6
+ class GeneralEncapsulatedObjectFrame < Id3v2Frame
7
+ include HasId
8
+
9
+ frame_info :GEO, :GEOB, :GEOB
10
+
11
+ attr_accessor :mime_type, :filename, :descriptor, :object_data
12
+
13
+ # builds the general encapsulated object frame
14
+ #
15
+ # @param mime_type [String] the mime type of the object e.g. image/png
16
+ # @param filename [String] the filename
17
+ # @param descriptor [String] the description
18
+ # @param object_data [String] the object data bytes represented as a String (str.bytes)
19
+ # @param options [Options::Options] options to use
20
+ # @param id3_version [Integer] the id3 version to build the frame for
21
+ #
22
+ # @return [Id3v2Frame] the resulting id3v2 frame
23
+ def self.build_frame(mime_type, filename, descriptor, object_data, options = nil, id3_version: 3)
24
+ supported?('GEOB', id3_version, options)
25
+
26
+ mime_type ||= ''
27
+ filename ||= ''
28
+ descriptor ||= ''
29
+ argument_not_nil(object_data, 'object_data')
30
+
31
+ frame = new(frame_id(id3_version, options), 0, build_id3_flags(id3_version), '')
32
+ frame.mime_type = mime_type
33
+ frame.filename = filename
34
+ frame.descriptor = descriptor
35
+ frame.object_data = object_data
36
+ frame
37
+ end
38
+
39
+ def process_content(content)
40
+ stream = StringIO.new(content)
41
+ encoding = find_encoding(stream.readbyte)
42
+
43
+ @mime_type = read_stream_until(stream, ZERO)
44
+ encoded_filename = read_stream_until(stream, zero_byte(encoding))
45
+ @filename = decode(encoded_filename, encoding)
46
+ encoded_descriptor = read_stream_until(stream, zero_byte(encoding))
47
+ @descriptor = decode(encoded_descriptor, encoding)
48
+ @object_data = stream.read
49
+ end
50
+
51
+ def content_to_bytes
52
+ encoded_filename = encode(@filename, null_terminated: true)
53
+ encoded_descriptor = encode(@descriptor, null_terminated: true)
54
+ merge(default_encoding_destination_byte, @mime_type, ZERO, encoded_filename, encoded_descriptor, @object_data)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module GeoFrames
6
+ include Frames::Frameable
7
+
8
+ # extracts the general encapsulated objects (GEOB/GEO)
9
+ #
10
+ # @return [Array<Frames::Geo::Entities::EncapsulatedObject>] returns the general encapsulated objects
11
+ def encapsulated_objects
12
+ frame = find_frames(Geo::GeneralEncapsulatedObjectFrame.frame_id(@major_version, @options))
13
+ return [] if frame.nil? || frame.empty?
14
+
15
+ frame.map { |f| Geo::Entities::EncapsulatedObject.new(f.mime_type, f.filename, f.descriptor, f.object_data) }
16
+ end
17
+
18
+ # adds a general encapsulated object (GEOB/GEO)
19
+ #
20
+ # @param object [Frames::Geo::Entities::EncapsulatedObject] the object to add
21
+ def encapsulated_object=(object)
22
+ set_frame_fields_by_selector(Geo::GeneralEncapsulatedObjectFrame, %i[@mime_type @filename @descriptor
23
+ @object_data],
24
+ ->(f) { f.descriptor == object.descriptor },
25
+ object.mime_type, object.filename, object.descriptor, object.object_data)
26
+ end
27
+
28
+ alias add_encapsulated_object encapsulated_object=
29
+
30
+ # removes an encapsulated object for the specific descriptor
31
+ #
32
+ # @param descriptor [String] the descriptor
33
+ def remove_encapsulated_object(descriptor)
34
+ @frames.delete_if do |f|
35
+ f.frame_id == Geo::GeneralEncapsulatedObjectFrame.frame_id(@major_version, @options) &&
36
+ f.descriptor == descriptor
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end