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