id3taginator 0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.idea/.gitignore +8 -0
- data/.idea/ID3Taginator.iml +73 -0
- data/.idea/misc.xml +4 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/.rspec +3 -0
- data/.rubocop.yml +26 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +408 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/id3taginator.gemspec +37 -0
- data/lib/id3taginator/audio_file.rb +136 -0
- data/lib/id3taginator/errors/id3_tag_error.rb +12 -0
- data/lib/id3taginator/extensions/argument_check.rb +88 -0
- data/lib/id3taginator/extensions/comparable.rb +28 -0
- data/lib/id3taginator/extensions/encodable.rb +215 -0
- data/lib/id3taginator/extensions/optionable.rb +73 -0
- data/lib/id3taginator/frames/buffer/entities/buffer.rb +26 -0
- data/lib/id3taginator/frames/buffer/rbuf_recommended_buffer_size_frame.rb +59 -0
- data/lib/id3taginator/frames/buffer_frames.rb +32 -0
- data/lib/id3taginator/frames/comment/comm_comment_frame.rb +56 -0
- data/lib/id3taginator/frames/comment/entities/comment.rb +26 -0
- data/lib/id3taginator/frames/comment_frames.rb +42 -0
- data/lib/id3taginator/frames/count/entities/popularimeter.rb +26 -0
- data/lib/id3taginator/frames/count/pcnt_play_counter_frame.rb +40 -0
- data/lib/id3taginator/frames/count/popm_popularimeter_frame.rb +52 -0
- data/lib/id3taginator/frames/count_frames.rb +51 -0
- data/lib/id3taginator/frames/custom_frame.rb +39 -0
- data/lib/id3taginator/frames/custom_frames.rb +54 -0
- data/lib/id3taginator/frames/encryption/aenc_audio_encryption.rb +54 -0
- data/lib/id3taginator/frames/encryption/encr_encryption_method_frame.rb +49 -0
- data/lib/id3taginator/frames/encryption/entities/audio_encryption.rb +28 -0
- data/lib/id3taginator/frames/encryption/entities/encryption_method.rb +26 -0
- data/lib/id3taginator/frames/encryption_frames.rb +77 -0
- data/lib/id3taginator/frames/frameable.rb +75 -0
- data/lib/id3taginator/frames/geo/entities/encapsulated_object.rb +28 -0
- data/lib/id3taginator/frames/geo/geob_general_encapsulated_object_frame.rb +59 -0
- data/lib/id3taginator/frames/geo_frames.rb +41 -0
- data/lib/id3taginator/frames/grouping/entities/group_identification.rb +26 -0
- data/lib/id3taginator/frames/grouping/grid_group_identification_frame.rb +49 -0
- data/lib/id3taginator/frames/grouping/grp1_grouping_frame.rb +40 -0
- data/lib/id3taginator/frames/grouping_frames.rb +61 -0
- data/lib/id3taginator/frames/has_id.rb +51 -0
- data/lib/id3taginator/frames/id3v23_frame_flags.rb +64 -0
- data/lib/id3taginator/frames/id3v24_frame_flags.rb +80 -0
- data/lib/id3taginator/frames/id3v2_frame.rb +249 -0
- data/lib/id3taginator/frames/id3v2_frame_factory.rb +98 -0
- data/lib/id3taginator/frames/ipl/entities/involved_person.rb +24 -0
- data/lib/id3taginator/frames/ipl/ipls_involved_people_frame.rb +48 -0
- data/lib/id3taginator/frames/ipl_frames.rb +31 -0
- data/lib/id3taginator/frames/lyrics/entities/unsync_lyrics.rb +26 -0
- data/lib/id3taginator/frames/lyrics/uslt_unsync_lyrics_frame.rb +56 -0
- data/lib/id3taginator/frames/lyrics_frames.rb +42 -0
- data/lib/id3taginator/frames/mcdi/mcdi_music_cd_identifier_frame.rb +40 -0
- data/lib/id3taginator/frames/mcdi_frames.rb +28 -0
- data/lib/id3taginator/frames/picture/apic_picture_frame.rb +106 -0
- data/lib/id3taginator/frames/picture/entities/picture.rb +32 -0
- data/lib/id3taginator/frames/picture_frames.rb +51 -0
- data/lib/id3taginator/frames/private/entities/private_frame.rb +24 -0
- data/lib/id3taginator/frames/private/priv_private_frame.rb +45 -0
- data/lib/id3taginator/frames/private_frames.rb +40 -0
- data/lib/id3taginator/frames/text/entities/copyright.rb +24 -0
- data/lib/id3taginator/frames/text/entities/date.rb +24 -0
- data/lib/id3taginator/frames/text/entities/part_of_set.rb +24 -0
- data/lib/id3taginator/frames/text/entities/time.rb +24 -0
- data/lib/id3taginator/frames/text/entities/track_number.rb +24 -0
- data/lib/id3taginator/frames/text/entities/user_info.rb +24 -0
- data/lib/id3taginator/frames/text/talb_album_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tbpm_bpm_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tcom_composer_frame.rb +42 -0
- data/lib/id3taginator/frames/text/tcon_genre_frame.rb +104 -0
- data/lib/id3taginator/frames/text/tcop_copyright_frame.rb +55 -0
- data/lib/id3taginator/frames/text/tdat_date_frame.rb +60 -0
- data/lib/id3taginator/frames/text/tdly_playlist_delay_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tenc_encoded_by_frame.rb +40 -0
- data/lib/id3taginator/frames/text/text_writers_frame.rb +43 -0
- data/lib/id3taginator/frames/text/tflt_file_type_frame.rb +71 -0
- data/lib/id3taginator/frames/text/time_time_frame.rb +60 -0
- data/lib/id3taginator/frames/text/tit1_content_group_description_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tit2_title_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tit3_subtitle_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tkey_initial_key_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tlan_language_frame.rb +48 -0
- data/lib/id3taginator/frames/text/tlen_length_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tmed_media_type_frame.rb +40 -0
- data/lib/id3taginator/frames/text/toal_original_album_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tofn_original_filename_frame.rb +40 -0
- data/lib/id3taginator/frames/text/toly_original_writers_frame.rb +43 -0
- data/lib/id3taginator/frames/text/tope_original_artists_frame.rb +43 -0
- data/lib/id3taginator/frames/text/tory_original_release_year_frame.rb +42 -0
- data/lib/id3taginator/frames/text/town_file_owner_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tpe1_artist_frame.rb +43 -0
- data/lib/id3taginator/frames/text/tpe2_album_artist_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tpe3_conductor_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tpe4_modified_by_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tpos_part_of_set_frame.rb +50 -0
- data/lib/id3taginator/frames/text/tpub_publisher_frame.rb +40 -0
- data/lib/id3taginator/frames/text/trck_track_number_frame.rb +50 -0
- data/lib/id3taginator/frames/text/trda_recording_dates_frame.rb +42 -0
- data/lib/id3taginator/frames/text/trsn_internet_radio_station_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tsiz_size_frame.rb +41 -0
- data/lib/id3taginator/frames/text/tsoa_album_sort_order_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tsop_performer_sort_order_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tsot_title_sort_order_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tsrc_isrc_frame.rb +40 -0
- data/lib/id3taginator/frames/text/tsse_encoder_frame.rb +40 -0
- data/lib/id3taginator/frames/text/txxx_user_text_info_frame.rb +51 -0
- data/lib/id3taginator/frames/text/tyer_year_frame.rb +42 -0
- data/lib/id3taginator/frames/text_frames.rb +840 -0
- data/lib/id3taginator/frames/tos/entities/ownership.rb +26 -0
- data/lib/id3taginator/frames/tos/entities/terms_of_use.rb +24 -0
- data/lib/id3taginator/frames/tos/owne_ownership_frame.rb +53 -0
- data/lib/id3taginator/frames/tos/user_terms_of_use_frame.rb +49 -0
- data/lib/id3taginator/frames/tos_frames.rb +54 -0
- data/lib/id3taginator/frames/ufid/entities/ufid_info.rb +24 -0
- data/lib/id3taginator/frames/ufid/ufid_unique_file_identifier_frame.rb +47 -0
- data/lib/id3taginator/frames/ufid_frames.rb +40 -0
- data/lib/id3taginator/frames/url/entities/user_info.rb +24 -0
- data/lib/id3taginator/frames/url/wcom_commercial_url_frame.rb +40 -0
- data/lib/id3taginator/frames/url/wcop_copyright_url_frame.rb +40 -0
- data/lib/id3taginator/frames/url/woaf_official_file_webpage_frame.rb +40 -0
- data/lib/id3taginator/frames/url/woar_official_artist_webpage_frame.rb +40 -0
- data/lib/id3taginator/frames/url/woas_official_source_webpage_frame.rb +40 -0
- data/lib/id3taginator/frames/url/wors_official_radio_station_homepage_frame.rb +40 -0
- data/lib/id3taginator/frames/url/wpay_payment_url_frame.rb +40 -0
- data/lib/id3taginator/frames/url/wpub_official_publisher_webpage_frame.rb +40 -0
- data/lib/id3taginator/frames/url/wxxx_user_url_link_frame.rb +50 -0
- data/lib/id3taginator/frames/url_frames.rb +195 -0
- data/lib/id3taginator/genres.rb +168 -0
- data/lib/id3taginator/header/id3v23_extended_header.rb +37 -0
- data/lib/id3taginator/header/id3v24_extended_header.rb +100 -0
- data/lib/id3taginator/header/id3v2_flags.rb +64 -0
- data/lib/id3taginator/id3v1_tag.rb +156 -0
- data/lib/id3taginator/id3v22_tag.rb +30 -0
- data/lib/id3taginator/id3v23_tag.rb +63 -0
- data/lib/id3taginator/id3v24_tag.rb +75 -0
- data/lib/id3taginator/id3v2_tag.rb +241 -0
- data/lib/id3taginator/options/options.rb +33 -0
- data/lib/id3taginator/util/compress_util.rb +25 -0
- data/lib/id3taginator/util/math_util.rb +68 -0
- data/lib/id3taginator/util/sync_util.rb +65 -0
- data/lib/id3taginator.rb +449 -0
- metadata +198 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Id3Taginator
|
4
|
+
module Frames
|
5
|
+
module Grouping
|
6
|
+
module Entities
|
7
|
+
class GroupIdentification
|
8
|
+
include Extensions::Comparable
|
9
|
+
|
10
|
+
attr_accessor :owner_id, :group_symbol, :group_dependant_data
|
11
|
+
|
12
|
+
# constructor
|
13
|
+
#
|
14
|
+
# @param owner_id [String] the owner id
|
15
|
+
# @param group_symbol [Integer] the group symbol, number between 0 and 255
|
16
|
+
# @param group_dependant_data [String] data bytes represented as a String (str.bytes)
|
17
|
+
def initialize(owner_id, group_symbol, group_dependant_data)
|
18
|
+
@owner_id = owner_id
|
19
|
+
@group_symbol = group_symbol
|
20
|
+
@group_dependant_data = group_dependant_data
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Id3Taginator
|
4
|
+
module Frames
|
5
|
+
module Grouping
|
6
|
+
class GroupIdentificationFrame < Id3v2Frame
|
7
|
+
include HasId
|
8
|
+
|
9
|
+
frame_info nil, :GRID, :GRID
|
10
|
+
|
11
|
+
attr_accessor :owner_id, :group_symbol, :group_dependant_data
|
12
|
+
|
13
|
+
# builds the group identification frame
|
14
|
+
#
|
15
|
+
# @param owner_id [String] the owner id
|
16
|
+
# @param group_symbol [Integer] the group symbol, number between 0 and 255
|
17
|
+
# @param group_dependant_data [String] data bytes represented as a String (str.bytes)
|
18
|
+
# @param options [Options::Options] options to use
|
19
|
+
# @param id3_version [Integer] the id3 version to build the frame for
|
20
|
+
#
|
21
|
+
# @return [Id3v2Frame] the resulting id3v2 frame
|
22
|
+
def self.build_frame(owner_id, group_symbol, group_dependant_data, options = nil, id3_version: 3)
|
23
|
+
supported?('GRID', id3_version, options)
|
24
|
+
|
25
|
+
argument_not_nil(owner_id, 'owner_id')
|
26
|
+
argument_between_num(group_symbol, 'group_symbol', 0, 255)
|
27
|
+
argument_not_nil(group_dependant_data, 'group_dependant_data')
|
28
|
+
|
29
|
+
frame = new(frame_id(id3_version, options), 0, build_id3_flags(id3_version), '')
|
30
|
+
frame.owner_id = owner_id
|
31
|
+
frame.group_symbol = group_symbol
|
32
|
+
frame.group_dependant_data = group_dependant_data
|
33
|
+
frame
|
34
|
+
end
|
35
|
+
|
36
|
+
def process_content(content)
|
37
|
+
stream = StringIO.new(content)
|
38
|
+
@owner_id = read_stream_until(stream, ZERO)
|
39
|
+
@group_symbol = stream.readbyte
|
40
|
+
@group_dependant_data = stream.read
|
41
|
+
end
|
42
|
+
|
43
|
+
def content_to_bytes
|
44
|
+
merge(@owner_id, "\x00", @group_symbol.chr, @group_dependant_data)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Id3Taginator
|
4
|
+
module Frames
|
5
|
+
module Grouping
|
6
|
+
class GroupingFrame < Id3v2Frame
|
7
|
+
include HasId
|
8
|
+
|
9
|
+
frame_info nil, :GRP1, :GRP1
|
10
|
+
|
11
|
+
attr_accessor :grouping
|
12
|
+
|
13
|
+
# builds the group identification frame
|
14
|
+
#
|
15
|
+
# @param grouping [String] the Grouping Information
|
16
|
+
# @param options [Options::Options] options to use
|
17
|
+
# @param id3_version [Integer] the id3 version to build the frame for
|
18
|
+
#
|
19
|
+
# @return [Id3v2Frame] the resulting id3v2 frame
|
20
|
+
def self.build_frame(grouping, options = nil, id3_version: 3)
|
21
|
+
supported?('GRP1', id3_version, options)
|
22
|
+
|
23
|
+
argument_not_nil(grouping, 'grouping')
|
24
|
+
|
25
|
+
frame = new(frame_id(id3_version, options), 0, build_id3_flags(id3_version), '')
|
26
|
+
frame.grouping = grouping.to_s
|
27
|
+
frame
|
28
|
+
end
|
29
|
+
|
30
|
+
def process_content(content)
|
31
|
+
@grouping = decode_using_encoding_byte(content)
|
32
|
+
end
|
33
|
+
|
34
|
+
def content_to_bytes
|
35
|
+
encode_and_add_encoding_byte(@grouping)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Id3Taginator
|
4
|
+
module Frames
|
5
|
+
module GroupingFrames
|
6
|
+
include Frames::Frameable
|
7
|
+
|
8
|
+
# extracts the group information (GRID)
|
9
|
+
#
|
10
|
+
# @return [Array<Frames::Grouping::Entities::GroupIdentification>] returns the grouping information
|
11
|
+
def group_identifications
|
12
|
+
frame = find_frames(Grouping::GroupIdentificationFrame.frame_id(@major_version, @options))
|
13
|
+
return [] if frame.nil? || frame.empty?
|
14
|
+
|
15
|
+
frame.map do |f|
|
16
|
+
Grouping::Entities::GroupIdentification.new(f.owner_id, f.group_symbol, f.group_dependant_data)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# adds a group identification (GRID)
|
21
|
+
#
|
22
|
+
# @param group [Frames::Grouping::Entities::GroupIdentification] the grouping information to add
|
23
|
+
def group_identifications=(group)
|
24
|
+
set_frame_fields_by_selector(Grouping::GroupIdentificationFrame, %i[@group_symbol @group_dependant_data],
|
25
|
+
->(f) { f.owner_id == group.owner_id },
|
26
|
+
group.owner_id, group.group_symbol, group.group_dependant_data)
|
27
|
+
end
|
28
|
+
|
29
|
+
alias add_group_identification group_identifications=
|
30
|
+
|
31
|
+
# removes a group identification for the specific owner_id
|
32
|
+
#
|
33
|
+
# @param owner_id [String] the owner_id
|
34
|
+
def remove_group_identification(owner_id)
|
35
|
+
@frames.delete_if do |f|
|
36
|
+
f.frame_id == Grouping::GroupIdentificationFrame.frame_id(@major_version, @options) &&
|
37
|
+
f.owner_id == owner_id
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# extracts the Grouping (GRP1)
|
42
|
+
#
|
43
|
+
# @return [String, nil] returns the Grouping information
|
44
|
+
def grouping
|
45
|
+
find_frame(Grouping::GroupingFrame.frame_id(@major_version, @options))&.grouping
|
46
|
+
end
|
47
|
+
|
48
|
+
# sets the grouping (GRP1)
|
49
|
+
#
|
50
|
+
# @param grouping [String] the Grouping
|
51
|
+
def grouping=(grouping)
|
52
|
+
set_frame_fields(Grouping::GroupingFrame, [:@grouping], grouping)
|
53
|
+
end
|
54
|
+
|
55
|
+
# removes the grouping frame
|
56
|
+
def remove_grouping
|
57
|
+
@frames.delete_if { |f| f.frame_id == Grouping::GroupingFrame.frame_id(@major_version, @options) }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Id3Taginator
|
4
|
+
module Frames
|
5
|
+
module HasId
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(self)
|
8
|
+
end
|
9
|
+
|
10
|
+
# creates a class method to return the frame id depending on the major id3tag version
|
11
|
+
# 3 and 4 return the 4 char Tag, 2 returns the version2 3 char Tag
|
12
|
+
# Additionally, a method to check if the version is supported is provided.
|
13
|
+
#
|
14
|
+
# @param version2 [Symbol, nil] the frame id for v2
|
15
|
+
# @param version3 [Symbol, nil] the frame id for v3
|
16
|
+
# @param version4 [Symbol, nil] the frame id for v4
|
17
|
+
def frame_info(version2, version3, version4)
|
18
|
+
define_singleton_method('frame_id') do |version = nil, options = nil|
|
19
|
+
result = nil
|
20
|
+
result = version3 if version.nil?
|
21
|
+
|
22
|
+
result = version2 if version == 2
|
23
|
+
result = version3 if version == 3
|
24
|
+
result = version4 if version == 4
|
25
|
+
|
26
|
+
result = version4 if version == 3 && options&.ignore_v23_frame_error && result.nil?
|
27
|
+
result = version3 if version == 4 && options&.ignore_v24_frame_error && result.nil?
|
28
|
+
|
29
|
+
result
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def supported?(frame_name, version, options)
|
34
|
+
frame_id = frame_id(version)
|
35
|
+
return true unless frame_id.nil?
|
36
|
+
|
37
|
+
if version == 3 && options&.ignore_v23_frame_error
|
38
|
+
v4_frame_id = frame_id(4)
|
39
|
+
return true unless v4_frame_id.nil?
|
40
|
+
end
|
41
|
+
|
42
|
+
if version == 4 && options&.ignore_v24_frame_error
|
43
|
+
v3_frame_id = frame_id(3)
|
44
|
+
return true unless v3_frame_id.nil?
|
45
|
+
end
|
46
|
+
|
47
|
+
raise Errors::Id3TagError, "#{frame_name} not supported by Id3v2.#{version}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Id3Taginator
|
4
|
+
module Frames
|
5
|
+
class Id3v23FrameFlags
|
6
|
+
|
7
|
+
def initialize(flags)
|
8
|
+
@flags = flags
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_bytes
|
12
|
+
@flags
|
13
|
+
end
|
14
|
+
|
15
|
+
# determined if the frame is alter preserved
|
16
|
+
#
|
17
|
+
# @return [Boolean] true if alter preserved, else false
|
18
|
+
def tag_alter_preservation?
|
19
|
+
flag = @flags.bytes[0]
|
20
|
+
(flag & 0b1000_0000) == 0b1000_0000
|
21
|
+
end
|
22
|
+
|
23
|
+
# determined if the file is alter preserved
|
24
|
+
#
|
25
|
+
# @return [Boolean] true if the file is alter preserved, else false
|
26
|
+
def file_alter_preservation?
|
27
|
+
flag = @flags.bytes[0]
|
28
|
+
(flag & 0b0100_0000) == 0b0100_0000
|
29
|
+
end
|
30
|
+
|
31
|
+
# determined if the file frame is read only
|
32
|
+
#
|
33
|
+
# @return [Boolean] true if frame is read only, else false
|
34
|
+
def read_only?
|
35
|
+
flag = @flags.bytes[0]
|
36
|
+
(flag & 0b0010_0000) == 0b0010_0000
|
37
|
+
end
|
38
|
+
|
39
|
+
# determined if the frame is compressed
|
40
|
+
#
|
41
|
+
# @return [Boolean] true if the frame is compressed, else false
|
42
|
+
def compression?
|
43
|
+
flag = @flags.bytes[1]
|
44
|
+
(flag & 0b1000_0000) == 0b1000_0000
|
45
|
+
end
|
46
|
+
|
47
|
+
# determined if the frame is encrypted
|
48
|
+
#
|
49
|
+
# @return [Boolean] true if the frame is encrypted, else false
|
50
|
+
def encryption?
|
51
|
+
flag = @flags.bytes[1]
|
52
|
+
(flag & 0b0100_0000) == 0b0100_0000
|
53
|
+
end
|
54
|
+
|
55
|
+
# determined if the frame has a group identity
|
56
|
+
#
|
57
|
+
# @return [Boolean] true if the frame has a group identity, else false
|
58
|
+
def group_identity?
|
59
|
+
flag = @flags.bytes[1]
|
60
|
+
(flag & 0b0010_0000) == 0b0010_0000
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Id3Taginator
|
4
|
+
module Frames
|
5
|
+
class Id3v24FrameFlags
|
6
|
+
|
7
|
+
def initialize(flags)
|
8
|
+
@flags = flags
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_bytes
|
12
|
+
@flags
|
13
|
+
end
|
14
|
+
|
15
|
+
# determined if the frame is alter preserved
|
16
|
+
#
|
17
|
+
# @return [Boolean] true if alter preserved, else false
|
18
|
+
def tag_alter_preservation?
|
19
|
+
flag = @flags.bytes[0]
|
20
|
+
(flag & 0b0100_0000) == 0b0100_0000
|
21
|
+
end
|
22
|
+
|
23
|
+
# determined if the file is alter preserved
|
24
|
+
#
|
25
|
+
# @return [Boolean] true if the file is alter preserved, else false
|
26
|
+
def file_alter_preservation?
|
27
|
+
flag = @flags.bytes[0]
|
28
|
+
(flag & 0b0010_0000) == 0b0010_0000
|
29
|
+
end
|
30
|
+
|
31
|
+
# determined if the file frame is read only
|
32
|
+
#
|
33
|
+
# @return [Boolean] true if frame is read only, else false
|
34
|
+
def read_only?
|
35
|
+
flag = @flags.bytes[0]
|
36
|
+
(flag & 0b0001_0000) == 0b0001_0000
|
37
|
+
end
|
38
|
+
|
39
|
+
# determined if the frame is compressed
|
40
|
+
#
|
41
|
+
# @return [Boolean] true if the frame is compressed, else false
|
42
|
+
def compression?
|
43
|
+
flag = @flags.bytes[1]
|
44
|
+
(flag & 0b0000_1000) == 0b0000_1000
|
45
|
+
end
|
46
|
+
|
47
|
+
# determined if the frame is encrypted
|
48
|
+
#
|
49
|
+
# @return [Boolean] true if the frame is encrypted, else false
|
50
|
+
def encryption?
|
51
|
+
flag = @flags.bytes[1]
|
52
|
+
(flag & 0b0000_0100) == 0b0000_0100
|
53
|
+
end
|
54
|
+
|
55
|
+
# determined if the frame has a group identity
|
56
|
+
#
|
57
|
+
# @return [Boolean] true if the frame has a group identity, else false
|
58
|
+
def group_identity?
|
59
|
+
flag = @flags.bytes[1]
|
60
|
+
(flag & 0b0100_0000) == 0b0100_0000
|
61
|
+
end
|
62
|
+
|
63
|
+
# determined if the frame is unsynchronized
|
64
|
+
#
|
65
|
+
# @return [Boolean] true if the frame is unsynchronized, else false
|
66
|
+
def unsynchronisation?
|
67
|
+
flag = @flags.bytes[1]
|
68
|
+
(flag & 0b0000_0010) == 0b0000_0010
|
69
|
+
end
|
70
|
+
|
71
|
+
# determined if the frame data length indicator is present
|
72
|
+
#
|
73
|
+
# @return [Boolean] true if the frame data length indicator is present, else false
|
74
|
+
def data_length_indicator?
|
75
|
+
flag = @flags.bytes[1]
|
76
|
+
(flag & 0b0000_0001) == 0b0000_0001
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,249 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Id3Taginator
|
4
|
+
module Frames
|
5
|
+
class Id3v2Frame
|
6
|
+
include Extensions::Encodable
|
7
|
+
include Extensions::ArgumentCheck
|
8
|
+
|
9
|
+
HEADER_SIZE_V_3_4 = 10
|
10
|
+
HEADER_SIZE_V_2 = 6
|
11
|
+
|
12
|
+
attr_accessor :options
|
13
|
+
attr_reader :frame_id
|
14
|
+
|
15
|
+
# builds an id3v2.2 frame of the given frame id and the given data stream. The data stream(0) must be after
|
16
|
+
# the frame id
|
17
|
+
#
|
18
|
+
# @param frame_id [String] the frame id
|
19
|
+
# @param file [StringIO, IO, file] the data stream
|
20
|
+
# @param options [Options::Options] the options to use
|
21
|
+
#
|
22
|
+
# @return [Id3v2Frame] the build id3v2.2 frame
|
23
|
+
def self.build_v2_frame(frame_id, file, options)
|
24
|
+
payload_size = Util::MathUtil.to_number(file.read(3)&.bytes)
|
25
|
+
frame_payload = file.read(payload_size)
|
26
|
+
|
27
|
+
raise Errors::Id3TagError, "Could not find any Frame data for #{frame_id}." if frame_payload.nil?
|
28
|
+
|
29
|
+
instance = new(frame_id, payload_size, nil, frame_payload, HEADER_SIZE_V_2, nil)
|
30
|
+
instance.options = options
|
31
|
+
instance.process_content(frame_payload)
|
32
|
+
instance
|
33
|
+
end
|
34
|
+
|
35
|
+
# builds an id3v2.3 frame of the given frame id and the given data stream. The data stream(0) must be after
|
36
|
+
# the frame id
|
37
|
+
#
|
38
|
+
# @param frame_id [String] the frame id
|
39
|
+
# @param file [StringIO, IO, file] the data stream
|
40
|
+
# @param options [Options::Options] the options to use
|
41
|
+
#
|
42
|
+
# @return [Id3v2Frame] the build id3v2.3 frame
|
43
|
+
def self.build_v3_frame(frame_id, file, options)
|
44
|
+
payload_size = Util::MathUtil.to_number(file.read(4)&.bytes)
|
45
|
+
flags = Id3v23FrameFlags.new(file.read(2))
|
46
|
+
decompressed_size = Util::MathUtil.to_number(file.read(4)&.bytes) if flags.compression?
|
47
|
+
|
48
|
+
if flags.compression?
|
49
|
+
compressed_data = file.read(payload_size)
|
50
|
+
raise Errors::Id3TagError, "Could not find any Frame data for #{frame_id}." if compressed_data.nil?
|
51
|
+
|
52
|
+
frame_payload = Util::CompressUtil.decompress_data(compressed_data)
|
53
|
+
# noinspection RubyScope
|
54
|
+
payload_size = decompressed_size
|
55
|
+
else
|
56
|
+
frame_payload = file.read(payload_size)
|
57
|
+
end
|
58
|
+
|
59
|
+
group_identify = nil
|
60
|
+
if flags.group_identity?
|
61
|
+
group_identify = frame_payload[0]
|
62
|
+
frame_payload = frame_payload[1..-1]
|
63
|
+
end
|
64
|
+
|
65
|
+
raise Errors::Id3TagError, "Could not find any Frame data for #{frame_id}." if frame_payload.nil?
|
66
|
+
|
67
|
+
instance = new(frame_id, payload_size, flags, frame_payload, HEADER_SIZE_V_3_4, group_identify)
|
68
|
+
instance.options = options
|
69
|
+
instance.process_content(frame_payload)
|
70
|
+
instance
|
71
|
+
end
|
72
|
+
|
73
|
+
# builds an id3v2.4 frame of the given frame id and the given data stream. The data stream(0) must be after
|
74
|
+
# the frame id
|
75
|
+
#
|
76
|
+
# @param frame_id [String] the frame id
|
77
|
+
# @param file [StringIO, IO, file] the data stream
|
78
|
+
# @param options [Options::Options] the options to use
|
79
|
+
#
|
80
|
+
# @return [Id3v2Frame] the build id3v2.4 frame
|
81
|
+
def self.build_v4_frame(frame_id, file, options)
|
82
|
+
payload_size = Util::MathUtil.to_32_synchsafe_integer(file.read(4)&.bytes)
|
83
|
+
flags = Id3v24FrameFlags.new(file.read(2))
|
84
|
+
|
85
|
+
frame_payload = file.read(payload_size)
|
86
|
+
raise Errors::Id3TagError, "Could not find any Frame data for #{frame_id}." if frame_payload.nil?
|
87
|
+
|
88
|
+
frame_payload = Util::SyncUtil.undo_synchronization(StringIO.new(frame_payload)) if flags.unsynchronisation?
|
89
|
+
frame_payload = Util::CompressUtil.decompress_data(frame_payload) if flags.compression?
|
90
|
+
|
91
|
+
group_identify = nil
|
92
|
+
if flags.group_identity?
|
93
|
+
group_identify = frame_payload[0]
|
94
|
+
frame_payload = frame_payload[1..-1]
|
95
|
+
end
|
96
|
+
|
97
|
+
raise Errors::Id3TagError, "Could not find any Frame data for #{frame_id}." if frame_payload.nil?
|
98
|
+
|
99
|
+
instance = new(frame_id, payload_size, flags, frame_payload, HEADER_SIZE_V_3_4, group_identify)
|
100
|
+
instance.options = options
|
101
|
+
instance.process_content(frame_payload)
|
102
|
+
instance
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.build_id3_flags(version, flags = "\x00\x00")
|
106
|
+
case version
|
107
|
+
when 2
|
108
|
+
nil
|
109
|
+
when 3
|
110
|
+
Id3v23FrameFlags.new(flags)
|
111
|
+
when 4
|
112
|
+
Id3v24FrameFlags.new(flags)
|
113
|
+
else
|
114
|
+
raise Errors::Id3TagError, "Id3v.2.#{version} is not supported."
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Constructor
|
119
|
+
#
|
120
|
+
# @param frame_id [String] the frame id
|
121
|
+
# @param payload_size [Integer] the payload size (excludes header)
|
122
|
+
# @param flags [Id3v23FrameFlags, Id3v24FrameFlags, nil] the frame flags
|
123
|
+
# @param frame_payload [String] the decompressed and unsynchronized payload
|
124
|
+
# @param header_size [Integer] the frame header size, 6 vor v2, 10 otherwise
|
125
|
+
# @param group_identify [String, nil] the group identify if present
|
126
|
+
def initialize(frame_id, payload_size, flags, frame_payload, header_size = 10, group_identify = nil)
|
127
|
+
@header_size = header_size
|
128
|
+
@frame_id = frame_id.to_sym
|
129
|
+
@payload_size = payload_size
|
130
|
+
@flags = flags
|
131
|
+
@frame_payload = frame_payload
|
132
|
+
@group_identity = group_identify
|
133
|
+
end
|
134
|
+
|
135
|
+
# processes the frame payload for the specific frame
|
136
|
+
#
|
137
|
+
# @param _content [String] the frame payload
|
138
|
+
def process_content(_content)
|
139
|
+
raise NotImplementedError, 'Implement this in a the actual frame class.'
|
140
|
+
end
|
141
|
+
|
142
|
+
# dumps the frame content to the byte representation as a String
|
143
|
+
#
|
144
|
+
# @return [String] the byte array as String
|
145
|
+
def content_to_bytes
|
146
|
+
raise NotImplementedError, 'Implement this in the actual frame class.'
|
147
|
+
end
|
148
|
+
|
149
|
+
# dumps the frame to a byte string. This dump already takes unsynchronization, padding and all other
|
150
|
+
# options into effect
|
151
|
+
#
|
152
|
+
# @return [String] frame dump as a String. tag.bytes represents the byte array
|
153
|
+
def to_bytes
|
154
|
+
size_padding = @frame_id.to_s.length == 3 ? 6 : 8
|
155
|
+
payload = content_to_bytes
|
156
|
+
payload_size_decompressed = Util::MathUtil.from_number(payload.length, size_padding)
|
157
|
+
|
158
|
+
result = @frame_id.to_s
|
159
|
+
if @flags&.compression?
|
160
|
+
payload_compressed = Util::CompressUtil.compress_data(payload)
|
161
|
+
payload_size_compressed = payload_compressed.size
|
162
|
+
result += Util::MathUtil.from_number(payload_size_compressed, size_padding)
|
163
|
+
else
|
164
|
+
result += payload_size_decompressed
|
165
|
+
end
|
166
|
+
|
167
|
+
result += @flags.to_bytes unless @flags.nil?
|
168
|
+
result += payload_size_decompressed if @flags&.compression?
|
169
|
+
result += @group_identity if @flags&.group_identity?
|
170
|
+
|
171
|
+
# noinspection RubyScope
|
172
|
+
result += @flags&.compression? ? payload_compressed : payload
|
173
|
+
|
174
|
+
result
|
175
|
+
end
|
176
|
+
|
177
|
+
# recalculates the payload size
|
178
|
+
def re_calc_payload_size
|
179
|
+
@payload_size = content_to_bytes.length
|
180
|
+
end
|
181
|
+
|
182
|
+
# calculates the frame size including header and payload, takes compression, group identity and everything
|
183
|
+
# into effect
|
184
|
+
#
|
185
|
+
# @return [Integer] the frame size in bytes
|
186
|
+
def frame_size
|
187
|
+
compression = compression? ? 4 : 0
|
188
|
+
group_identity = group_identity? ? 1 : 0
|
189
|
+
|
190
|
+
# noinspection RubyMismatchedReturnType
|
191
|
+
@header_size + @payload_size + compression + group_identity
|
192
|
+
end
|
193
|
+
|
194
|
+
# determined if the frame is alter preserved
|
195
|
+
#
|
196
|
+
# @return [Boolean] true if alter preserved, else false
|
197
|
+
def tag_alter_preservation?
|
198
|
+
return false if @flags.nil?
|
199
|
+
|
200
|
+
@flags.tag_alter_preservation?
|
201
|
+
end
|
202
|
+
|
203
|
+
# determined if the file is alter preserved
|
204
|
+
#
|
205
|
+
# @return [Boolean] true if the file is alter preserved, else false
|
206
|
+
def file_alter_preservation?
|
207
|
+
return false if @flags.nil?
|
208
|
+
|
209
|
+
@flags.file_alter_preservation?
|
210
|
+
end
|
211
|
+
|
212
|
+
# determined if the file frame is read only
|
213
|
+
#
|
214
|
+
# @return [Boolean] true if frame is read only, else false
|
215
|
+
def read_only?
|
216
|
+
return false if @flags.nil?
|
217
|
+
|
218
|
+
@flags.read_only?
|
219
|
+
end
|
220
|
+
|
221
|
+
# determined if the frame is compressed
|
222
|
+
#
|
223
|
+
# @return [Boolean] true if the frame is compressed, else false
|
224
|
+
def compression?
|
225
|
+
return false if @flags.nil?
|
226
|
+
|
227
|
+
@flags.compression?
|
228
|
+
end
|
229
|
+
|
230
|
+
# determined if the frame is encrypted
|
231
|
+
#
|
232
|
+
# @return [Boolean] true if the frame is encrypted, else false
|
233
|
+
def encryption?
|
234
|
+
return false if @flags.nil?
|
235
|
+
|
236
|
+
@flags.encryption?
|
237
|
+
end
|
238
|
+
|
239
|
+
# determined if the frame has a group identity
|
240
|
+
#
|
241
|
+
# @return [Boolean] true if the frame has a group identity, else false
|
242
|
+
def group_identity?
|
243
|
+
return false if @flags.nil?
|
244
|
+
|
245
|
+
@flags.group_identity?
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|