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,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
|