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,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ class Id3v2FrameFactory
6
+
7
+ BUFFER_FRAMES = [Buffer::RecommendedBufferSizeFrame].freeze
8
+
9
+ COMMENT_FRAMES = [Comment::CommentFrame].freeze
10
+
11
+ COUNT_FRAMES = [Count::PopularityFrame, Count::PlayCounterFrame].freeze
12
+
13
+ ENCRYPTION_FRAMES = [Encryption::AudioEncryptionFrame, Encryption::EncryptionMethodFrame].freeze
14
+
15
+ GEO_FRAMES = [Geo::GeneralEncapsulatedObjectFrame].freeze
16
+
17
+ GROUPING_FRAMES = [Grouping::GroupIdentificationFrame, Grouping::GroupingFrame].freeze
18
+
19
+ IPL_FRAMES = [Ipl::InvolvedPeopleFrame].freeze
20
+
21
+ LYRICS_FRAMES = [Lyrics::UnsyncLyricsFrame].freeze
22
+
23
+ MCDI_FRAMES = [Mcdi::MusicCDIdentifierFrame].freeze
24
+
25
+ PICTURE_FRAMES = [Picture::PictureFrame].freeze
26
+
27
+ PRIVATE_FRAMES = [Private::PrivateFrame].freeze
28
+
29
+ TEXT_FRAMES = [Text::AlbumFrame, Text::BPMFrame, Text::ComposerFrame, Text::GenreFrame, Text::CopyrightFrame,
30
+ Text::DateFrame, Text::PlaylistDelayFrame, Text::EncodedByFrame, Text::WritersFrame,
31
+ Text::FileTypeFrame, Text::TimeFrame, Text::ContentGroupDescriptionFrame, Text::TitleFrame,
32
+ Text::SubtitleFrame, Text::InitialKeyFrame, Text::LanguageFrame, Text::LengthFrame,
33
+ Text::MediaTypeFrame, Text::OriginalAlbumFrame, Text::OriginalFilenameFrame,
34
+ Text::OriginalWritersFrame, Text::OriginalArtistsFrame, Text::OriginalReleaseYearFrame,
35
+ Text::FileOwnerFrame, Text::ArtistsFrame, Text::AlbumArtistFrame, Text::ConductorFrame,
36
+ Text::ModifiedByFrame, Text::PartOfSetFrame, Text::PublisherFrame, Text::TrackNumberFrame,
37
+ Text::RecordingDatesFrame, Text::InternetRadioStationFrame, Text::SizeFrame,
38
+ Text::AlbumSortOrderFrame, Text::PerformerSortOrderFrame, Text::TitleSortOrderFrame,
39
+ Text::ISRCFrame, Text::EncoderFrame, Text::UserTextInfoFrame, Text::YearFrame].freeze
40
+
41
+ TOS_FRAMES = [Tos::OwnershipFrame, Tos::TermsOfUseFrame].freeze
42
+
43
+ UFID_FRAMES = [Ufid::UniqueFileIdentifierFrame].freeze
44
+
45
+ URL_FRAMES = [Url::CommercialUrlFrame, Url::CopyrightUrlFrame, Url::OfficialFileWebpageFrame,
46
+ Url::OfficialArtistWebpageFrame, Url::OfficialSourceWebpageFrame,
47
+ Url::OfficialAudioRadioStationHomepageFrame, Url::PaymentUrlFrame,
48
+ Url::OfficialPublisherWebpageFrame, Url::UserUrlLinkFrame].freeze
49
+
50
+ # Constructor
51
+ #
52
+ # @param file [StringIO, IO, File] the data stream
53
+ # @param version [Integer] the Id3tag major version - 2, 3 or 4
54
+ # @param options [Options::Options] the options to use
55
+ def initialize(file, version, options)
56
+ @file = file
57
+ @version = version
58
+ @options = options
59
+ end
60
+
61
+ # reads the next frame in the data stream
62
+ #
63
+ # @return [Id3v2Frame, nil] returns the parsed Id3v2 frame or nil of no frame is found (end of Tag and\or
64
+ # only padding left)
65
+ def next_frame
66
+ frame_id = @version == 2 ? @file.read(3) : @file.read(4)
67
+ return nil if frame_id.bytes.all?(&:zero?)
68
+
69
+ frame = frame_for_id(frame_id)
70
+ case @version
71
+ when 2
72
+ frame&.build_v2_frame(frame_id, @file, @options)
73
+ when 3
74
+ frame&.build_v3_frame(frame_id, @file, @options)
75
+ when 4
76
+ frame&.build_v4_frame(frame_id, @file, @options)
77
+ else
78
+ raise Errors::Id3TagError, "id3v2.#{@version} is not supported."
79
+ end
80
+ end
81
+
82
+ # returns the frame for the given frame id
83
+ #
84
+ # @param frame_id [String] the frame id to get the Frame for
85
+ #
86
+ # @return [Class<Id3v2Frame>] the frame for the given frame id
87
+ def frame_for_id(frame_id)
88
+ frame = (BUFFER_FRAMES + COMMENT_FRAMES + COUNT_FRAMES + ENCRYPTION_FRAMES + GEO_FRAMES + GROUPING_FRAMES +
89
+ IPL_FRAMES + LYRICS_FRAMES + MCDI_FRAMES + PICTURE_FRAMES + PRIVATE_FRAMES + TOS_FRAMES + TEXT_FRAMES +
90
+ UFID_FRAMES + URL_FRAMES).find { |f| f.frame_id(@version, @options) == frame_id.to_sym }
91
+ # noinspection RubyMismatchedReturnType
92
+ frame.nil? ? CustomFrame : frame
93
+ end
94
+
95
+ private :frame_for_id
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Ipl
6
+ module Entities
7
+ class InvolvedPerson
8
+ include Extensions::Comparable
9
+
10
+ attr_accessor :involvement, :involvee
11
+
12
+ # constructor
13
+ #
14
+ # @param involvement [String] the involvement of the person
15
+ # @param involvee [String] the involvee, e.g. the name of the person
16
+ def initialize(involvement, involvee)
17
+ @involvement = involvement
18
+ @involvee = involvee
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Ipl
6
+ class InvolvedPeopleFrame < Id3v2Frame
7
+ include HasId
8
+
9
+ frame_info :IPL, :IPLS, :IPLS
10
+
11
+ attr_accessor :involved_people
12
+
13
+ # builds the group identification frame
14
+ #
15
+ # @param involved_people [Array<Entities::InvolvedPerson>, Entities::InvolvedPerson] the involved people
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(involved_people, options = nil, id3_version: 3)
21
+ supported?('IPLS', id3_version, options)
22
+
23
+ argument_not_empty(involved_people, 'involved_people')
24
+
25
+ frame = new(frame_id(id3_version, options), 0, build_id3_flags(id3_version), '')
26
+ frame.involved_people = involved_people.is_a?(Array) ? involved_people : [involved_people]
27
+ frame
28
+ end
29
+
30
+ def process_content(content)
31
+ content = decode_using_encoding_byte(content).split("\x00")
32
+ @involved_people = []
33
+ until content.empty?
34
+ involvement = content.shift
35
+ involvee = content.shift
36
+ person = Entities::InvolvedPerson.new(involvement, involvee)
37
+ @involved_people << person
38
+ end
39
+ end
40
+
41
+ def content_to_bytes
42
+ content = @involved_people.map { |person| "#{person.involvement}\x00#{person.involvee}" }.join("\x00")
43
+ encode_and_add_encoding_byte(content)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module IplFrames
6
+ include Frames::Frameable
7
+
8
+ # extracts the involved people list (IPLS/IPL)
9
+ #
10
+ # @return [Array<Frames::Ipl::Entities::InvolvedPerson>] returns a list of involved people
11
+ def involved_people
12
+ frame = find_frame(Ipl::InvolvedPeopleFrame.frame_id(@major_version, @options))
13
+ return [] if frame.nil?
14
+
15
+ frame.involved_people
16
+ end
17
+
18
+ # sets the involved people (IPLS/IPL)
19
+ #
20
+ # @param involved_people [Array<Frames::Ipl::Entities::InvolvedPerson>] the involved people
21
+ def involved_people=(involved_people)
22
+ set_frame_fields(Ipl::InvolvedPeopleFrame, [:@involved_people], involved_people)
23
+ end
24
+
25
+ # removes the involved people frame
26
+ def remove_involved_people
27
+ @frames.delete_if { |f| f.frame_id == Ipl::InvolvedPeopleFrame.frame_id(@major_version, @options) }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Lyrics
6
+ module Entities
7
+ class UnsyncLyrics
8
+ include Extensions::Comparable
9
+
10
+ attr_accessor :language, :descriptor, :lyrics
11
+
12
+ # constructor
13
+ #
14
+ # @param language [String] the lyrics language as 3 character language
15
+ # @param descriptor [String] the description
16
+ # @param lyrics [String] the lyrics text
17
+ def initialize(language, descriptor, lyrics)
18
+ @language = language
19
+ @descriptor = descriptor
20
+ @lyrics = lyrics
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Lyrics
6
+ class UnsyncLyricsFrame < Id3v2Frame
7
+ include HasId
8
+
9
+ frame_info :ULT, :USLT, :USLT
10
+
11
+ attr_accessor :language, :descriptor, :lyrics
12
+
13
+ # builds the unsync lyrics frame
14
+ #
15
+ # @param language [String] the lyrics language as 3 character language
16
+ # @param descriptor [String] the description
17
+ # @param lyrics [String] the lyrics 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, lyrics, options = nil, id3_version: 3)
23
+ supported?('USLT', id3_version, options)
24
+
25
+ argument_not_nil(language, 'language')
26
+ argument_exactly_chars(language, 'language', 3)
27
+ argument_not_nil(lyrics, 'lyrics')
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.lyrics = lyrics
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_lyrics = stream.read
45
+ @lyrics = decode(encoded_lyrics, encoding)
46
+ end
47
+
48
+ def content_to_bytes
49
+ encrypted_descriptor = encode(@descriptor, null_terminated: true)
50
+ encrypted_lyrics = encode(@lyrics)
51
+ merge(default_encoding_destination_byte, @language, encrypted_descriptor, encrypted_lyrics)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module LyricsFrames
6
+ include Frames::Frameable
7
+
8
+ # extracts the unsync lyrics (USLT/ULT)
9
+ #
10
+ # @return [Array<Frames::Lyrics::Entities::UnsyncLyrics>] returns the Lyrics
11
+ def unsync_lyrics
12
+ frame = find_frames(Lyrics::UnsyncLyricsFrame.frame_id(@major_version, @options))
13
+ return [] if frame.nil? || frame.empty?
14
+
15
+ frame.map { |f| Lyrics::Entities::UnsyncLyrics.new(f.language, f.descriptor, f.lyrics) }
16
+ end
17
+
18
+ # adds an unsync lyrics. (USLT/ULT)
19
+ # Lyrics with the same language and descriptor will be updated.
20
+ #
21
+ # @param lyrics [Frames::Lyrics::Entities::UnsyncLyrics] the lyrics to add
22
+ def unsync_lyrics=(lyrics)
23
+ set_frame_fields_by_selector(Lyrics::UnsyncLyricsFrame, %i[@language @descriptor @lyrics],
24
+ ->(f) { f.language == lyrics.language && f.descriptor == lyrics.descriptor },
25
+ lyrics.language, lyrics.descriptor, lyrics.lyrics)
26
+ end
27
+
28
+ alias add_unsync_lyrics unsync_lyrics=
29
+
30
+ # removes an unsync lyrics for the specific language and descriptor
31
+ #
32
+ # @param language [String] the language
33
+ # @param descriptor [String] the descriptor
34
+ def remove_unsync_lyrics(language, descriptor)
35
+ @frames.delete_if do |f|
36
+ f.frame_id == Lyrics::UnsyncLyricsFrame.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,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Mcdi
6
+ class MusicCDIdentifierFrame < Id3v2Frame
7
+ include HasId
8
+
9
+ frame_info :MCI, :MCDI, :MCDI
10
+
11
+ attr_accessor :cd_toc
12
+
13
+ # builds the music cd identifier frame
14
+ #
15
+ # @param cd_toc [String] the cd toc bytes represented as a String (str.bytes)
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(cd_toc, options = nil, id3_version: 3)
21
+ supported?('MCDI', id3_version, options)
22
+
23
+ argument_not_nil(cd_toc, 'cd_toc')
24
+
25
+ frame = new(frame_id(id3_version, options), 0, build_id3_flags(id3_version), '')
26
+ frame.cd_toc = cd_toc
27
+ frame
28
+ end
29
+
30
+ def process_content(content)
31
+ @cd_toc = content
32
+ end
33
+
34
+ def content_to_bytes
35
+ @cd_toc
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module McdiFrames
6
+ include Frames::Frameable
7
+
8
+ # extracts the music cd identifier (MCDI/MCI)
9
+ #
10
+ # @return [String, nil] returns the music cd identifier
11
+ def music_cd_identifier
12
+ find_frame(Mcdi::MusicCDIdentifierFrame.frame_id(@major_version, @options))&.cd_toc
13
+ end
14
+
15
+ # sets the music cd identifier (MCDI/MCI)
16
+ #
17
+ # @param cd_toc [String] sets the music cd identifier
18
+ def music_cd_identifier=(cd_toc)
19
+ set_frame_fields(Mcdi::MusicCDIdentifierFrame, [:@cd_toc], cd_toc)
20
+ end
21
+
22
+ # removes the music cd identifier frame
23
+ def remove_music_cd_identifier
24
+ @frames.delete_if { |f| f.frame_id == Mcdi::MusicCDIdentifierFrame.frame_id(@major_version, @options) }
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Picture
6
+ class PictureFrame < Id3v2Frame
7
+ include HasId
8
+
9
+ frame_info :PIC, :APIC, :APIC
10
+
11
+ LINK_MIME = '-->'
12
+
13
+ attr_accessor :mime_type, :picture_type, :descriptor, :picture_data
14
+
15
+ # builds the picture frame
16
+ #
17
+ # @param mime_type [String] the mime type e.g. image/png. '-->' if the image is a reference, e.g. a URI
18
+ # @param picture_type [Symbol, nil] the picture type, all types can be found in Picture::PictureType
19
+ # @param descriptor [String] the description
20
+ # @param picture_data [String] the picture data bytes represented as a String (str.bytes)
21
+ # @param options [Options::Options] options to use
22
+ # @param id3_version [Integer] the id3 version to build the frame for
23
+ #
24
+ # @return [Id3v2Frame] the resulting id3v2 frame
25
+ def self.build_frame(mime_type, picture_type, descriptor, picture_data, options = nil, id3_version: 3)
26
+ supported?('APIC', id3_version, options)
27
+
28
+ argument_not_nil(mime_type, 'mime_type')
29
+ descriptor ||= ''
30
+ argument_less_than_chars(descriptor, 'descriptor', 64)
31
+ argument_not_nil(picture_data, 'picture_data')
32
+
33
+ picture_type ||= :OTHER
34
+
35
+ argument_sym(picture_type, 'picture_type')
36
+
37
+ mime_type = "image/#{mime_type}" if !mime_type.include?('/') && mime_type != LINK_MIME
38
+
39
+ frame = new(frame_id(id3_version, options), 0, build_id3_flags(id3_version), '')
40
+ frame.mime_type = mime_type
41
+ frame.picture_type = picture_type
42
+ frame.descriptor = descriptor
43
+ frame.picture_data = picture_data
44
+ frame
45
+ end
46
+
47
+ def process_content(content)
48
+ stream = StringIO.new(content)
49
+ encoding = find_encoding(stream.readbyte)
50
+ @mime_type = read_stream_until(stream, ZERO)
51
+ @picture_type = PictureType.type_by_value(stream.readbyte)
52
+ encoded_descriptor = read_stream_until(stream, zero_byte(encoding))
53
+ @descriptor = decode(encoded_descriptor, encoding)
54
+ @picture_data = stream.read
55
+ end
56
+
57
+ def content_to_bytes
58
+ pic_type = [PictureType.type(@picture_type)].pack('C*')
59
+ encoded_descriptor = encode(@descriptor, null_terminated: true)
60
+ merge(default_encoding_destination_byte, @mime_type, ZERO, pic_type, encoded_descriptor, @picture_data)
61
+ end
62
+ end
63
+
64
+ module PictureType
65
+ OTHER = 0x00
66
+ PIXELS_32X32_FILE_ICON__PNG_ONLY = 0x01
67
+ OTHER_FILE_ICON = 0x02
68
+ COVER_FRONT = 0x03
69
+ COVER_BACK = 0x04
70
+ LEAFLET_PAGE = 0x05
71
+ MEDIA = 0x06
72
+ LEAD_ARTIST = 0x07
73
+ ARTIST = 0x08
74
+ CONDUCTOR = 0x09
75
+ BAND = 0x0A
76
+ COMPOSER = 0x0B
77
+ LYRICIST = 0x0C
78
+ RECORDING_LOCATION = 0x0D
79
+ DURING_RECORDING = 0x0E
80
+ DURING_PERFORMANCE = 0x0F
81
+ VIDEO_SCREEN_CAPTURE = 0x10
82
+ A_BRIGHT_COLORED_FISH = 0x11
83
+ ILLUSTRATION = 0x12
84
+ BAND_LOGOTYPE = 0x13
85
+ PUBLISHER_LOGOTYPE = 0x14
86
+
87
+ def self.type(type)
88
+ return OTHER unless valid?(type)
89
+
90
+ const_get(type)
91
+ end
92
+
93
+ def self.type_by_value(value)
94
+ res = constants.find { |c| const_get(c) == value }
95
+ res.nil? ? :OTHER : res
96
+ end
97
+
98
+ def self.valid?(type)
99
+ const_get(type)
100
+ rescue NameError
101
+ false
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Picture
6
+ module Entities
7
+ class Picture
8
+ include Extensions::Comparable
9
+
10
+ attr_accessor :mime_type, :picture_type, :descriptor, :picture_data
11
+
12
+ # constructor
13
+ #
14
+ # @param mime_type [String] the mime type e.g. image/png. '-->' if the image is a reference, e.g. a URI
15
+ # @param picture_type [Symbol, nil] the picture type, all types can be found in Picture::PictureType
16
+ # @param descriptor [String] the description
17
+ # @param picture_data [String] the picture data bytes represented as a String (str.bytes)
18
+ def initialize(mime_type, picture_type, descriptor, picture_data)
19
+ @mime_type = mime_type
20
+ @picture_type = picture_type
21
+ @descriptor = descriptor
22
+ @picture_data = picture_data
23
+ end
24
+
25
+ def write_picture(file)
26
+ File.open(file, 'wb') { |f| f.write(@picture_data) }
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module PictureFrames
6
+ include Frames::Frameable
7
+
8
+ # extracts the pictures (APIC/PIC)
9
+ #
10
+ # @return [Array<Frames::Picture::Entities::Picture>] returns the Pictures
11
+ def pictures
12
+ frame = find_frames(Picture::PictureFrame.frame_id(@major_version, @options))
13
+ return [] if frame.nil? || frame.empty?
14
+
15
+ frame.map { |f| Picture::Entities::Picture.new(f.mime_type, f.picture_type, f.descriptor, f.picture_data) }
16
+ end
17
+
18
+ # adds a pictures (APIC/PIC)
19
+ # Multiple ones can be added, as long as they have a different picture_type
20
+ #
21
+ # @param picture [Frames::Picture::Entities::Picture] the picture to add
22
+ def pictures=(picture)
23
+ if picture.picture_type == Frames::Picture::PictureType::PIXELS_32X32_FILE_ICON__PNG_ONLY ||
24
+ picture.picture_type == Frames::Picture::PictureType::OTHER_FILE_ICON
25
+ frames = pictures
26
+ if frames.any? { |f| f.picture_type == picture.picture_type }
27
+ raise Errors::Id3TagError, "Only one frame of type #{picture.picture_type} is allowed"
28
+ end
29
+ end
30
+
31
+ set_frame_fields_by_selector(Picture::PictureFrame, %i[@mime_type @picture_type @descriptor @picture_data],
32
+ lambda { |f|
33
+ f.picture_type == picture.picture_type &&
34
+ f.descriptor == picture.descriptor
35
+ },
36
+ picture.mime_type, picture.picture_type, picture.descriptor, picture.picture_data)
37
+ end
38
+
39
+ alias add_picture pictures=
40
+
41
+ # removes a pictures for the specific descriptor
42
+ #
43
+ # @param descriptor [String] the descriptor
44
+ def remove_picture(descriptor)
45
+ @frames.delete_if do |f|
46
+ f.frame_id == Picture::PictureFrame.frame_id(@major_version, @options) && f.descriptor == descriptor
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Private
6
+ module Entities
7
+ class PrivateFrame
8
+ include Extensions::Comparable
9
+
10
+ attr_accessor :owner_id, :private_data
11
+
12
+ # constructor
13
+ #
14
+ # @param owner_id [String] the owner id
15
+ # @param private_data [String] the private data bytes represented as a String (str.bytes)
16
+ def initialize(owner_id, private_data)
17
+ @owner_id = owner_id
18
+ @private_data = private_data
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Id3Taginator
4
+ module Frames
5
+ module Private
6
+ class PrivateFrame < Id3v2Frame
7
+ include HasId
8
+
9
+ frame_info nil, :PRIV, :PRIV
10
+
11
+ attr_accessor :owner_id, :private_data
12
+
13
+ # builds the private frame
14
+ #
15
+ # @param owner_id [String] the owner id
16
+ # @param private_data [String] the private data bytes represented as a String (str.bytes)
17
+ # @param options [Options::Options] options to use
18
+ # @param id3_version [Integer] the id3 version to build the frame for
19
+ #
20
+ # @return [Id3v2Frame] the resulting id3v2 frame
21
+ def self.build_frame(owner_id, private_data, options = nil, id3_version: 3)
22
+ supported?('PRIV', id3_version, options)
23
+
24
+ argument_not_nil(owner_id, 'owner_id')
25
+ argument_not_nil(private_data, 'private_data')
26
+
27
+ frame = new(frame_id(id3_version, options), 0, build_id3_flags(id3_version), '')
28
+ frame.owner_id = owner_id
29
+ frame.private_data = private_data
30
+ frame
31
+ end
32
+
33
+ def process_content(content)
34
+ stream = StringIO.new(content)
35
+ @owner_id = read_stream_until(stream, ZERO)
36
+ @private_data = stream.read
37
+ end
38
+
39
+ def content_to_bytes
40
+ "#{@owner_id}\x00#{@private_data}"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end