id3tag 0.8.0 → 0.9.0
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 +4 -4
- data/.gitignore +3 -46
- data/.travis.yml +6 -5
- data/id3tag.gemspec +8 -8
- data/lib/id3tag/audio_file.rb +24 -6
- data/lib/id3tag/id3_v2_tag_header.rb +4 -4
- data/lib/id3tag/number_util.rb +1 -0
- data/lib/id3tag/tag.rb +2 -2
- data/lib/id3tag/version.rb +1 -1
- data/spec/features/can_read_non_audio_files_spec.rb +8 -7
- data/spec/features/can_read_tag_v1_spec.rb +6 -6
- data/spec/lib/id3tag/audio_file_spec.rb +85 -15
- data/spec/lib/id3tag/frames/util/genre_name_by_id_finder_spec.rb +2 -2
- data/spec/lib/id3tag/frames/v1/comments_frame_spec.rb +7 -7
- data/spec/lib/id3tag/frames/v1/genre_frame_spec.rb +3 -3
- data/spec/lib/id3tag/frames/v1/text_frame_spec.rb +2 -2
- data/spec/lib/id3tag/frames/v1/track_nr_frame_spec.rb +3 -3
- data/spec/lib/id3tag/frames/v2/basic_frame_spec.rb +56 -16
- data/spec/lib/id3tag/frames/v2/comments_frame_spec.rb +5 -5
- data/spec/lib/id3tag/frames/v2/frame_fabricator_spec.rb +8 -8
- data/spec/lib/id3tag/frames/v2/frame_flags_spec.rb +539 -108
- data/spec/lib/id3tag/frames/v2/genre_frame/genre_parser_24_spec.rb +3 -3
- data/spec/lib/id3tag/frames/v2/genre_frame/genre_parser_pre_24_spec.rb +8 -8
- data/spec/lib/id3tag/frames/v2/genre_frame_spec.rb +9 -9
- data/spec/lib/id3tag/frames/v2/picture_frame_spec.rb +37 -17
- data/spec/lib/id3tag/frames/v2/text_frame_spec.rb +7 -7
- data/spec/lib/id3tag/frames/v2/unique_file_id_frame_spec.rb +4 -4
- data/spec/lib/id3tag/frames/v2/user_text_frame_spec.rb +19 -4
- data/spec/lib/id3tag/id3_v1_frame_parser_spec.rb +10 -10
- data/spec/lib/id3tag/id3_v2_frame_parser_spec.rb +6 -6
- data/spec/lib/id3tag/id3_v2_tag_header_spec.rb +105 -21
- data/spec/lib/id3tag/id3tag_spec.rb +1 -1
- data/spec/lib/id3tag/io_util_spec.rb +4 -4
- data/spec/lib/id3tag/number_util_spec.rb +10 -2
- data/spec/lib/id3tag/string_util_spec.rb +10 -10
- data/spec/lib/id3tag/synchsafe_integer_spec.rb +6 -6
- data/spec/lib/id3tag/tag_spec.rb +192 -91
- metadata +28 -29
- data/Gemfile.lock +0 -77
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 29c3cc9a5756c4275ce59da395e80643854f8dec
|
4
|
+
data.tar.gz: a5e6942ac0b6fa2d7242682c32506842421f68ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5bcb3bda2e129e52030e22e4bde12b016646205967a7f4ab7be13337d6bca4483f6b3b66217a822a2d9f4f5b22d0b114f58a85b9c853275d23e6a3433218b5dc
|
7
|
+
data.tar.gz: d711c5880bfaca342a734ee69c1f9c93e635e09ea62535d145b7dbe000b80ee496a2497e706da9c007b98bedd58afd0288a19d2e4cb23c436b9e4b204c3ebe09
|
data/.gitignore
CHANGED
@@ -1,50 +1,7 @@
|
|
1
|
-
# rcov generated
|
2
1
|
coverage
|
3
|
-
|
4
|
-
|
5
|
-
# rdoc generated
|
6
|
-
rdoc
|
7
|
-
|
8
|
-
# yard generated
|
9
|
-
doc
|
10
|
-
.yardoc
|
11
|
-
|
12
|
-
# bundler
|
2
|
+
Gemfile.lock
|
13
3
|
.bundle
|
14
|
-
|
15
|
-
# jeweler generated
|
16
|
-
pkg
|
17
4
|
tags
|
5
|
+
vendor/bundle
|
6
|
+
bin
|
18
7
|
|
19
|
-
# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
|
20
|
-
#
|
21
|
-
# * Create a file at ~/.gitignore
|
22
|
-
# * Include files you want ignored
|
23
|
-
# * Run: git config --global core.excludesfile ~/.gitignore
|
24
|
-
#
|
25
|
-
# After doing this, these files will be ignored in all your git projects,
|
26
|
-
# saving you from having to 'pollute' every project you touch with them
|
27
|
-
#
|
28
|
-
# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
|
29
|
-
#
|
30
|
-
# For MacOS:
|
31
|
-
#
|
32
|
-
#.DS_Store
|
33
|
-
|
34
|
-
# For TextMate
|
35
|
-
#*.tmproj
|
36
|
-
#tmtags
|
37
|
-
|
38
|
-
# For emacs:
|
39
|
-
#*~
|
40
|
-
#\#*
|
41
|
-
#.\#*
|
42
|
-
|
43
|
-
# For vim:
|
44
|
-
#*.swp
|
45
|
-
|
46
|
-
# For redcar:
|
47
|
-
#.redcar
|
48
|
-
|
49
|
-
# For rubinius:
|
50
|
-
#*.rbc
|
data/.travis.yml
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
language: ruby
|
2
|
+
before_install:
|
3
|
+
- gem install bundler
|
2
4
|
rvm:
|
3
5
|
- ruby-head
|
4
|
-
- 2.1.2
|
5
|
-
- 2.0.0
|
6
|
-
- 1.9.3
|
7
|
-
- jruby-19mode
|
8
6
|
- jruby-head
|
9
|
-
-
|
7
|
+
- 2.3.0
|
8
|
+
- 2.2.4
|
9
|
+
- 2.1.8
|
10
|
+
- jruby-9.0.5.0
|
10
11
|
matrix:
|
11
12
|
allow_failures:
|
12
13
|
- rvm: jruby-head
|
data/id3tag.gemspec
CHANGED
@@ -5,7 +5,7 @@ Gem::Specification.new do |s|
|
|
5
5
|
s.name = "id3tag"
|
6
6
|
s.version = ID3Tag::VERSION
|
7
7
|
s.authors = ["Krists Ozols"]
|
8
|
-
s.email = "krists@
|
8
|
+
s.email = "krists.ozols@gmail.com"
|
9
9
|
s.description = "Native Ruby ID3 tag reader that aims for 100% covarage of ID3v2.x and ID3v1.x standards"
|
10
10
|
s.summary = "Native Ruby ID3 tag reader that aims for 100% covarage of ID3v2.x and ID3v1.x standards"
|
11
11
|
s.homepage = "http://github.com/krists/id3tag"
|
@@ -18,11 +18,11 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.require_paths = ["lib"]
|
19
19
|
s.required_ruby_version = Gem::Requirement.new(">= 1.9.2")
|
20
20
|
|
21
|
-
s.add_development_dependency "bundler"
|
22
|
-
s.add_development_dependency "rake"
|
23
|
-
s.add_development_dependency "rdoc", "~> 4.
|
24
|
-
s.add_development_dependency "rspec", "~>
|
25
|
-
s.add_development_dependency "simplecov"
|
26
|
-
s.add_development_dependency 'coveralls'
|
27
|
-
s.add_development_dependency 'pry'
|
21
|
+
s.add_development_dependency "bundler"
|
22
|
+
s.add_development_dependency "rake", "~> 11.1.1"
|
23
|
+
s.add_development_dependency "rdoc", "~> 4.2.2"
|
24
|
+
s.add_development_dependency "rspec", "~> 3.4.0"
|
25
|
+
s.add_development_dependency "simplecov", "~> 0.11.2"
|
26
|
+
s.add_development_dependency 'coveralls', "~> 0.8.13"
|
27
|
+
s.add_development_dependency 'pry', "~> 0.10.3"
|
28
28
|
end
|
data/lib/id3tag/audio_file.rb
CHANGED
@@ -24,17 +24,35 @@ module ID3Tag
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def v1_tag_body
|
27
|
-
if
|
28
|
-
@file.seek(-
|
27
|
+
if v1_tag_present?
|
28
|
+
@file.seek(-v1_tag_size, IO::SEEK_END)
|
29
|
+
@file.read
|
30
|
+
else
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def v1_tag_size
|
36
|
+
if v1_tag_present?
|
37
|
+
ID3V1_TAG_SIZE - IDV1_TAG_IDENTIFIER.size
|
29
38
|
else
|
30
|
-
|
39
|
+
0
|
31
40
|
end
|
32
|
-
@file.read
|
33
41
|
end
|
34
42
|
|
35
43
|
def v2_tag_body
|
36
|
-
|
37
|
-
|
44
|
+
if v2_tag_size > 0
|
45
|
+
@file.seek(v2_tag_frame_and_padding_position)
|
46
|
+
@file.read(v2_tag_frame_and_padding_size)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def v2_tag_size
|
51
|
+
if v2_tag_present?
|
52
|
+
v2_tag_header.tag_size
|
53
|
+
else
|
54
|
+
0
|
55
|
+
end
|
38
56
|
end
|
39
57
|
|
40
58
|
def v2_tag_version
|
@@ -19,19 +19,19 @@ module ID3Tag
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def unsynchronisation?
|
22
|
-
flags_byte
|
22
|
+
0b1000_0000 & flags_byte > 0
|
23
23
|
end
|
24
24
|
|
25
25
|
def extended_header?
|
26
|
-
flags_byte
|
26
|
+
0b100_0000 & flags_byte > 0
|
27
27
|
end
|
28
28
|
|
29
29
|
def experimental?
|
30
|
-
flags_byte
|
30
|
+
0b10_0000 & flags_byte > 0
|
31
31
|
end
|
32
32
|
|
33
33
|
def footer_present?
|
34
|
-
flags_byte
|
34
|
+
0b1_0000 & flags_byte > 0
|
35
35
|
end
|
36
36
|
|
37
37
|
def tag_size
|
data/lib/id3tag/number_util.rb
CHANGED
@@ -3,6 +3,7 @@ module ID3Tag
|
|
3
3
|
FORMAT_FOR_8_BIT_SIGNED_INTEGER = 'c'
|
4
4
|
FORMAT_FOR_32BIT_INTEGER = 'N'
|
5
5
|
def self.convert_string_to_32bit_integer(string)
|
6
|
+
raise(ArgumentError, "Input must be a string. #{string.inspect} given.") unless string.is_a?(::String)
|
6
7
|
integers = string.unpack(FORMAT_FOR_32BIT_INTEGER)
|
7
8
|
integers.first || raise(ArgumentError, "String: '#{string}' could not be decoded as 32-bit integer")
|
8
9
|
end
|
data/lib/id3tag/tag.rb
CHANGED
@@ -112,11 +112,11 @@ module ID3Tag
|
|
112
112
|
end
|
113
113
|
|
114
114
|
def should_and_could_read_v1_frames?
|
115
|
-
scope.include?(:v1) && audio_file.v1_tag_present?
|
115
|
+
scope.include?(:v1) && audio_file.v1_tag_present? && audio_file.v1_tag_size > 0
|
116
116
|
end
|
117
117
|
|
118
118
|
def should_and_could_read_v2_frames?
|
119
|
-
scope.include?(:v2) && audio_file.v2_tag_present?
|
119
|
+
scope.include?(:v2) && audio_file.v2_tag_present? && audio_file.v2_tag_size > 0
|
120
120
|
end
|
121
121
|
end
|
122
122
|
end
|
data/lib/id3tag/version.rb
CHANGED
@@ -5,12 +5,13 @@ describe 'can read any file and does not raise errors if no tag found' do
|
|
5
5
|
subject { ID3Tag.read(Tempfile.new('fake_mp3')) }
|
6
6
|
|
7
7
|
it 'should return blanks' do
|
8
|
-
subject.title.
|
9
|
-
subject.artist.
|
10
|
-
subject.album.
|
11
|
-
subject.genre.
|
12
|
-
subject.year.
|
13
|
-
subject.track_nr.
|
14
|
-
subject.frames.
|
8
|
+
expect(subject.title).to eq(nil)
|
9
|
+
expect(subject.artist).to eq(nil)
|
10
|
+
expect(subject.album).to eq(nil)
|
11
|
+
expect(subject.genre).to eq(nil)
|
12
|
+
expect(subject.year).to eq(nil)
|
13
|
+
expect(subject.track_nr).to eq(nil)
|
14
|
+
expect(subject.frames).to eq([])
|
15
|
+
expect(subject.frame_ids).to eq([])
|
15
16
|
end
|
16
17
|
end
|
@@ -5,11 +5,11 @@ describe 'can read v1 info from file' do
|
|
5
5
|
subject { ID3Tag.read(mp3_with_v1_1_tag) }
|
6
6
|
|
7
7
|
it 'reading file with only v1 tag' do
|
8
|
-
subject.title.
|
9
|
-
subject.artist.
|
10
|
-
subject.album.
|
11
|
-
subject.genre.
|
12
|
-
subject.year.
|
13
|
-
subject.track_nr.
|
8
|
+
expect(subject.title).to eq("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaA")
|
9
|
+
expect(subject.artist).to eq("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbB")
|
10
|
+
expect(subject.album).to eq("cccccccccccccccccccccccccccccC")
|
11
|
+
expect(subject.genre).to eq("Blues")
|
12
|
+
expect(subject.year).to eq("2003")
|
13
|
+
expect(subject.track_nr).to eq("1")
|
14
14
|
end
|
15
15
|
end
|
@@ -3,31 +3,89 @@ describe ID3Tag::AudioFile do
|
|
3
3
|
let(:mp3_with_v1_tag) { mp3_fixture('id3v1_without_track_nr.mp3') }
|
4
4
|
let(:mp3_with_v2_tag) { mp3_fixture('id3v2.mp3') }
|
5
5
|
let(:mp3_with_v1_and_v2_tags) { mp3_fixture('id3v1_and_v2.mp3') }
|
6
|
+
let(:broken_mp3) do
|
7
|
+
file = Tempfile.new("broken_mp3")
|
8
|
+
file.write("just something..but not enough")
|
9
|
+
file
|
10
|
+
end
|
6
11
|
|
7
12
|
describe "Tag presence checking methods" do
|
8
13
|
context "reading file only with ID3v1 tag" do
|
9
14
|
subject { described_class.new(mp3_with_v1_tag) }
|
10
|
-
|
11
|
-
|
15
|
+
|
16
|
+
describe '#v1_tag_present?' do
|
17
|
+
subject { super().v1_tag_present? }
|
18
|
+
it { is_expected.to eq(true) }
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#v1_tag_size' do
|
22
|
+
subject { super().v1_tag_size }
|
23
|
+
it { is_expected.to eq(125) }
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
describe '#v2_tag_present?' do
|
28
|
+
subject { super().v2_tag_present? }
|
29
|
+
it { is_expected.to eq(false) }
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#v2_tag_size' do
|
33
|
+
subject { super().v2_tag_size }
|
34
|
+
it { is_expected.to eq(0) }
|
35
|
+
end
|
12
36
|
end
|
13
37
|
|
14
38
|
context "reading file only with ID3v2 tag" do
|
15
39
|
subject { described_class.new(mp3_with_v2_tag) }
|
16
|
-
|
17
|
-
|
40
|
+
|
41
|
+
describe '#v1_tag_present?' do
|
42
|
+
subject { super().v1_tag_present? }
|
43
|
+
it { is_expected.to eq(false) }
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#v1_tag_size' do
|
47
|
+
subject { super().v1_tag_size }
|
48
|
+
it { is_expected.to eq(0) }
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#v2_tag_present?' do
|
52
|
+
subject { super().v2_tag_present? }
|
53
|
+
it { is_expected.to eq(true) }
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#v2_tag_size' do
|
57
|
+
subject { super().v2_tag_size }
|
58
|
+
it { is_expected.to eq(246) }
|
59
|
+
end
|
18
60
|
end
|
19
61
|
|
20
62
|
context "reading file with ID3v1 and ID3v2 tags" do
|
21
63
|
subject { described_class.new(mp3_with_v1_and_v2_tags) }
|
22
|
-
|
23
|
-
|
64
|
+
|
65
|
+
describe '#v1_tag_present?' do
|
66
|
+
subject { super().v1_tag_present? }
|
67
|
+
it { is_expected.to eq(true) }
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '#v2_tag_present?' do
|
71
|
+
subject { super().v2_tag_present? }
|
72
|
+
it { is_expected.to eq(true) }
|
73
|
+
end
|
24
74
|
end
|
25
75
|
end
|
26
76
|
|
27
77
|
describe "#v1_tag_body" do
|
28
|
-
|
29
|
-
|
30
|
-
|
78
|
+
context "when reading file with alteast 125 bytes" do
|
79
|
+
subject { described_class.new(mp3_with_v1_tag) }
|
80
|
+
it "should return last 125 bytes of audio file" do
|
81
|
+
expect(subject.v1_tag_body).to eq(File.read(mp3_with_v1_tag, 125, 579))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
context "when reading file with size less ID3v1 tag" do
|
85
|
+
subject { described_class.new(broken_mp3) }
|
86
|
+
it "should return as much bytes as possible" do
|
87
|
+
expect(subject.v1_tag_body).to eq(nil)
|
88
|
+
end
|
31
89
|
end
|
32
90
|
end
|
33
91
|
|
@@ -35,18 +93,18 @@ describe ID3Tag::AudioFile do
|
|
35
93
|
context "when extended header is not present" do
|
36
94
|
subject { described_class.new(StringIO.new("ID3\u0003\u0000\u0000\u0000\u0000\u0000\u0003ABC")) }
|
37
95
|
it "should return frame and padding bytes" do
|
38
|
-
subject.v2_tag_body.
|
96
|
+
expect(subject.v2_tag_body).to eq("ABC")
|
39
97
|
end
|
40
98
|
end
|
41
99
|
context "when extended header is present" do
|
42
100
|
subject { described_class.new(StringIO.new("ID3\u0003\u0000\u0040\u0000\u0000\u0000\u0011" + "\u0000\u0000\u0000\u000A" + ("\u0000" * 10) + "ABC")) }
|
43
101
|
it "should return frame and padding bytes" do
|
44
|
-
subject.v2_tag_body.
|
102
|
+
expect(subject.v2_tag_body).to eq("ABC")
|
45
103
|
end
|
46
104
|
context "when tag verison is v.2.4 and extended header size is calculated differently" do
|
47
105
|
subject { described_class.new(StringIO.new("ID3\u0004\u0000\u0040\u0000\u0000\u0000\u0011" + "\u0000\u0000\u0000\u000A" + ("\u0000" * 6) + "ABC")) }
|
48
106
|
it "should return frame and padding bytes" do
|
49
|
-
subject.v2_tag_body.
|
107
|
+
expect(subject.v2_tag_body).to eq("ABC")
|
50
108
|
end
|
51
109
|
end
|
52
110
|
end
|
@@ -54,8 +112,20 @@ describe ID3Tag::AudioFile do
|
|
54
112
|
|
55
113
|
context "when reading file with v2.3.0 tag" do
|
56
114
|
subject { described_class.new(StringIO.new("ID3\u0003\u0000")) }
|
57
|
-
|
58
|
-
|
59
|
-
|
115
|
+
|
116
|
+
describe '#v2_tag_version' do
|
117
|
+
subject { super().v2_tag_version }
|
118
|
+
it { is_expected.to eq '3.0' }
|
119
|
+
end
|
120
|
+
|
121
|
+
describe '#v2_tag_major_version_number' do
|
122
|
+
subject { super().v2_tag_major_version_number }
|
123
|
+
it { is_expected.to eq 3 }
|
124
|
+
end
|
125
|
+
|
126
|
+
describe '#v2_tag_minor_version_number' do
|
127
|
+
subject { super().v2_tag_minor_version_number }
|
128
|
+
it { is_expected.to eq 0 }
|
129
|
+
end
|
60
130
|
end
|
61
131
|
end
|
@@ -7,12 +7,12 @@ describe ID3Tag::Frames::Util::GenreNames do
|
|
7
7
|
|
8
8
|
context "when calling with id 0 what represents Blues" do
|
9
9
|
let(:id) { 0 }
|
10
|
-
it {
|
10
|
+
it { is_expected.to eq('Blues') }
|
11
11
|
end
|
12
12
|
|
13
13
|
context "when calling with id 17 what represents Rock" do
|
14
14
|
let(:id) { 17 }
|
15
|
-
it {
|
15
|
+
it { is_expected.to eq('Rock') }
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -2,38 +2,38 @@ require 'spec_helper'
|
|
2
2
|
describe ID3Tag::Frames::V1::CommentsFrame do
|
3
3
|
describe '#id' do
|
4
4
|
subject { described_class.new('comments', nil).id }
|
5
|
-
it {
|
5
|
+
it { is_expected.to eq('comments') }
|
6
6
|
end
|
7
7
|
|
8
8
|
describe '#content' do
|
9
9
|
context 'when comment is present' do
|
10
10
|
subject { described_class.new('comments', 'some comment about the song').content }
|
11
|
-
it {
|
11
|
+
it { is_expected.to eq('some comment about the song') }
|
12
12
|
end
|
13
13
|
|
14
14
|
context 'when comment is not present' do
|
15
15
|
subject { described_class.new('comments', '').content }
|
16
|
-
it {
|
16
|
+
it { is_expected.to eq('') }
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
20
|
describe "#text" do
|
21
21
|
subject { described_class.new('comments', 'some comment about the song') }
|
22
22
|
it "should be the same as #content" do
|
23
|
-
subject.
|
24
|
-
subject.text.
|
23
|
+
allow(subject).to receive(:content) { "ZXC" }
|
24
|
+
expect(subject.text).to eq(subject.content)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
28
|
describe '#language' do
|
29
29
|
it 'should be unknown' do
|
30
|
-
described_class.new('comments', '').language.
|
30
|
+
expect(described_class.new('comments', '').language).to eq('unknown')
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
34
|
describe '#desciption' do
|
35
35
|
it 'should be unknown' do
|
36
|
-
described_class.new('comments', '').desciption.
|
36
|
+
expect(described_class.new('comments', '').desciption).to eq('unknown')
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|