id3tag 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|