id3tag 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MmYzYTExMDI2NWEzZjk2N2RhMTE3OTlmMDE0MTI1ZDY2MDRkM2Q0Mg==
4
+ NzNiYTE0YzFlYTA3ZGVhZDVhMDA4ZWUwNTU4ODcxNTc0ODQzMDg4MQ==
5
5
  data.tar.gz: !binary |-
6
- YTNmZTcwZGQzMTM3ZWEwNmE1NzJmMmQzYWFhMzI5MDQ0ZmFlN2NlYw==
6
+ ODNiYzM1NTUzNDg4YmJiNGExY2M4YjdkZjg4MjZlODYzMDc1YzNlOA==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- OWEwOTQ5MzU4MDU3YWJjYzcyOTVhM2M5NDhlYzcxZDc2ZTNjMTViYmJjM2Zh
10
- OTNlNWEyYzQzODcyOTY3ZGNiMDIzN2MyZGM2MjM2ZjQ1MDMxMDU0MWI5NDcw
11
- NmNmZDM2NmUzZTJhNDE0OWVjZTcyNzYzMWFiMDFiZjQ2YTQ0ODQ=
9
+ NTc3ZDYzY2FiYWEwNTgzOGU4MjM1NDYxYTU1NDgyNzZlYWVhNGRjOTAzZDk1
10
+ YmVhMTViOThlYmMxZDRkNzVmMDRkMGViZGU2MWY5ZjViY2IxMTMyOTIwYjUy
11
+ NmRhZWNkNDliMjA2MDliMGFmNDM3MzI5NWVmZTM0OTNjY2QzMmM=
12
12
  data.tar.gz: !binary |-
13
- ZTdkZDA0ZTdjOTMwNjE0OWE5Mzg1NGExMDQ4N2FkYTFlYWY0ZmY5OTg4NjFj
14
- MDdmYWY0YzMxZjI4Njc5YWFjMWQzNmI2MmFiMDg5YTA3YTY0ZjljMWNjMGNl
15
- OGI5OGYyOGY4ZDRiMzU0Y2ZmMTkxY2NhZjIyYmE3YWRmOWEyOTc=
13
+ MGE3NWQwMGM4MmQ3M2E3ZTkxNWMxYzkzODk2MTAxOThjYTc4ODllMGI1Yjgy
14
+ MGU1ZTdhZTMyYjdlYmFmMzFlMDZiZjIyN2IzMzk0NjYxZTJiNzk3YzdjMzg0
15
+ OTNmN2EwMWY5NGM3MzZkMmEwZDkwZmI0MmZkYTY2NzNiNjkzNTk=
data/README.md CHANGED
@@ -25,7 +25,8 @@ mp3_file = File.open('/path/to/your/favorite_song.mp3')
25
25
  tag = ID3Tag.read(mp3_file)
26
26
  puts "#{tag.artist} - #{tag.title}"
27
27
  ```
28
- `ID3Tag::Tag` class provides easy accessors to frames like `artist`, `title`, `album`, `year`, `comments`, `track_nr`, `genre` but you can read any frame by using `get_frame` or `get_frame_content`.
28
+ `ID3Tag::Tag` class provides easy accessors to frames like `artist`, `title`, `album`, `year`, `track_nr`, `genre` but you can read any frame by using `get_frame(id)` or `get_frame_content(id)` or browsing all frames by calling `frames`.
29
+ When using easy accessors to frames like `artist` the reader will look for v.2.x tags artist frame first and if it can not find it artist frame from v1.x will be returned (if v1.x tag exists)
29
30
 
30
31
  ```ruby
31
32
  mp3s = Dir.entries("/some/dir").select { |filename| filename =~ /\.mp3/i }
@@ -37,7 +38,6 @@ mp3s.each do |file|
37
38
  puts tag.title
38
39
  puts tag.album
39
40
  puts tag.year
40
- puts tag.comments
41
41
  puts tag.track_nr
42
42
  puts tag.genre
43
43
  puts "---"
@@ -45,8 +45,16 @@ mp3s.each do |file|
45
45
  end
46
46
  end
47
47
  ```
48
+ By default `ID3Tag` reads both v1.x and v2.x tags but it is possible to specify only one of them:
49
+ ```ruby
50
+ ID3Tag.read(file,:all) # default behaviour
51
+ ID3Tag.read(file,:v1) # Reads only v1.x tag
52
+ ID3Tag.read(file,:v2) # Reads only v2.x tag
53
+ ```
54
+
48
55
  You can inspect tag by calling `frame_ids` to see available frame ids or `frames` to return all frames
49
56
 
57
+
50
58
  ## Features
51
59
 
52
60
  * Can read v1.x, v2.2.x, v2.3.x, v2.4.x tags
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.1
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "id3tag"
8
- s.version = "0.1.0"
8
+ s.version = "0.1.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Krists Ozols"]
12
- s.date = "2013-05-01"
12
+ s.date = "2013-05-13"
13
13
  s.description = "Native Ruby ID3 tag reader that aims for 100% covarage of ID3v2.x and ID3v1.x standards"
14
14
  s.email = "krists@iesals.lv"
15
15
  s.extra_rdoc_files = [
@@ -51,6 +51,9 @@ Gem::Specification.new do |s|
51
51
  "lib/id3tag/string_util.rb",
52
52
  "lib/id3tag/synchsafe_integer.rb",
53
53
  "lib/id3tag/tag.rb",
54
+ "lib/id3tag/unsynchronization.rb",
55
+ "spec/features/can_read_non_audio_files_spec.rb",
56
+ "spec/features/can_read_tag_v1_spec.rb",
54
57
  "spec/fixtures/id3v1_and_v2.mp3",
55
58
  "spec/fixtures/id3v1_with_track_nr.mp3",
56
59
  "spec/fixtures/id3v1_without_track_nr.mp3",
@@ -63,8 +66,10 @@ Gem::Specification.new do |s|
63
66
  "spec/lib/id3tag/frames/v1/track_nr_frame_spec.rb",
64
67
  "spec/lib/id3tag/frames/v2/basic_frame_spec.rb",
65
68
  "spec/lib/id3tag/frames/v2/comments_frame_spec.rb",
69
+ "spec/lib/id3tag/frames/v2/frame_fabricator_spec.rb",
66
70
  "spec/lib/id3tag/frames/v2/genre_frame/genre_parser_24_spec.rb",
67
71
  "spec/lib/id3tag/frames/v2/genre_frame/genre_parser_pre_24_spec.rb",
72
+ "spec/lib/id3tag/frames/v2/genre_frame/genre_parser_spec.rb",
68
73
  "spec/lib/id3tag/frames/v2/genre_frame_spec.rb",
69
74
  "spec/lib/id3tag/frames/v2/text_frame_spec.rb",
70
75
  "spec/lib/id3tag/frames/v2/unique_file_id_frame_spec.rb",
@@ -1,9 +1,8 @@
1
- require "set"
2
1
  require "stringio"
3
-
4
2
  require "id3tag/synchsafe_integer"
5
3
  require "id3tag/audio_file"
6
4
  require "id3tag/id3_v2_tag_header"
5
+ require "id3tag/unsynchronization"
7
6
  require "id3tag/number_util"
8
7
  require "id3tag/string_util"
9
8
  require "id3tag/id3_v1_frame_parser"
@@ -26,7 +25,7 @@ require "id3tag/frames/v2/genre_frame/genre_parser_24"
26
25
  require "id3tag/frames/v2/frame_fabricator"
27
26
 
28
27
  module ID3Tag
29
- def self.read(source, version = nil)
28
+ def self.read(source, version = :all)
30
29
  tag = Tag.read(source, version)
31
30
  yield tag if block_given?
32
31
  tag
@@ -41,16 +41,6 @@ module ID3Tag
41
41
  v2_tag_header.minor_version_number
42
42
  end
43
43
 
44
- def greatest_tag_version
45
- if v2_tag_present?
46
- 2
47
- elsif v1_tag_present?
48
- 1
49
- else
50
- nil
51
- end
52
- end
53
-
54
44
  private
55
45
 
56
46
  def v2_tag_frame_and_padding_position
@@ -44,7 +44,13 @@ module ID3Tag
44
44
  end
45
45
 
46
46
  def advise(frame_name)
47
- COMMON_FRAME_IDS_BY_VERSION["v#{@version}.#{@major_version}"][frame_name]
47
+ version_ids && version_ids[frame_name]
48
+ end
49
+
50
+ private
51
+
52
+ def version_ids
53
+ COMMON_FRAME_IDS_BY_VERSION["v#{@version}.#{@major_version}"]
48
54
  end
49
55
  end
50
56
  end
@@ -12,7 +12,7 @@ module ID3Tag
12
12
  private
13
13
 
14
14
  def get_frames
15
- frames = Set.new
15
+ frames = []
16
16
  frames << title_frame
17
17
  frames << artist_frame
18
18
  frames << album_frame
@@ -38,6 +38,10 @@ module ID3Tag
38
38
  @tag_size ||= get_tag_size
39
39
  end
40
40
 
41
+ def inspect
42
+ "<#{self.class.name} version:2.#{version_number} size:#{tag_size} unsync:#{unsynchronisation?} ext.header:#{extended_header?} experimental:#{experimental?} footer:#{footer_present?}>"
43
+ end
44
+
41
45
  private
42
46
 
43
47
  def flags_byte
@@ -3,5 +3,17 @@ module ID3Tag
3
3
  def self.blank?(string)
4
4
  string !~ /[^[:space:]]/
5
5
  end
6
+
7
+ def self.do_unsynchronization(input)
8
+ unsynch = Unsynchronization.new(input)
9
+ unsynch.apply
10
+ unsynch.output
11
+ end
12
+
13
+ def self.undo_unsynchronization(input)
14
+ unsynch = Unsynchronization.new(input)
15
+ unsynch.remove
16
+ unsynch.output
17
+ end
6
18
  end
7
19
  end
@@ -3,48 +3,37 @@ module ID3Tag
3
3
  class MultipleFrameError < StandardError; end
4
4
 
5
5
  class << self
6
- def read(source, version = nil)
6
+ def read(source, version = :all)
7
7
  new(source, version)
8
8
  end
9
9
  end
10
10
 
11
- def initialize(source, version = nil)
11
+ def initialize(source, version = :all)
12
12
  @source, @version = source, version
13
13
  end
14
14
 
15
15
  def artist
16
- get_frame_content(frame_id_by_name(:artist))
16
+ get_frame_content(frame_id(:v2, :artist), frame_id(:v1, :artist))
17
17
  end
18
18
 
19
19
  def title
20
- get_frame_content(frame_id_by_name(:title))
20
+ get_frame_content(frame_id(:v2, :title), frame_id(:v1, :title))
21
21
  end
22
22
 
23
23
  def album
24
- get_frame_content(frame_id_by_name(:album))
24
+ get_frame_content(frame_id(:v2, :album), frame_id(:v1, :album))
25
25
  end
26
26
 
27
27
  def year
28
- get_frame_content(frame_id_by_name(:year))
29
- end
30
-
31
- def comments(language = nil)
32
- all_comments_frames = get_frames(frame_id_by_name(:comments))
33
- comments_frame = if language
34
- all_comments_frames.select { |frame| frame.language == language.to_s.downcase }.first
35
- else
36
- in_english = all_comments_frames.select { |frame| frame.language == 'eng' }
37
- in_english.first || all_comments_frames.first
38
- end
39
- comments_frame && comments_frame.content
28
+ get_frame_content(frame_id(:v2, :year), frame_id(:v1, :year))
40
29
  end
41
30
 
42
31
  def track_nr
43
- get_frame_content(frame_id_by_name(:track_nr))
32
+ get_frame_content(frame_id(:v2, :track_nr), frame_id(:v1, :track_nr))
44
33
  end
45
34
 
46
35
  def genre
47
- get_frame_content(frame_id_by_name(:genre))
36
+ get_frame_content(frame_id(:v2, :genre), frame_id(:v1, :genre))
48
37
  end
49
38
 
50
39
  def get_frame(frame_id)
@@ -65,52 +54,46 @@ module ID3Tag
65
54
  end
66
55
 
67
56
  def frames
68
- @frames ||= parse_frames
69
- end
70
-
71
- def get_frame_content(frame_id)
72
- frame = get_frame(frame_id)
73
- frame && frame.content
74
- end
75
-
76
- def parsable_version
77
- @parsable_version ||= calc_parsable_version
57
+ @frames ||= v2_frames + v1_frames
78
58
  end
79
59
 
80
- private
81
-
82
- def frame_id_by_name(name)
83
- case parsable_version
84
- when 2
85
- FrameIdAdvisor.new(2, audio_file.v2_tag_major_version_number).advise(name)
86
- when 1
87
- FrameIdAdvisor.new(1, 'x').advise(name)
60
+ def v2_frames
61
+ if audio_file.v2_tag_present? && [:v2, :all].include?(@version)
62
+ ID3V2FrameParser.new(audio_file.v2_tag_body, audio_file.v2_tag_major_version_number).frames
88
63
  else
89
- nil
64
+ []
90
65
  end
91
66
  end
92
67
 
93
- def parse_frames
94
- case parsable_version
95
- when 2
96
- ID3V2FrameParser.new(audio_file.v2_tag_body, audio_file.v2_tag_major_version_number).frames
97
- when 1
68
+ def v1_frames
69
+ if audio_file.v1_tag_present? && [:v1, :all].include?(@version)
98
70
  ID3V1FrameParser.new(audio_file.v1_tag_body).frames
99
71
  else
100
72
  []
101
73
  end
102
74
  end
103
75
 
104
- def calc_parsable_version
105
- if @version
106
- method = "v#{@version}_tag_present?"
107
- if audio_file.respond_to?(method) && audio_file.send(method)
108
- @version
109
- else
110
- nil
76
+ def get_frame_content(frame_id, *alt_frame_ids)
77
+ frame = nil
78
+ alt_frame_ids.unshift(frame_id).each do |frame_id|
79
+ frame = get_frame(frame_id)
80
+ break if frame
81
+ end
82
+ frame && frame.content
83
+ end
84
+
85
+ private
86
+
87
+ def frame_id(version, name)
88
+ case version
89
+ when :v2
90
+ if audio_file.v2_tag_present?
91
+ FrameIdAdvisor.new(2, audio_file.v2_tag_major_version_number).advise(name)
111
92
  end
93
+ when :v1
94
+ FrameIdAdvisor.new(1, 'x').advise(name)
112
95
  else
113
- audio_file.greatest_tag_version
96
+ nil
114
97
  end
115
98
  end
116
99
 
@@ -0,0 +1,45 @@
1
+ module ID3Tag
2
+ class Unsynchronization
3
+ def initialize(input)
4
+ @input = input
5
+ @encoding = input.encoding
6
+ end
7
+
8
+ def apply
9
+ loop do
10
+ current_byte = input_bytes.next
11
+ output_bytes << current_byte
12
+ next_byte = input_bytes.peek
13
+ if (current_byte == 255) && (next_byte > 224)
14
+ output_bytes << 0
15
+ end
16
+ end
17
+ end
18
+
19
+ def remove
20
+ prev_byte = nil
21
+ loop do
22
+ current_byte = input_bytes.next
23
+ next_byte = input_bytes.peek rescue 0
24
+ unless (prev_byte == 255) && (current_byte == 0) && (next_byte > 224)
25
+ output_bytes << current_byte
26
+ end
27
+ prev_byte = current_byte
28
+ end
29
+ end
30
+
31
+ def output
32
+ output_bytes.pack("C*").force_encoding(@encoding)
33
+ end
34
+
35
+ private
36
+
37
+ def input_bytes
38
+ @input_bytes ||= @input.unpack("C*").to_enum
39
+ end
40
+
41
+ def output_bytes
42
+ @output_bytes ||= []
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'can read any file and does not raise errors if no tag found' do
4
+ subject { ID3Tag.read(File.open('/dev/null')) }
5
+
6
+ it 'should return blanks' do
7
+ subject.title.should == nil
8
+ subject.artist.should == nil
9
+ subject.album.should == nil
10
+ subject.genre.should == nil
11
+ subject.year.should == nil
12
+ subject.track_nr.should == nil
13
+ subject.frames.should == []
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'can read v1 info from file' do
4
+ let(:mp3_with_v1_1_tag) { mp3_fixture('id3v1_with_track_nr.mp3') }
5
+ subject { ID3Tag.read(mp3_with_v1_1_tag) }
6
+
7
+ it 'reading file with only v1 tag' do
8
+ subject.title.should == "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaA"
9
+ subject.artist.should == "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbB"
10
+ subject.album.should == "cccccccccccccccccccccccccccccC"
11
+ subject.genre.should == "Blues"
12
+ subject.year.should == "2003"
13
+ subject.track_nr.should == "1"
14
+ end
15
+ end
File without changes
File without changes
File without changes
@@ -32,9 +32,24 @@ describe ID3Tag::AudioFile do
32
32
  end
33
33
 
34
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)
35
+ context "when extended header is not present" do
36
+ subject { described_class.new(StringIO.new("ID3\u0003\u0000\u0000\u0000\u0000\u0000\u0003ABC")) }
37
+ it "should return frame and padding bytes" do
38
+ subject.v2_tag_body.should == "ABC"
39
+ end
38
40
  end
41
+ context "when extended header is present" do
42
+ subject { described_class.new(StringIO.new("ID3\u0003\u0000\u0040\u0000\u0000\u0000\u0011" + "\u0000\u0000\u0000\u000E" + ("\u0000" * 10) + "ABC")) }
43
+ it "should return frame and padding bytes" do
44
+ subject.v2_tag_body.should == "ABC"
45
+ end
46
+ end
47
+ end
48
+
49
+ context "when reading file with v2.3.0 tag" do
50
+ subject { described_class.new(StringIO.new("ID3\u0003\u0000")) }
51
+ its(:v2_tag_version) { should eq '3.0' }
52
+ its(:v2_tag_major_version_number) { should eq 3 }
53
+ its(:v2_tag_minor_version_number) { should eq 0 }
39
54
  end
40
55
  end
@@ -17,6 +17,14 @@ describe ID3Tag::Frames::V1::CommentsFrame do
17
17
  end
18
18
  end
19
19
 
20
+ describe "#text" do
21
+ subject { described_class.new('comments', 'some comment about the song') }
22
+ it "should be the same as #content" do
23
+ subject.stub(:content) { "ZXC" }
24
+ subject.text.should == subject.content
25
+ end
26
+ end
27
+
20
28
  describe '#language' do
21
29
  it 'should be unknown' do
22
30
  described_class.new('comments', '').language.should eq('unknown')
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe ID3Tag::Frames::V2::FrameFabricator do
4
+ let(:id) { nil }
5
+ let(:content) { 'some content' }
6
+ let(:flags) { nil }
7
+ let(:major_version_number) { 4 }
8
+
9
+ describe "self#fabricate" do
10
+ subject { described_class.fabricate(id, content, flags, major_version_number) }
11
+ context "when frame is a genre frame TCON" do
12
+ let(:id) { "TCON" }
13
+ it "fabricates genre frame" do
14
+ ID3Tag::Frames::V2::GenreFrame.should_receive(:new).with(id, content, flags, major_version_number)
15
+ subject
16
+ end
17
+ end
18
+
19
+ context "when frame is a genre frame TCO" do
20
+ let(:id) { "TCO" }
21
+ it "fabricates genre frame" do
22
+ ID3Tag::Frames::V2::GenreFrame.should_receive(:new).with(id, content, flags, major_version_number)
23
+ subject
24
+ end
25
+ end
26
+ context "when frame is a text frame TIT2" do
27
+ let(:id) { "TIT2" }
28
+ it "fabricates text frame" do
29
+ ID3Tag::Frames::V2::TextFrame.should_receive(:new).with(id, content, flags, major_version_number)
30
+ subject
31
+ end
32
+ end
33
+ context "when frame is a comment frame COM" do
34
+ let(:id) { "COMM" }
35
+ it "fabricates comment frame" do
36
+ ID3Tag::Frames::V2::CommentsFrame.should_receive(:new).with(id, content, flags, major_version_number)
37
+ subject
38
+ end
39
+ end
40
+ context "when frame is a unique id" do
41
+ let(:id) { "UFID" }
42
+ it "fabricates unique id frame" do
43
+ ID3Tag::Frames::V2::UniqueFileIdFrame.should_receive(:new).with(id, content, flags, major_version_number)
44
+ subject
45
+ end
46
+ end
47
+ context "when frame is a unknown" do
48
+ let(:id) { "unknown" }
49
+ it "fabricates basic frame" do
50
+ ID3Tag::Frames::V2::BasicFrame.should_receive(:new).with(id, content, flags, major_version_number)
51
+ subject
52
+ end
53
+ end
54
+ end
55
+ end
56
+
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe ID3Tag::Frames::V2::GenreFrame::GenreParser do
5
+ let(:genre_string) { "17" }
6
+ subject { described_class.new(genre_string) }
7
+
8
+ describe '#genres' do
9
+ it "should raise not implemented error" do
10
+ expect { subject.genres }.to raise_error(/genres is not implemented/)
11
+ end
12
+ end
13
+ end
@@ -16,19 +16,37 @@ describe ID3Tag::Frames::V2::GenreFrame do
16
16
  describe '#content' do
17
17
  subject { frame.content }
18
18
 
19
- context "when one genre" do
20
- before do
21
- ID3Tag::Frames::V2::GenreFrame::GenreParserPre24.any_instance.stub(:genres) { ['A'] }
19
+ context "when tag major version is pre 3" do
20
+ let(:major_version_number) { 3 }
21
+
22
+ context "when one genre" do
23
+ before do
24
+ ID3Tag::Frames::V2::GenreFrame::GenreParserPre24.any_instance.stub(:genres) { ['A'] }
25
+ end
26
+
27
+ it { should eq('A') }
22
28
  end
29
+ context "when two genres" do
30
+ before do
31
+ ID3Tag::Frames::V2::GenreFrame::GenreParserPre24.any_instance.stub(:genres) { ['A', 'B'] }
32
+ end
23
33
 
24
- it { should eq('A') }
34
+ it { should eq('A, B') }
35
+ end
25
36
  end
26
- context "when two genres" do
37
+
38
+ context "when tag major version is 4" do
39
+ let(:major_version_number) { 4 }
27
40
  before do
28
- ID3Tag::Frames::V2::GenreFrame::GenreParserPre24.any_instance.stub(:genres) { ['A', 'B'] }
41
+ ID3Tag::Frames::V2::GenreFrame::GenreParser24.any_instance.stub(:genres) { ['B'] }
42
+ end
43
+ it { should eq('B') }
44
+ end
45
+ context "when tag major version is 232" do
46
+ let(:major_version_number) { 232 }
47
+ it "should raise MissingGenreParser error" do
48
+ expect { subject }.to raise_error(ID3Tag::Frames::V2::GenreFrame::MissingGenreParser)
29
49
  end
30
-
31
- it { should eq('A, B') }
32
50
  end
33
51
  end
34
52
 
@@ -7,10 +7,6 @@ describe ID3Tag::ID3V1FrameParser do
7
7
  describe "#frames" do
8
8
  subject { described_class.new(frame_bytes_v1_0).frames }
9
9
 
10
- it "should return array or frames" do
11
- subject.should be_kind_of(Set)
12
- end
13
-
14
10
  describe "common frames between v1.0 and v.1.1" do
15
11
 
16
12
  it "should contain title frame" do
@@ -1,18 +1,23 @@
1
1
  require "spec_helper"
2
2
  describe ID3Tag::ID3V2FrameParser do
3
- let(:mp3_with_v2_3_tag) { mp3_fixture('id3v2.mp3') }
4
- let(:frame_bytes) { File.read(mp3_with_v2_3_tag, 246, 10) }
5
- let(:tag_major_version) { 3 }
6
- describe "#frames" do
7
- subject { described_class.new(frame_bytes, tag_major_version).frames }
8
- it "should have frame TIT2" do
9
- subject.select { |x| x.id == :TIT2 }.count.should == 1
10
- end
3
+ subject { described_class.new(frame_bytes, tag_major_version) }
11
4
 
12
- describe "text frames" do
13
- it "should parse text frames and initialize as TextFrame objects" do
14
- subject.select { |x| x.id == :TIT2 }.first.should be_kind_of(ID3Tag::Frames::V2::TextFrame)
15
- end
5
+ context "parsing v2.2.x tag" do
6
+ let(:tag_major_version) { 2 }
7
+ let(:frame_bytes) { "TP2\u0000\u0000\u0004\u0000ABC" + "TP1\u0000\u0000\u0004\u0000DEF" }
8
+ it "should fabricate frames" do
9
+ ID3Tag::Frames::V2::FrameFabricator.should_receive(:fabricate).with('TP2', "\u0000ABC", nil, 2).once
10
+ ID3Tag::Frames::V2::FrameFabricator.should_receive(:fabricate).with('TP1', "\u0000DEF", nil, 2).once
11
+ subject.frames
12
+ end
13
+ end
14
+ context "parsing v2.4.x tag" do
15
+ let(:tag_major_version) { 4 }
16
+ let(:frame_bytes) { "TIT2\u0000\u0000\u0000\u0004\u0000\u0000\u0000ABC" + "TIT1\u0000\u0000\u0000\u0004\u0000\u0000\u0000DEF" }
17
+ it "should fabricate frames" do
18
+ ID3Tag::Frames::V2::FrameFabricator.should_receive(:fabricate).with('TIT2', "\u0000ABC", "\u0000\u0000", 4).once
19
+ ID3Tag::Frames::V2::FrameFabricator.should_receive(:fabricate).with('TIT1', "\u0000DEF", "\u0000\u0000", 4).once
20
+ subject.frames
16
21
  end
17
22
  end
18
23
  end
@@ -57,4 +57,9 @@ describe ID3Tag::ID3v2TagHeader do
57
57
  let(:header_data) { "ID3abc\x00\x00\x01\x7F" }
58
58
  its(:tag_size) { should == 255 }
59
59
  end
60
+
61
+ describe "#inspect" do
62
+ let(:header_data) { "ID3\u0003\u0000\u0000\u0000\u0000\u0000\u0000" }
63
+ its(:inspect) { should eq "<ID3Tag::ID3v2TagHeader version:2.3.0 size:0 unsync:false ext.header:false experimental:false footer:false>" }
64
+ end
60
65
  end
@@ -18,4 +18,32 @@ describe ID3Tag::StringUtil do
18
18
  it { should be_false }
19
19
  end
20
20
  end
21
+
22
+ describe "do_unsynchronization" do
23
+ let(:input) { }
24
+ subject { described_class.do_unsynchronization(input) }
25
+
26
+ context "when a false synchronization is present" do
27
+ let(:input) { "\xFF\xEE" }
28
+ it { should eq("\xFF\x00\xEE") }
29
+ end
30
+ context "when a false synchronization is not present" do
31
+ let(:input) { "\xEE\xEE" }
32
+ it { should eq("\xEE\xEE") }
33
+ end
34
+ end
35
+
36
+ describe "undo_unsynchronization" do
37
+ let(:input) { }
38
+ subject { described_class.undo_unsynchronization(input) }
39
+
40
+ context "when unsynchronization in present" do
41
+ let(:input) { "\xFF\x00\xEE\xEE" }
42
+ it { should eq("\xFF\xEE\xEE") }
43
+ end
44
+ context "when unsynchronization in not present" do
45
+ let(:input) { "\xEE\xEE" }
46
+ it { should eq("\xEE\xEE") }
47
+ end
48
+ end
21
49
  end
@@ -1,84 +1,268 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe ID3Tag::Tag do
4
- let(:mp3_with_v1_tag) { mp3_fixture('id3v1_without_track_nr.mp3') }
5
- let(:mp3_with_v1_1_tag) { mp3_fixture('id3v1_with_track_nr.mp3') }
6
- context "when reading file with v1.x tag" do
7
- subject { described_class.new(mp3_with_v1_tag)}
8
- describe "#frames" do
9
- it "reads frames from source" do
10
- subject.frames.size.should > 0
4
+ describe "class method #read" do
5
+
6
+ end
7
+
8
+ context "when file has v2.4.x tag and v.1.x tag" do
9
+ subject { described_class.read(nil) }
10
+ before :each do
11
+ ID3Tag::AudioFile.any_instance.stub(:v2_tag_present?) { true }
12
+ ID3Tag::AudioFile.any_instance.stub(:v2_tag_major_version_number) { 4 }
13
+ end
14
+
15
+ describe "#artist" do
16
+ it "reads TPE1 or v1 artist" do
17
+ subject.should_receive(:get_frame_content).with(:TPE1, :artist).and_return('A')
18
+ subject.artist.should eq('A')
11
19
  end
12
20
  end
13
21
 
14
- describe "Class methods" do
15
- describe "#read" do
16
- it "should initialize new instance of tag object" do
17
- described_class.read(mp3_with_v1_tag).should be_instance_of(described_class)
18
- end
22
+ describe "#title" do
23
+ it "reads TIT2 or v1 title" do
24
+ subject.should_receive(:get_frame_content).with(:TIT2, :title).and_return('A')
25
+ subject.title.should eq('A')
26
+ end
27
+ end
28
+
29
+ describe "#album" do
30
+ it "reads TALB or v1 album" do
31
+ subject.should_receive(:get_frame_content).with(:TALB, :album).and_return('A')
32
+ subject.album.should eq('A')
33
+ end
34
+ end
35
+
36
+ describe "#year" do
37
+ it "reads TDRC or v1 year" do
38
+ subject.should_receive(:get_frame_content).with(:TDRC, :year).and_return('A')
39
+ subject.year.should eq('A')
19
40
  end
20
41
  end
21
42
 
22
- describe "#get_frame" do
23
- it "returns string with frames content" do
24
- subject.get_frame(:title).should be_kind_of(ID3Tag::Frames::V1::TextFrame)
25
- subject.get_frame(:title).id.should == :title
43
+ describe "#track_nr" do
44
+ it "reads TRCK or v1 track_nr" do
45
+ subject.should_receive(:get_frame_content).with(:TRCK, :track_nr).and_return('A')
46
+ subject.track_nr.should eq('A')
26
47
  end
48
+ end
27
49
 
28
- it "raises MultipleFrameError when tag has multiple tags with the same id" do
29
- subject.stub(:frames) { [ID3Tag::Frames::V1::TextFrame.new(:TIT2, "some title"), ID3Tag::Frames::V1::TextFrame.new(:TIT2, "some other title")] }
30
- expect { subject.get_frame(:TIT2) }.to raise_error(ID3Tag::Tag::MultipleFrameError)
50
+ describe "#genre" do
51
+ it "reads TCON or v1 genre" do
52
+ subject.should_receive(:get_frame_content).with(:TCON, :genre).and_return('A')
53
+ subject.genre.should eq('A')
31
54
  end
32
55
  end
56
+ end
33
57
 
34
- describe "#get_frames" do
35
- it "returns string with frames content" do
36
- subject.get_frames(:title).should be_kind_of(Array)
37
- subject.get_frames(:title).count.should == 1
58
+ describe "#get_frame" do
59
+ subject { described_class.read(nil) }
60
+ context "when more that one frame by that ID exists" do
61
+ before :each do
62
+ subject.stub(:get_frames) { [:frame, :frame] }
63
+ end
64
+ it "should raise MultipleFrameError" do
65
+ expect { subject.get_frame(:some_unique_frame) }.to raise_error(ID3Tag::Tag::MultipleFrameError)
38
66
  end
39
67
  end
40
68
 
41
- describe "#frames" do
42
- context "when file has v2.x tag version" do
69
+ context "when only one frame by that ID exists" do
70
+ before :each do
71
+ subject.stub(:get_frames) { [:frame] }
72
+ end
73
+ it "should return the frame" do
74
+ subject.get_frame(:some_unique_frame).should eq(:frame)
75
+ end
76
+ end
77
+ end
78
+
79
+ describe "#get_frames" do
80
+ subject { described_class.read(nil) }
81
+ let(:a1) { ID3Tag::Frames::V1::TextFrame.new(:A, 'a1') }
82
+ let(:a2) { ID3Tag::Frames::V1::TextFrame.new(:A, 'a2') }
83
+ let(:b) { ID3Tag::Frames::V1::TextFrame.new(:B, 'b') }
84
+ before :each do
85
+ subject.stub(:frames) { [a1, a2, b] }
86
+ end
87
+ it "returns frames with specific IDs" do
88
+ subject.get_frames(:A).should eq([a1, a2])
89
+ end
90
+ end
91
+
92
+ describe "#frames" do
93
+ subject { described_class.read(nil) }
94
+ before do
95
+ subject.stub(:v1_frames) { [:v1_frame1, :v1_frame2] }
96
+ subject.stub(:v2_frames) { [:v2_frame1, :v2_frame2] }
97
+ end
98
+ it "returns v2 frames and v1 frames" do
99
+ subject.frames.should eq([:v2_frame1, :v2_frame2, :v1_frame1, :v1_frame2])
100
+ end
101
+ end
102
+
103
+ describe "#frame_ids" do
104
+ subject { described_class.read(nil) }
105
+ let(:frame_1) { ID3Tag::Frames::V1::TextFrame.new(:AA, 'a1') }
106
+ let(:frame_2) { ID3Tag::Frames::V1::TextFrame.new(:BB, 'a2') }
107
+ before do
108
+ subject.stub(:frames) { [frame_1, frame_2] }
109
+ end
110
+ it "returns frames ids" do
111
+ subject.frame_ids.should eq([:AA, :BB])
112
+ end
113
+ end
114
+
115
+ describe "#v1_frames" do
116
+ context "when tag reading initialized with v1 tag only" do
117
+ subject { described_class.read(nil, :v1) }
118
+ context "when file has v1 tag" do
43
119
  before do
44
120
  ID3Tag::AudioFile.any_instance.stub(:v1_tag_present?) { true }
45
- ID3Tag::AudioFile.any_instance.stub(:v2_tag_present?) { true }
46
- ID3Tag::ID3V2FrameParser.any_instance.stub(:frames) { [1,2,3] }
121
+ ID3Tag::AudioFile.any_instance.stub(:v1_tag_body) { '' }
122
+ ID3Tag::ID3V1FrameParser.any_instance.stub(:frames) { [:v1_frame] }
123
+ end
124
+ it "reads v1 tags" do
125
+ subject.v1_frames.should eq([:v1_frame])
126
+ end
127
+ end
128
+
129
+ context "when file does not have v1 tag" do
130
+ before do
131
+ ID3Tag::AudioFile.any_instance.stub(:v1_tag_present?) { false }
132
+ ID3Tag::AudioFile.any_instance.stub(:v1_tag_body) { '' }
133
+ end
134
+ it "returns empty array" do
135
+ subject.v1_frames.should eq([])
136
+ end
137
+ end
138
+ end
139
+
140
+ context "when tag reading initialized with v2 tag only" do
141
+ subject { described_class.read(nil, :v2) }
142
+ context "when file has v1 tag" do
143
+ before do
144
+ ID3Tag::AudioFile.any_instance.stub(:v1_tag_present?) { true }
145
+ ID3Tag::AudioFile.any_instance.stub(:v1_tag_body) { '' }
47
146
  end
48
- it "should return frames from ID3V2FrameParser class" do
49
- subject.frames.should == [1,2,3]
147
+ it "reads v1 tags" do
148
+ subject.v1_frames.should eq([])
50
149
  end
51
150
  end
52
- context "when file has v2.x tag version" do
151
+ end
152
+
153
+ context "when tag reading initialized with all versions flag" do
154
+ subject { described_class.read(nil, :all) }
155
+ context "when file has v1 tag" do
53
156
  before do
54
157
  ID3Tag::AudioFile.any_instance.stub(:v1_tag_present?) { true }
158
+ ID3Tag::AudioFile.any_instance.stub(:v1_tag_body) { '' }
159
+ ID3Tag::ID3V1FrameParser.any_instance.stub(:frames) { [:v1_frame] }
160
+ end
161
+ it "reads v1 tags" do
162
+ subject.v1_frames.should eq([:v1_frame])
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ describe "#v2_frames" do
169
+ before do
170
+ ID3Tag::AudioFile.any_instance.stub(:v2_tag_major_version_number) { 3 }
171
+ end
172
+ context "when tag reading initialized with v2 tag only" do
173
+ subject { described_class.read(nil, :v2) }
174
+ context "when file has v2 tag" do
175
+ before do
176
+ ID3Tag::AudioFile.any_instance.stub(:v2_tag_present?) { true }
177
+ ID3Tag::AudioFile.any_instance.stub(:v2_tag_body) { '' }
178
+ ID3Tag::ID3V2FrameParser.any_instance.stub(:frames) { [:v2_frame] }
179
+ end
180
+ it "reads v2 tags" do
181
+ subject.v2_frames.should eq([:v2_frame])
182
+ end
183
+ end
184
+
185
+ context "when file does not have v2 tag" do
186
+ before do
55
187
  ID3Tag::AudioFile.any_instance.stub(:v2_tag_present?) { false }
56
- ID3Tag::ID3V1FrameParser.any_instance.stub(:frames) { [1,2,3] }
188
+ ID3Tag::AudioFile.any_instance.stub(:v2_tag_body) { '' }
57
189
  end
58
- it "should return frames from ID3V1FrameParser class" do
59
- subject.frames.should == [1,2,3]
190
+ it "returns empty array" do
191
+ subject.v2_frames.should eq([])
60
192
  end
61
193
  end
62
194
  end
63
195
 
64
- describe "#frame_ids" do
65
- before do
66
- subject.stub(:frames) { [ID3Tag::Frames::V1::TextFrame.new(:TIT2, "some title")] }
196
+ context "when tag reading initialized with v1 tag only" do
197
+ subject { described_class.read(nil, :v1) }
198
+ context "when file has v2 tag" do
199
+ before do
200
+ ID3Tag::AudioFile.any_instance.stub(:v2_tag_present?) { true }
201
+ ID3Tag::AudioFile.any_instance.stub(:v2_tag_body) { '' }
202
+ end
203
+ it "returns empty array" do
204
+ subject.v2_frames.should eq([])
205
+ end
67
206
  end
68
- it "returns its of all frames" do
69
- subject.frame_ids.should == [:TIT2]
207
+ end
208
+
209
+ context "when tag reading initialized with all versions flag" do
210
+ subject { described_class.read(nil, :all) }
211
+ context "when file has v2 tag" do
212
+ before do
213
+ ID3Tag::AudioFile.any_instance.stub(:v2_tag_present?) { true }
214
+ ID3Tag::AudioFile.any_instance.stub(:v2_tag_body) { '' }
215
+ ID3Tag::ID3V2FrameParser.any_instance.stub(:frames) { [:v2_frame] }
216
+ end
217
+ it "reads v2 tags" do
218
+ subject.v2_frames.should eq([:v2_frame])
219
+ end
70
220
  end
71
221
  end
72
222
  end
73
223
 
74
- describe "Easy access methods to text frames" do
75
- subject { described_class.new(mp3_with_v1_1_tag)}
76
- its(:title) { should == "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaA" }
77
- its(:artist) { should == "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbB" }
78
- its(:album) { should == "cccccccccccccccccccccccccccccC" }
79
- its(:comments) { should == "dddddddddddddddddddddddddddD" }
80
- its(:genre) { should == "Blues" }
81
- its(:year) { should == "2003" }
82
- its(:track_nr) { should == "1" }
224
+ describe "#get_frame_content" do
225
+ subject { described_class.read(nil, :all) }
226
+ let(:frame_1) { ID3Tag::Frames::V1::TextFrame.new(:some_id, 'some content') }
227
+ let(:frame_2) { ID3Tag::Frames::V1::TextFrame.new(:some_other_id, 'some other content') }
228
+ context "with one ID as argument" do
229
+ context "when frame with ID exists" do
230
+ before :each do
231
+ subject.stub(:get_frame).with(:some_id) { frame_1 }
232
+ end
233
+ it "should return frame" do
234
+ subject.get_frame_content(:some_id).should == 'some content'
235
+ end
236
+ end
237
+ context "when frame with ID does not exists" do
238
+ before :each do
239
+ subject.stub(:get_frame).with(:some_id) { nil }
240
+ end
241
+ it "should return none" do
242
+ subject.get_frame_content(:some_id).should be_nil
243
+ end
244
+ end
245
+ end
246
+
247
+ context "with multiple ID as armguments" do
248
+ context "when first one does exist" do
249
+ before :each do
250
+ subject.stub(:get_frame).with(:some_id) { frame_1 }
251
+ subject.stub(:get_frame).with(:some_other_id) { frame_2 }
252
+ end
253
+ it "should return first existing frame's content" do
254
+ subject.get_frame_content(:some_id, :some_other_id).should eq 'some content'
255
+ end
256
+ end
257
+ context "when second exists" do
258
+ before :each do
259
+ subject.stub(:get_frame).with(:some_id) { nil }
260
+ subject.stub(:get_frame).with(:some_other_id) { frame_2 }
261
+ end
262
+ it "should return first existing frame's content" do
263
+ subject.get_frame_content(:some_id, :some_other_id).should eq 'some other content'
264
+ end
265
+ end
266
+ end
83
267
  end
84
268
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: id3tag
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Krists Ozols
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-05-01 00:00:00.000000000 Z
11
+ date: 2013-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: debugger
@@ -151,6 +151,9 @@ files:
151
151
  - lib/id3tag/string_util.rb
152
152
  - lib/id3tag/synchsafe_integer.rb
153
153
  - lib/id3tag/tag.rb
154
+ - lib/id3tag/unsynchronization.rb
155
+ - spec/features/can_read_non_audio_files_spec.rb
156
+ - spec/features/can_read_tag_v1_spec.rb
154
157
  - spec/fixtures/id3v1_and_v2.mp3
155
158
  - spec/fixtures/id3v1_with_track_nr.mp3
156
159
  - spec/fixtures/id3v1_without_track_nr.mp3
@@ -163,8 +166,10 @@ files:
163
166
  - spec/lib/id3tag/frames/v1/track_nr_frame_spec.rb
164
167
  - spec/lib/id3tag/frames/v2/basic_frame_spec.rb
165
168
  - spec/lib/id3tag/frames/v2/comments_frame_spec.rb
169
+ - spec/lib/id3tag/frames/v2/frame_fabricator_spec.rb
166
170
  - spec/lib/id3tag/frames/v2/genre_frame/genre_parser_24_spec.rb
167
171
  - spec/lib/id3tag/frames/v2/genre_frame/genre_parser_pre_24_spec.rb
172
+ - spec/lib/id3tag/frames/v2/genre_frame/genre_parser_spec.rb
168
173
  - spec/lib/id3tag/frames/v2/genre_frame_spec.rb
169
174
  - spec/lib/id3tag/frames/v2/text_frame_spec.rb
170
175
  - spec/lib/id3tag/frames/v2/unique_file_id_frame_spec.rb