easytag 0.4.3 → 1.0.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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/Gemfile +7 -2
  4. data/README.md +33 -27
  5. data/Rakefile +7 -7
  6. data/easytag.gemspec +2 -4
  7. data/lib/easytag.rb +12 -2
  8. data/lib/easytag/attributes/base.rb +32 -128
  9. data/lib/easytag/attributes/flac.rb +25 -0
  10. data/lib/easytag/attributes/mp3.rb +91 -554
  11. data/lib/easytag/attributes/mp4.rb +22 -492
  12. data/lib/easytag/attributes/ogg.rb +27 -0
  13. data/lib/easytag/attributes/vorbis.rb +18 -0
  14. data/lib/easytag/taggers/base.rb +19 -0
  15. data/lib/easytag/taggers/factory.rb +59 -0
  16. data/lib/easytag/taggers/flac.rb +23 -0
  17. data/lib/easytag/taggers/mp3.rb +77 -0
  18. data/lib/easytag/taggers/mp4.rb +73 -0
  19. data/lib/easytag/taggers/ogg.rb +25 -0
  20. data/lib/easytag/taggers/vorbis.rb +66 -0
  21. data/lib/easytag/util.rb +9 -31
  22. data/lib/easytag/version.rb +3 -4
  23. data/scripts/build_attributes_table.rb +34 -0
  24. data/{test → spec/data}/consistency.01.m4a +0 -0
  25. data/{test → spec/data}/consistency.01.mp3 +0 -0
  26. data/spec/data/consistency.flac +0 -0
  27. data/{test/consistency.02.m4a → spec/data/consistency.m4a} +0 -0
  28. data/{test/consistency.02.mp3 → spec/data/consistency.mp3} +0 -0
  29. data/spec/data/consistency.multiple_images.flac +0 -0
  30. data/{test/no_tags.m4a → spec/data/consistency.multiple_images.m4a} +0 -0
  31. data/spec/data/consistency.multiple_images.mp3 +0 -0
  32. data/spec/data/consistency.multiple_images.ogg +0 -0
  33. data/spec/data/consistency.ogg +0 -0
  34. data/{test → spec/data}/musicbrainz.m4a +0 -0
  35. data/spec/data/no_tags.flac +0 -0
  36. data/spec/data/no_tags.m4a +0 -0
  37. data/{test → spec/data}/no_tags.mp3 +0 -0
  38. data/spec/data/no_tags.ogg +0 -0
  39. data/{test → spec/data}/only_id3v1.mp3 +0 -0
  40. data/{test → spec/data}/only_id3v2.mp3 +0 -0
  41. data/spec/flac_tagger_spec.rb +24 -0
  42. data/spec/mp3_tagger_spec.rb +64 -0
  43. data/spec/mp4_tagger_spec.rb +22 -0
  44. data/spec/ogg_tagger_spec.rb +21 -0
  45. data/spec/shared_examples.rb +115 -0
  46. data/spec/spec_helper.rb +34 -0
  47. data/spec/tagger_factory_spec.rb +27 -0
  48. data/spec/util_spec.rb +47 -0
  49. metadata +47 -48
  50. data/lib/easytag/attributes.rb +0 -0
  51. data/lib/easytag/base.rb +0 -28
  52. data/lib/easytag/file.rb +0 -44
  53. data/lib/easytag/interfaces.rb +0 -18
  54. data/lib/easytag/interfaces/base.rb +0 -24
  55. data/lib/easytag/interfaces/mp3.rb +0 -48
  56. data/lib/easytag/interfaces/mp4.rb +0 -14
  57. data/test/test_consistency.rb +0 -233
  58. data/test/test_mp3.rb +0 -98
  59. data/test/test_mp4.rb +0 -49
  60. data/test/test_util.rb +0 -72
@@ -1,10 +1,9 @@
1
1
  module EasyTag
2
2
  class Version
3
- MAJOR = 0
4
- MINOR = 4
5
- TINY = 3
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ TINY = 0
6
6
  end
7
7
 
8
8
  VERSION = [Version::MAJOR, Version::MINOR, Version::TINY].join('.')
9
-
10
9
  end
@@ -0,0 +1,34 @@
1
+ require 'date'
2
+ require 'easytag'
3
+
4
+ DOT = '●'
5
+ TAGGERS = {
6
+ mp3: EasyTag::MP3Tagger,
7
+ mp4: EasyTag::MP4Tagger,
8
+ flac: EasyTag::FLACTagger,
9
+ ogg: EasyTag::OggTagger
10
+ }
11
+
12
+ NOTES = {
13
+ }
14
+
15
+ methods = Set.new
16
+
17
+ IGNORED_METHODS = [:method_missing, :taglib, :close]
18
+
19
+ TAGGERS.each_value do |klass|
20
+ methods.merge(klass.instance_methods - Object.instance_methods - IGNORED_METHODS)
21
+ end
22
+
23
+ puts "#### Standard Attributes (Last Updated: #{Date.today}) ####"
24
+ puts '| Method | MP3 | MP4 | FLAC | Ogg | Notes|'
25
+ puts '|-------|:---:|:---:|:----:|:---:|------|'
26
+
27
+ methods.to_a.sort.each do |method|
28
+ line = "| `##{method}` "
29
+ [:mp3, :mp4, :flac, :ogg].each do |key|
30
+ line << (TAGGERS[key].instance_methods.include?(method) ? "| #{DOT} " : '| ')
31
+ end
32
+ line << '|'
33
+ puts line
34
+ end
Binary file
File without changes
Binary file
Binary file
File without changes
Binary file
File without changes
File without changes
@@ -0,0 +1,24 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe EasyTag::FLACTagger do
4
+ before(:all) do
5
+ @no_tags = described_class.new(data_path('no_tags.flac'))
6
+ end
7
+
8
+ after(:all) do
9
+ easytag_close @no_tags
10
+ end
11
+
12
+ include_context 'no tags', described_class.new(data_path('no_tags.flac'))
13
+ include_context 'consistency', described_class.new(data_path('consistency.flac'))
14
+ include_context 'consistency with multiple images', described_class.new(data_path('consistency.multiple_images.flac'))
15
+
16
+ it 'should return the audio properties of the track correctly' do
17
+ @no_tags.length.should be(4)
18
+ @no_tags.bitrate.should be(250)
19
+ @no_tags.channels.should be(2)
20
+ @no_tags.sample_rate.should be(44100)
21
+ @no_tags.sample_width.should be(16)
22
+ @no_tags.signature.bytes.should eql([97, 202, 139, 118, 67, 37, 2, 125, 159, 68, 118, 5, 249, 172, 220, 101])
23
+ end
24
+ end
@@ -0,0 +1,64 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe EasyTag::MP3Tagger do
4
+ before(:all) do
5
+ @id3v1_only = described_class.new(data_path('only_id3v1.mp3'))
6
+ @id3v2_only = described_class.new(data_path('only_id3v2.mp3'))
7
+ @no_tags = described_class.new(data_path('no_tags.mp3'))
8
+ end
9
+
10
+ after(:all) do
11
+ easytag_close @id3v1_only, @id3v2_only, @no_tags
12
+ end
13
+
14
+ include_context 'no tags', described_class.new(data_path('no_tags.mp3'))
15
+ include_context 'consistency', described_class.new(data_path('consistency.mp3'))
16
+ include_context 'consistency with multiple images', described_class.new(data_path('consistency.multiple_images.mp3'))
17
+
18
+ context 'when containing only id3v1 tags' do
19
+ it 'should read all id3v1 tags correctly' do
20
+ @id3v1_only.title.should eql('Track Title')
21
+ @id3v1_only.artist.should eql('Track Artist')
22
+ @id3v1_only.album.should eql('Album Name')
23
+ @id3v1_only.comments.should eql(['this is a comment'])
24
+ @id3v1_only.genre.should eql('Swing')
25
+ @id3v1_only.year.should be(1988)
26
+ @id3v1_only.date.year.should be(1988)
27
+ @id3v1_only.track_number.should be(3)
28
+ @id3v1_only.total_tracks.should be(0)
29
+ @id3v1_only.disc_number.should be(0)
30
+ @id3v1_only.total_discs.should be(0)
31
+ end
32
+
33
+ it 'should return proper responses for attributes that require id3v2' do
34
+ @id3v1_only.album_art.empty?.should be(true)
35
+ end
36
+ end
37
+
38
+ context 'when containing only id3v2 tags' do
39
+ it 'should read all id3v2 tags correctly' do
40
+ @id3v2_only.title.should eql('Track Title')
41
+ @id3v2_only.artist.should eql('Track Artist')
42
+ @id3v2_only.album.should eql('Album Name')
43
+ @id3v2_only.comments.should eql(['this is a comment'])
44
+ @id3v2_only.genre.should eql('Swing')
45
+ @id3v2_only.year.should be(1988)
46
+ @id3v2_only.date.year.should be(1988)
47
+ @id3v2_only.track_number.should be(3)
48
+ @id3v2_only.total_tracks.should be(0)
49
+ @id3v2_only.disc_number.should be(0)
50
+ @id3v2_only.total_discs.should be(0)
51
+ end
52
+ end
53
+
54
+ it 'should return the audio properties of the track correctly' do
55
+ @no_tags.length.should be(4)
56
+ @no_tags.bitrate.should be(74)
57
+ @no_tags.sample_rate.should be(44100)
58
+ @no_tags.channels.should be(2)
59
+ @no_tags.layer.should be(3)
60
+ @no_tags.original?.should be(true)
61
+ @no_tags.copyrighted?.should be(false)
62
+ @no_tags.protection_enabled?.should be(false)
63
+ end
64
+ end
@@ -0,0 +1,22 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe EasyTag::MP4Tagger do
4
+ before(:all) do
5
+ @no_tags = described_class.new(data_path('no_tags.m4a'))
6
+ end
7
+
8
+ after(:all) do
9
+ easytag_close @no_tags
10
+ end
11
+
12
+ include_context 'no tags', described_class.new(data_path('no_tags.m4a'))
13
+ include_context 'consistency', described_class.new(data_path('consistency.m4a'))
14
+
15
+ it 'should return the audio properties of the track correctly' do
16
+ @no_tags.length.should be(4)
17
+ @no_tags.bitrate.should be(176)
18
+ @no_tags.channels.should be(2)
19
+ @no_tags.sample_rate.should be(44100)
20
+ @no_tags.bits_per_sample.should be(16)
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe EasyTag::OggTagger do
4
+ before(:all) do
5
+ @no_tags = described_class.new(data_path('no_tags.ogg'))
6
+ end
7
+ include_context 'no tags', described_class.new(data_path('no_tags.ogg'))
8
+ include_context 'consistency', described_class.new(data_path('consistency.ogg'))
9
+ include_context 'consistency with multiple images', described_class.new(data_path('consistency.multiple_images.ogg'))
10
+
11
+ it 'should return the audio properties of the track correctly' do
12
+ @no_tags.length.should be(4)
13
+ @no_tags.bitrate.should be(64)
14
+ @no_tags.channels.should be(2)
15
+ @no_tags.sample_rate.should be(44100)
16
+ @no_tags.bitrate_maximum.should be(0)
17
+ @no_tags.bitrate_minimum.should be(0)
18
+ @no_tags.bitrate_nominal.should be(64000)
19
+ @no_tags.vorbis_version.should be(0)
20
+ end
21
+ end
@@ -0,0 +1,115 @@
1
+ shared_context 'no tags' do |tagger|
2
+ after(:all) do
3
+ easytag_close tagger
4
+ end
5
+
6
+ it 'should return nil for non-list/non-int attributes' do
7
+ [:title, :title_sort_order, :artist, :artist_sort_order,
8
+ :album_artist, :album_artist_sort_order, :album, :album_sort_order,
9
+ :genre, :lyrics, :date, :encoded_by, :group, :mood, :copyright, :subtitle, :disc_subtitle, :media,
10
+ :label, :composer, :remixer, :lyricist, :asin, :barcode, :catalog_number,
11
+ :musicbrainz_recording_id, :musicbrainz_track_id, :musicbrainz_album_artist_id,
12
+ :musicbrainz_album_id, :musicbrainz_release_group_id,
13
+ :musicbrainz_release_status, :musicbrainz_release_country].each do |attr|
14
+ actual = tagger.send(attr)
15
+ actual.should be(nil), "##{attr} should return nil but returned #{actual.class}:#{actual}"
16
+ end
17
+ end
18
+
19
+ it 'should return zero for int attributes' do
20
+ [:year, :bpm, :track_number, :total_tracks, :disc_number, :total_discs].each do |attr|
21
+ actual = tagger.send(attr)
22
+ actual.should be(0), "##{attr} should return 0 but returned #{actual.class}:#{actual}"
23
+ end
24
+ end
25
+
26
+ it 'should return an empty array for list attributes' do
27
+ [:comments, :album_art, :musicbrainz_artist_id, :musicbrainz_release_type].each do |attr|
28
+ actual = tagger.send(attr)
29
+ actual.should be_a(Array), "##{attr} should return an Array but returned #{actual.class}"
30
+ actual.empty?.should be(true), "##{attr} should be empty but isn't"
31
+ end
32
+ end
33
+ end
34
+
35
+ shared_context 'consistency' do |tagger|
36
+ after(:all) do
37
+ tagger.close
38
+ end
39
+
40
+ it 'should return all standard attributes consistently' do
41
+ tagger.title.should eql('She Lives in My Lap')
42
+ tagger.title_sort_order.should be(nil)
43
+ tagger.artist.should eql('OutKast feat. Rosario Dawson')
44
+ tagger.artist_sort_order.should eql('OutKast feat. Dawson, Rosario')
45
+ tagger.album_artist.should eql('OutKast')
46
+ tagger.album_artist_sort_order.should eql('OutKast')
47
+ tagger.album.should eql('Speakerboxxx / The Love Below')
48
+ tagger.album_sort_order.should be(nil)
49
+ tagger.comments.count.should be(0)
50
+ tagger.genre.should be(nil)
51
+ tagger.year.should be(2003)
52
+ tagger.date.year.should be(2003)
53
+ tagger.date.month.should be(9)
54
+ tagger.date.day.should be(23)
55
+ tagger.track_number.should be(8)
56
+ tagger.total_tracks.should be(21)
57
+ tagger.disc_number.should be(2)
58
+ tagger.total_discs.should be(2)
59
+ tagger.disc_subtitle.should eql('The Love Below')
60
+ tagger.media.should eql('CD')
61
+ tagger.label.should eql('Arista')
62
+ tagger.compilation?.should be(true)
63
+ tagger.bpm.should be(0)
64
+ tagger.asin.should eql('B0000AGWFX')
65
+ tagger.isrc.should eql('USAR10300997')
66
+ tagger.script.should eql('Latn')
67
+ tagger.barcode.should eql('828765013321')
68
+ tagger.catalog_number.should eql('82876 50133 2')
69
+ end
70
+
71
+ it 'should return consistent album art' do
72
+ tagger.album_art.count.should be(1)
73
+
74
+ art = tagger.album_art[0]
75
+ Digest::SHA1.hexdigest(art.data).should eql('de2e3998753d4a11241205788b5c6fe4bf9dc722')
76
+ art.mime_type.should eql('image/jpeg')
77
+ art.width.should eq(500)
78
+ art.height.should eq(500)
79
+ end
80
+
81
+ it 'should return musicbrainz data consistently' do
82
+ tagger.musicbrainz_recording_id.should eql('e1402418-eaea-4108-81e3-a12c22178325')
83
+ tagger.musicbrainz_track_id.should be(nil)
84
+ tagger.musicbrainz_release_type.should eql(['album'])
85
+ tagger.musicbrainz_album_artist_id.should eql('73fdb566-a9b1-494c-9f32-51768ec9fd27')
86
+ tagger.musicbrainz_artist_id.count.should be(2)
87
+ tagger.musicbrainz_artist_id.should include('73fdb566-a9b1-494c-9f32-51768ec9fd27')
88
+ tagger.musicbrainz_artist_id.should include('9facf8dc-df23-4561-85c5-ece75d692f21')
89
+ tagger.musicbrainz_album_id.should eql('468cd19e-d55c-46a2-a5a6-66292d2f0a90')
90
+ tagger.musicbrainz_release_group_id.should eql('fa64febd-61e0-346e-aaa2-04564ed4f0a3')
91
+ tagger.musicbrainz_release_country.should eql('US')
92
+ tagger.musicbrainz_release_status.should eql('official')
93
+ end
94
+ end
95
+
96
+ shared_context 'consistency with multiple images' do |tagger|
97
+ after(:all) do
98
+ easytag_close tagger
99
+ end
100
+ it 'should return consistent album art' do
101
+ tagger.album_art.count.should be(2)
102
+
103
+ art = tagger.album_art[0]
104
+ Digest::SHA1.hexdigest(art.data).should eql('17bd6b61a49c57fb4b2658e66db159c5bdef1106')
105
+ art.mime_type.should eql('image/jpeg')
106
+ art.width.should eq(15)
107
+ art.height.should eq(15)
108
+
109
+ art = tagger.album_art[1]
110
+ Digest::SHA1.hexdigest(art.data).should eql('2a48f42183fb60891532f0ada60ffb7ea63d706a')
111
+ art.mime_type.should eql('image/png')
112
+ art.width.should eq(20)
113
+ art.height.should eq(20)
114
+ end
115
+ end
@@ -0,0 +1,34 @@
1
+ require 'simplecov'
2
+ require 'coveralls'
3
+
4
+ DATA_DIR = File.join(File.dirname(__FILE__), 'data')
5
+
6
+ def data_path(file_name)
7
+ File.join(DATA_DIR, file_name)
8
+ end
9
+
10
+ class SimpleFormatter
11
+ def format(result)
12
+ puts "Coverage: #{result.covered_lines} / #{result.total_lines} LOC (#{result.covered_percent.round(2)}%) covered."
13
+ end
14
+ end
15
+
16
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
17
+ SimpleFormatter,
18
+ Coveralls::SimpleCov::Formatter
19
+ ]
20
+
21
+ SimpleCov.start do
22
+ add_filter '/spec/'
23
+ end
24
+
25
+ def easytag_close(*instances)
26
+ instances.each { |instance| instance.close }
27
+ end
28
+
29
+ require 'rspec'
30
+ require 'digest/sha1'
31
+
32
+ require 'easytag'
33
+ require_relative 'shared_examples'
34
+
@@ -0,0 +1,27 @@
1
+ require_relative 'spec_helper'
2
+
3
+ def tagger_for_signature_helper(file)
4
+ fp = File.open(data_path(file), 'rb')
5
+ data = fp.read(16)
6
+ fp.close
7
+ EasyTag::TaggerFactory.tagger_for_signature(data.bytes)
8
+ end
9
+
10
+ describe EasyTag::TaggerFactory do
11
+ it 'should return the correct tagger class based on filename' do
12
+ EasyTag::TaggerFactory.tagger_for_filename('file.mp3').should be(EasyTag::MP3Tagger)
13
+ EasyTag::TaggerFactory.tagger_for_filename('file.mp4').should be(EasyTag::MP4Tagger)
14
+ EasyTag::TaggerFactory.tagger_for_filename('file.m4a').should be(EasyTag::MP4Tagger)
15
+ EasyTag::TaggerFactory.tagger_for_filename('file.flac').should be(EasyTag::FLACTagger)
16
+ EasyTag::TaggerFactory.tagger_for_filename('file.ogg').should be(EasyTag::OggTagger)
17
+ end
18
+
19
+ it 'should return the correct tagger class based on the file signature' do
20
+ tagger_for_signature_helper('consistency.mp3').should be(EasyTag::MP3Tagger)
21
+ tagger_for_signature_helper('only_id3v1.mp3').should be(EasyTag::MP3Tagger)
22
+ tagger_for_signature_helper('only_id3v2.mp3').should be(EasyTag::MP3Tagger)
23
+ tagger_for_signature_helper('consistency.m4a').should be(EasyTag::MP4Tagger)
24
+ tagger_for_signature_helper('consistency.flac').should be(EasyTag::FLACTagger)
25
+ tagger_for_signature_helper('consistency.ogg').should be(EasyTag::OggTagger)
26
+ end
27
+ end
@@ -0,0 +1,47 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe EasyTag::Utilities do
4
+ it '#get_int_pair parses int pairs' do
5
+ EasyTag::Utilities.get_int_pair(nil).should eql([0, 0])
6
+ EasyTag::Utilities.get_int_pair('').should eql([0, 0])
7
+ EasyTag::Utilities.get_int_pair('3').should eql([3, 0])
8
+ EasyTag::Utilities.get_int_pair('/9').should eql([0, 9])
9
+ EasyTag::Utilities.get_int_pair('5/10').should eql([5, 10])
10
+ EasyTag::Utilities.get_int_pair([8, 21]).should eql([8, 21])
11
+ end
12
+
13
+ it '#get_datetime parses dates' do
14
+ date = EasyTag::Utilities.get_datetime('2006')
15
+ date.year.should be(2006)
16
+ date.month.should be(1)
17
+ date.day.should be(1)
18
+
19
+ date = EasyTag::Utilities.get_datetime('1955-05')
20
+ date.year.should be(1955)
21
+ date.month.should be(5)
22
+ date.day.should be(1)
23
+
24
+ date = EasyTag::Utilities.get_datetime('2012-12-07T08:00:00Z')
25
+ date.year.should be(2012)
26
+ date.month.should be(12)
27
+ date.day.should be(7)
28
+
29
+ date = EasyTag::Utilities.get_datetime('19880711')
30
+ date.year.should be(1988)
31
+ date.month.should be(11)
32
+ date.day.should be(7)
33
+
34
+ date = EasyTag::Utilities.get_datetime('2006-99-99')
35
+ date.year.should be(2006)
36
+ date.month.should be(1)
37
+ date.day.should be(1)
38
+
39
+ date = EasyTag::Utilities.get_datetime('2000-00-00')
40
+ date.year.should be(2000)
41
+ date.month.should be(1)
42
+ date.day.should be(1)
43
+
44
+ date = EasyTag::Utilities.get_datetime('')
45
+ date.should be(nil)
46
+ end
47
+ end