id3tag 0.0.0 → 0.1.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 +15 -0
- data/.travis.yml +7 -0
- data/Gemfile +9 -9
- data/Gemfile.lock +36 -14
- data/README.md +73 -0
- data/Rakefile +3 -2
- data/VERSION +1 -1
- data/id3tag.gemspec +75 -19
- data/lib/id3tag.rb +34 -0
- data/lib/id3tag/audio_file.rb +90 -0
- data/lib/id3tag/frame_id_advisor.rb +50 -0
- data/lib/id3tag/frames/util/genre_names.rb +140 -0
- data/lib/id3tag/frames/v1/comments_frame.rb +24 -0
- data/lib/id3tag/frames/v1/genre_frame.rb +19 -0
- data/lib/id3tag/frames/v1/text_frame.rb +27 -0
- data/lib/id3tag/frames/v1/track_nr_frame.rb +18 -0
- data/lib/id3tag/frames/v2/basic_frame.rb +26 -0
- data/lib/id3tag/frames/v2/comments_frame.rb +44 -0
- data/lib/id3tag/frames/v2/frame_fabricator.rb +36 -0
- data/lib/id3tag/frames/v2/genre_frame.rb +34 -0
- data/lib/id3tag/frames/v2/genre_frame/genre_parser.rb +36 -0
- data/lib/id3tag/frames/v2/genre_frame/genre_parser_24.rb +15 -0
- data/lib/id3tag/frames/v2/genre_frame/genre_parser_pre_24.rb +39 -0
- data/lib/id3tag/frames/v2/text_frame.rb +37 -0
- data/lib/id3tag/frames/v2/unique_file_id_frame.rb +25 -0
- data/lib/id3tag/id3_v1_frame_parser.rb +87 -0
- data/lib/id3tag/id3_v2_frame_parser.rb +72 -0
- data/lib/id3tag/id3_v2_tag_header.rb +61 -0
- data/lib/id3tag/number_util.rb +14 -0
- data/lib/id3tag/string_util.rb +7 -0
- data/lib/id3tag/synchsafe_integer.rb +28 -0
- data/lib/id3tag/tag.rb +121 -0
- data/spec/fixtures/id3v1_and_v2.mp3 +0 -0
- data/spec/fixtures/id3v1_with_track_nr.mp3 +0 -0
- data/spec/fixtures/id3v1_without_track_nr.mp3 +0 -0
- data/spec/fixtures/id3v2.mp3 +0 -0
- data/spec/lib/id3tag/audio_file_spec.rb +40 -0
- data/spec/lib/id3tag/frames/util/genre_name_by_id_finder_spec.rb +18 -0
- data/spec/lib/id3tag/frames/v1/comments_frame_spec.rb +31 -0
- data/spec/lib/id3tag/frames/v1/genre_frame_spec.rb +20 -0
- data/spec/lib/id3tag/frames/v1/text_frame_spec.rb +14 -0
- data/spec/lib/id3tag/frames/v1/track_nr_frame_spec.rb +19 -0
- data/spec/lib/id3tag/frames/v2/basic_frame_spec.rb +25 -0
- data/spec/lib/id3tag/frames/v2/comments_frame_spec.rb +45 -0
- data/spec/lib/id3tag/frames/v2/genre_frame/genre_parser_24_spec.rb +26 -0
- data/spec/lib/id3tag/frames/v2/genre_frame/genre_parser_pre_24_spec.rb +48 -0
- data/spec/lib/id3tag/frames/v2/genre_frame_spec.rb +44 -0
- data/spec/lib/id3tag/frames/v2/text_frame_spec.rb +59 -0
- data/spec/lib/id3tag/frames/v2/unique_file_id_frame_spec.rb +31 -0
- data/spec/lib/id3tag/id3_v1_frame_parser_spec.rb +67 -0
- data/spec/lib/id3tag/id3_v2_frame_parser_spec.rb +18 -0
- data/spec/lib/id3tag/id3_v2_tag_header_spec.rb +60 -0
- data/spec/lib/id3tag/id3tag_spec.rb +17 -0
- data/spec/lib/id3tag/number_util_spec.rb +24 -0
- data/spec/lib/id3tag/string_util_spec.rb +21 -0
- data/spec/lib/id3tag/synchsafe_integer_spec.rb +14 -0
- data/spec/lib/id3tag/tag_spec.rb +84 -0
- data/spec/spec_helper.rb +9 -1
- data/spec/support/mp3_fixtures.rb +4 -0
- metadata +102 -38
- data/README.rdoc +0 -19
- data/spec/id3tag_spec.rb +0 -7
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
describe ID3Tag::AudioFile do
|
3
|
+
let(:mp3_with_v1_tag) { mp3_fixture('id3v1_without_track_nr.mp3') }
|
4
|
+
let(:mp3_with_v2_tag) { mp3_fixture('id3v2.mp3') }
|
5
|
+
let(:mp3_with_v1_and_v2_tags) { mp3_fixture('id3v1_and_v2.mp3') }
|
6
|
+
|
7
|
+
describe "Tag presence checking methods" do
|
8
|
+
context "reading file only with ID3v1 tag" do
|
9
|
+
subject { described_class.new(mp3_with_v1_tag) }
|
10
|
+
its(:v1_tag_present?) { should be_true }
|
11
|
+
its(:v2_tag_present?) { should be_false }
|
12
|
+
end
|
13
|
+
|
14
|
+
context "reading file only with ID3v2 tag" do
|
15
|
+
subject { described_class.new(mp3_with_v2_tag) }
|
16
|
+
its(:v1_tag_present?) { should be_false }
|
17
|
+
its(:v2_tag_present?) { should be_true }
|
18
|
+
end
|
19
|
+
|
20
|
+
context "reading file with ID3v1 and ID3v2 tags" do
|
21
|
+
subject { described_class.new(mp3_with_v1_and_v2_tags) }
|
22
|
+
its(:v1_tag_present?) { should be_true }
|
23
|
+
its(:v2_tag_present?) { should be_true }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#v1_tag_body" do
|
28
|
+
subject { described_class.new(mp3_with_v1_tag) }
|
29
|
+
it "should return last 125 bytes of audio file" do
|
30
|
+
subject.v1_tag_body.should == File.read(mp3_with_v1_tag, 125, 579)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#v2_tag_body" do
|
35
|
+
subject { described_class.new(mp3_with_v2_tag) }
|
36
|
+
it "should return frame and padding bytes" do
|
37
|
+
subject.v2_tag_body.should == File.read(mp3_with_v2_tag, 246, 10)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ID3Tag::Frames::Util::GenreNames do
|
4
|
+
|
5
|
+
describe 'self#find_by_id' do
|
6
|
+
subject { described_class.find_by_id(id) }
|
7
|
+
|
8
|
+
context "when calling with id 0 what represents Blues" do
|
9
|
+
let(:id) { 0 }
|
10
|
+
it { should eq('Blues') }
|
11
|
+
end
|
12
|
+
|
13
|
+
context "when calling with id 17 what represents Rock" do
|
14
|
+
let(:id) { 17 }
|
15
|
+
it { should eq('Rock') }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe ID3Tag::Frames::V1::CommentsFrame do
|
3
|
+
describe '#id' do
|
4
|
+
subject { described_class.new('comments', nil).id }
|
5
|
+
it { should == 'comments' }
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#content' do
|
9
|
+
context 'when comment is present' do
|
10
|
+
subject { described_class.new('comments', 'some comment about the song').content }
|
11
|
+
it { should == 'some comment about the song' }
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'when comment is not present' do
|
15
|
+
subject { described_class.new('comments', '').content }
|
16
|
+
it { should == '' }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#language' do
|
21
|
+
it 'should be unknown' do
|
22
|
+
described_class.new('comments', '').language.should eq('unknown')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#desciption' do
|
27
|
+
it 'should be unknown' do
|
28
|
+
described_class.new('comments', '').desciption.should eq('unknown')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe ID3Tag::Frames::V1::GenreFrame do
|
3
|
+
describe '#id' do
|
4
|
+
subject { described_class.new('genre', nil).id }
|
5
|
+
it { should == 'genre' }
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#content' do
|
9
|
+
context 'when genre is blues' do
|
10
|
+
subject { described_class.new('genre', [0].pack('c')).content }
|
11
|
+
it { should == 'Blues' }
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'when genre is metal' do
|
15
|
+
subject { described_class.new('genre', [9].pack('c')).content }
|
16
|
+
it { should == 'Metal' }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe ID3Tag::Frames::V1::TextFrame do
|
3
|
+
describe '#id' do
|
4
|
+
subject { described_class.new('album', nil).id }
|
5
|
+
it { should == 'album' }
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#content' do
|
9
|
+
context 'when containing track artist' do
|
10
|
+
subject { described_class.new('artist', 'some fancy artist').content }
|
11
|
+
it { should == 'some fancy artist' }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe ID3Tag::Frames::V1::TrackNrFrame do
|
3
|
+
describe '#id' do
|
4
|
+
subject { described_class.new('track_nr', nil).id }
|
5
|
+
it { should == 'track_nr' }
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#content' do
|
9
|
+
context 'when nr is 3' do
|
10
|
+
subject { described_class.new('track_nr', [3].pack('c')).content }
|
11
|
+
it { should == '3' }
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'when nr is 11' do
|
15
|
+
subject { described_class.new('track_nr', [11].pack('c')).content }
|
16
|
+
it { should == '11' }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ID3Tag::Frames::V2::BasicFrame do
|
4
|
+
let(:id) { 'foo' }
|
5
|
+
let(:raw_content) { 'bar' }
|
6
|
+
let(:flags) { nil }
|
7
|
+
let(:major_version_number) { 4 }
|
8
|
+
let(:frame) { described_class.new(id, raw_content, flags, major_version_number) }
|
9
|
+
|
10
|
+
describe '#id' do
|
11
|
+
subject { frame.id }
|
12
|
+
it { should == :foo }
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#content' do
|
16
|
+
subject { frame.content }
|
17
|
+
it { should == 'bar' }
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#inspect' do
|
21
|
+
it 'should be pretty inspectable' do
|
22
|
+
frame.inspect.should eq('<ID3Tag::Frames::V2::BasicFrame foo: bar>')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe ID3Tag::Frames::V2::CommentsFrame do
|
5
|
+
let(:id) { "COMM" }
|
6
|
+
let(:raw_content) do
|
7
|
+
text.encode(target_encoding).prepend(separator).prepend(short_desc).prepend(lang).prepend(encoding)
|
8
|
+
end
|
9
|
+
let(:separator) { "\x00".force_encoding(target_encoding) }
|
10
|
+
let(:short_desc) { 'bob once said'.force_encoding(target_encoding) }
|
11
|
+
let(:lang) { 'lav'.force_encoding(target_encoding) }
|
12
|
+
let(:encoding) { encoding_byte.force_encoding(target_encoding) }
|
13
|
+
let(:flags) { nil }
|
14
|
+
let(:major_version_number) { 4 }
|
15
|
+
let(:target_encoding) { Encoding::UTF_8 }
|
16
|
+
let(:encoding_byte) { "\x03" }
|
17
|
+
let(:text) { "Glāzšķūņrūķīši" }
|
18
|
+
let(:frame) { described_class.new(id, raw_content, flags, major_version_number) }
|
19
|
+
|
20
|
+
describe '#id' do
|
21
|
+
subject { frame.id }
|
22
|
+
it { should == :COMM }
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#content' do
|
26
|
+
subject { frame.content }
|
27
|
+
it { should == 'Glāzšķūņrūķīši' }
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#language' do
|
31
|
+
subject { frame.language }
|
32
|
+
it { should == 'lav' }
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#description' do
|
36
|
+
subject { frame.description }
|
37
|
+
it { should == 'bob once said' }
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#inspect' do
|
41
|
+
it 'should be pretty inspectable' do
|
42
|
+
frame.inspect.should eq('<ID3Tag::Frames::V2::CommentsFrame COMM: Glāzšķūņrūķīši>')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe ID3Tag::Frames::V2::GenreFrame::GenreParser24 do
|
5
|
+
describe '#genres' do
|
6
|
+
|
7
|
+
subject { described_class.new(genre_string).genres }
|
8
|
+
|
9
|
+
context "when version is 2.4" do
|
10
|
+
context "with remix and cover" do
|
11
|
+
let(:genre_string) { "RX\x00CR" }
|
12
|
+
it { should == ['Remix', 'Cover'] }
|
13
|
+
end
|
14
|
+
context "with refinement and number" do
|
15
|
+
let(:genre_string) { "ABC\x0017" }
|
16
|
+
it { should == ['ABC', 'Rock'] }
|
17
|
+
end
|
18
|
+
context "with one genre" do
|
19
|
+
let(:genre_string) { "17" }
|
20
|
+
it { should == ['Rock'] }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe ID3Tag::Frames::V2::GenreFrame::GenreParserPre24 do
|
5
|
+
describe '#genres' do
|
6
|
+
|
7
|
+
subject { described_class.new(genre_string).genres }
|
8
|
+
|
9
|
+
context "without refinements" do
|
10
|
+
context "with one genre" do
|
11
|
+
let(:genre_string) { '(17)' }
|
12
|
+
it { should == ['Rock'] }
|
13
|
+
end
|
14
|
+
context "with multiple genres" do
|
15
|
+
let(:genre_string) { '(17)(18)' }
|
16
|
+
it { should == ['Rock', 'Techno'] }
|
17
|
+
end
|
18
|
+
context "with remix and cover" do
|
19
|
+
let(:genre_string) { '(RX)(CR)' }
|
20
|
+
it { should == ['Remix', 'Cover'] }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
context "with refinements" do
|
24
|
+
context "with one genre" do
|
25
|
+
let(:genre_string) { '(17)qwerty' }
|
26
|
+
it { should == ['qwerty'] }
|
27
|
+
end
|
28
|
+
context "with miltiple genres but only 1 refinement" do
|
29
|
+
let(:genre_string) { '(0)(16)(17)qwerty' }
|
30
|
+
it { should == ['Blues', 'Reggae', 'qwerty'] }
|
31
|
+
end
|
32
|
+
context "with multiple genres" do
|
33
|
+
let(:genre_string) { '(17)qwerty(18)abcdef' }
|
34
|
+
it { should == ['qwerty', 'abcdef'] }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
context "with refinement containing '('" do
|
38
|
+
context "with one genre" do
|
39
|
+
let(:genre_string) { '(55)((I think...)' }
|
40
|
+
it { should == ['(I think...)'] }
|
41
|
+
end
|
42
|
+
context "with multiple genres" do
|
43
|
+
let(:genre_string) { '(55)((I think...)(17)' }
|
44
|
+
it { should == ['(I think...)', 'Rock'] }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe ID3Tag::Frames::V2::GenreFrame do
|
5
|
+
let(:id) { "TCON" }
|
6
|
+
let(:raw_content) { "(17)" }
|
7
|
+
let(:flags) { nil }
|
8
|
+
let(:major_version_number) { 3 }
|
9
|
+
let(:frame) { described_class.new(id, raw_content, flags, major_version_number) }
|
10
|
+
|
11
|
+
describe '#id' do
|
12
|
+
subject { frame.id }
|
13
|
+
it { should == :TCON }
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#content' do
|
17
|
+
subject { frame.content }
|
18
|
+
|
19
|
+
context "when one genre" do
|
20
|
+
before do
|
21
|
+
ID3Tag::Frames::V2::GenreFrame::GenreParserPre24.any_instance.stub(:genres) { ['A'] }
|
22
|
+
end
|
23
|
+
|
24
|
+
it { should eq('A') }
|
25
|
+
end
|
26
|
+
context "when two genres" do
|
27
|
+
before do
|
28
|
+
ID3Tag::Frames::V2::GenreFrame::GenreParserPre24.any_instance.stub(:genres) { ['A', 'B'] }
|
29
|
+
end
|
30
|
+
|
31
|
+
it { should eq('A, B') }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#inspect' do
|
36
|
+
before do
|
37
|
+
frame.stub(:content) { 'Rock' }
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should be pretty inspectable' do
|
41
|
+
frame.inspect.should eq('<ID3Tag::Frames::V2::GenreFrame TCON: Rock>')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe ID3Tag::Frames::V2::TextFrame do
|
5
|
+
let(:id) { "artist" }
|
6
|
+
let(:raw_content) { text.encode(target_encoding).prepend(encoding_byte.force_encoding(target_encoding)) }
|
7
|
+
let(:flags) { nil }
|
8
|
+
let(:major_version_number) { 4 }
|
9
|
+
|
10
|
+
let(:frame) { described_class.new(id, raw_content, flags, major_version_number) }
|
11
|
+
let(:target_encoding) { Encoding::UTF_8 }
|
12
|
+
let(:encoding_byte) { "\x03" }
|
13
|
+
let(:text) { "Glāzšķūņrūķīši" }
|
14
|
+
|
15
|
+
describe '#id' do
|
16
|
+
subject { frame.id }
|
17
|
+
it { should == :artist }
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#content' do
|
21
|
+
subject { frame.content }
|
22
|
+
|
23
|
+
context "when encoding byte is not present" do
|
24
|
+
let(:encoding_byte) { "" }
|
25
|
+
it { expect { subject }.to raise_error(ID3Tag::Frames::V2::TextFrame::UnsupportedTextEncoding) }
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when encoding is ISO08859_1" do
|
29
|
+
let(:target_encoding) { Encoding::ISO8859_1 }
|
30
|
+
let(:encoding_byte) { "\x00" }
|
31
|
+
let(:text) { "some fancy artist" }
|
32
|
+
it { should == 'some fancy artist' }
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when encoding is UTF_16" do
|
36
|
+
let(:target_encoding) { Encoding::UTF_16 }
|
37
|
+
let(:encoding_byte) { "\x01" }
|
38
|
+
it { should == 'Glāzšķūņrūķīši' }
|
39
|
+
end
|
40
|
+
|
41
|
+
context "when encoding is UTF_16BE" do
|
42
|
+
let(:target_encoding) { Encoding::UTF_16BE }
|
43
|
+
let(:encoding_byte) { "\x02" }
|
44
|
+
it { should == 'Glāzšķūņrūķīši' }
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when encoding is UTF_8" do
|
48
|
+
let(:target_encoding) { Encoding::UTF_8 }
|
49
|
+
let(:encoding_byte) { "\x03" }
|
50
|
+
it { should == 'Glāzšķūņrūķīši' }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#inspect' do
|
55
|
+
it 'should be pretty inspectable' do
|
56
|
+
frame.inspect.should eq('<ID3Tag::Frames::V2::TextFrame artist: Glāzšķūņrūķīši>')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe ID3Tag::Frames::V2::UniqueFileIdFrame do
|
5
|
+
let(:id) { "UFID" }
|
6
|
+
let(:raw_content) { "ZXC\x00foobar" }
|
7
|
+
let(:flags) { nil }
|
8
|
+
let(:major_version_number) { 4 }
|
9
|
+
let(:frame) { described_class.new(id, raw_content, flags, major_version_number) }
|
10
|
+
|
11
|
+
describe '#id' do
|
12
|
+
subject { frame.id }
|
13
|
+
it { should == :UFID }
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#owner_identifier' do
|
17
|
+
subject { frame.owner_identifier }
|
18
|
+
it { should == 'ZXC' }
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#content' do
|
22
|
+
subject { frame.content }
|
23
|
+
it { should == 'foobar' }
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#inspect' do
|
27
|
+
it 'should be pretty inspectable' do
|
28
|
+
frame.inspect.should eq('<ID3Tag::Frames::V2::UniqueFileIdFrame UFID: ZXC>')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|