easytag 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d32bfe7e7828920f2801fd17d56c52362eeb9a97
4
+ data.tar.gz: 2e8739c3b7ed170e5db24ab52e75fb6494d777c6
5
+ SHA512:
6
+ metadata.gz: fc522bd6064e15d3e08bfc13d0e0a5ff659730f7a684c4141ad3551e8dbce792bec92f9cee40a8772c64a991352d6d8be600d67b9c02fb696779c47e1485bb52
7
+ data.tar.gz: 41a958d976e06dd70321632b9b080d350ed82b2069124f26092b79f732f24fb90eadc0556368b277d977116a7196b78ca6d610468130f86a2eeac1a85a016b78
data/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ ##### v0.2.0 (Not yet released) #####
2
+ * added:
3
+ - `#track_num`
4
+ - `#disc_num`
5
+ - `#user_info`
6
+ - `#disc_subtitle`
7
+ - `#media`
8
+ - `#label`
9
+ - `#title_sort_order`
10
+ - `#artist_sort_order`
11
+ - `#album_artist_sort_order`
12
+ - `#album_sort_order`
13
+ - `#original_date`
14
+ * fixed: bug related to M4A files without artwork
15
+
16
+ ##### v0.1.0 (2013-05-23) #####
17
+ * initial release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # read dependencies from gemspec file
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Chris Lucas <chris@chrisjlucas.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ EasyTag is an abstraction layer to the [TagLib](http://taglib.github.io/) audio tagging library. It is designed to provide a simple and consistent API regardless of file format being read.
2
+
3
+ A quick reference to which attributes are currently supported can be found [here](https://github.com/cjlucas/ruby-easytag/wiki/Currently-Supported-Attributes).
4
+
5
+ [![Build Status (master)](https://travis-ci.org/cjlucas/ruby-easytag.png?branch=master "Branch: master")](https://travis-ci.org/cjlucas/ruby-easytag)
6
+ [![Build Status (develop)](https://travis-ci.org/cjlucas/ruby-easytag.png?branch=develop "Branch: develop")](https://travis-ci.org/cjlucas/ruby-easytag)
7
+
8
+ ---
9
+ ## Synopsis ##
10
+ ```ruby
11
+ require 'easytag'
12
+
13
+ mp3 = EasyTag::File.new("01 Shout Out Loud.mp3")
14
+
15
+ # easy access to attributes
16
+ mp3.title
17
+ # => "Shout Out Loud"
18
+ mp3.artist
19
+ # => "Amos Lee"
20
+ mp3.year
21
+ # => 2006
22
+ mp3.date
23
+ # => #<DateTime: 2006-10-03T00:00:00+00:00 ((2454012j,0s,0n),+0s,2299161j)>
24
+ mp3.album_art
25
+ # => [#<EasyTag::Image:0x007f7fa542f528>]
26
+
27
+ # get access to the taglib-powered backend
28
+ mp3.info
29
+ # => #<TagLib::MPEG::File:0x007f7fa539e050 @__swigtype__="_p_TagLib__MPEG__File">
30
+ mp3.close
31
+
32
+ # A block interface is also available
33
+ EasyTag::File.open('01 Shout Out Loud.m4a') do |m4a|
34
+ puts m4a.title
35
+ puts m4a.comments
36
+ puts m4a.genre
37
+ end
38
+ ```
39
+ ## Requirements ##
40
+ - Ruby versions
41
+ - 1.9
42
+ - 2.0
43
+ - Dependencies
44
+ - [TagLib](http://taglib.github.io/) (1.8+)
45
+ - [taglib-ruby](https://github.com/robinst/taglib-ruby) (0.6.0+)
46
+ - [ruby-mp3info](https://github.com/moumar/ruby-mp3info)
47
+ - [ruby-imagespec](https://github.com/andersonbrandon/ruby-imagespec)
48
+
49
+ ## TODO ##
50
+ - API Documentation
51
+ - FLAC and OGG support
52
+ - Write support
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
2
+
3
+ require 'rake/testtask'
4
+
5
+ require 'easytag/version'
6
+
7
+ task :default => [:test]
8
+
9
+ task :test do
10
+ Rake::TestTask.new do |t|
11
+ t.libs << 'test'
12
+ t.test_files = FileList['test/test_*.rb']
13
+ t.verbose = false
14
+ end
15
+ end
16
+
17
+ task :build do
18
+ system 'gem build easytag.gemspec'
19
+ end
20
+
21
+ task :release => :build do
22
+ system "gem push easytag-#{EasyTag::VERSION}.gem"
23
+ end
24
+
25
+ task :clean do
26
+ system 'rm -f *.gem'
27
+ end
data/easytag.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
2
+
3
+ require 'easytag/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'easytag'
7
+ s.version = EasyTag::VERSION
8
+ s.summary = 'A simple audio metadata tagging interface'
9
+ s.description = `head -n1 README.md | ./bin/strip_md.rb`
10
+ s.authors = ['Chris Lucas']
11
+ s.email = ['chris@chrisjlucas.com']
12
+ s.homepage = 'https://github.com/cjlucas/ruby-easytag'
13
+ s.license = 'MIT'
14
+ s.files = `git ls-files | egrep '^[^\.]'`.split(/\r?\n/)
15
+ s.test_files = s.files.select { |f| f.match(/^test\/.*\.rb$/) }
16
+ s.platform = Gem::Platform::RUBY
17
+
18
+ s.add_runtime_dependency('taglib-ruby', '>= 0.6.0')
19
+ s.add_runtime_dependency('ruby-imagespec', '>= 0.3.1')
20
+ s.add_runtime_dependency('ruby-mp3info', '>= 0.8')
21
+
22
+ s.add_development_dependency('rake')
23
+ end
data/lib/easytag.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler/setup'
2
+
3
+ require 'easytag/util'
4
+ require 'easytag/file'
5
+ require 'easytag/interfaces'
@@ -0,0 +1,30 @@
1
+ require 'taglib'
2
+
3
+ require 'easytag/interfaces'
4
+
5
+ module EasyTag
6
+ class EasyTagFileUnsupportedError < Exception; end
7
+
8
+ class Base
9
+ attr_reader :interface
10
+
11
+ def self.build_proxy_methods(iface)
12
+ # generate dynamic getters
13
+ (iface.instance_methods - Object.instance_methods).each do |m|
14
+ define_method(m) { proxy_getter(m) }
15
+ end
16
+ end
17
+
18
+ def proxy_getter(m)
19
+ if @interface.respond_to?(m)
20
+ @interface.send(m)
21
+ else
22
+ warn "#{@interface.class} doesn't support method #{m}"
23
+ end
24
+ end
25
+
26
+ # dynamic instance method generation
27
+ EasyTag::Interfaces.all.each { |iface| self.build_proxy_methods(iface) }
28
+ end
29
+ end
30
+
@@ -0,0 +1,42 @@
1
+ require 'easytag/base'
2
+
3
+ module EasyTag
4
+ class File < Base
5
+ attr_reader :file
6
+
7
+ def initialize(file)
8
+ @file = file
9
+
10
+ if audio_interface_class.nil?
11
+ raise AudioMetadataProxyFileUnsupportedError.new(
12
+ "Couldn't determine which interface to use")
13
+ end
14
+
15
+ @interface = audio_interface_class.new(file)
16
+ end
17
+
18
+ # block interface
19
+ def self.open(file)
20
+ interface = self.new(file)
21
+ begin
22
+ yield interface if block_given?
23
+ ensure
24
+ interface.close if interface.respond_to?(:close)
25
+ end
26
+ end
27
+
28
+ private
29
+ # sufficient for now
30
+ def audio_interface_class
31
+ ext = ::File.extname(@file).downcase[1..-1]
32
+ case ext
33
+ when 'mp3'
34
+ EasyTag::Interfaces::MP3
35
+ when 'mp4', 'm4a', 'aac'
36
+ EasyTag::Interfaces::MP4
37
+ else
38
+ nil
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,84 @@
1
+ require 'image_spec'
2
+
3
+ module EasyTag
4
+ # mimics constants of TagLib::ID3v2::AttachedPictureFrame
5
+ class ImageType
6
+ None = -1
7
+ Other = 0
8
+ FileIcon = 1
9
+ OtherFileIcon = 2
10
+ FrontCover = 3
11
+ BackCover = 4
12
+ LeafletPage = 5
13
+ Media = 6
14
+ LeadArtist = 7
15
+ Artist = 8
16
+ Conductor = 9
17
+ Band = 10
18
+ Composer = 11
19
+ Lyricist = 12
20
+ RecordingLocation = 13
21
+ DuringRecording = 14
22
+ DuringPerformance = 15
23
+ MovieScreenCapture = 16
24
+ ColouredFish = 17
25
+ Illustration = 18
26
+ BandLogo = 19
27
+ PublisherLogo = 20
28
+
29
+ IMAGE_TYPE_TO_STRING_MAP = {
30
+ None => '',
31
+ Other => 'Other',
32
+ FileIcon => 'File Icon (small)',
33
+ OtherFileIcon => 'File Icon',
34
+ FrontCover => 'Cover (front)',
35
+ BackCover => 'Cover (back)',
36
+ LeafletPage => 'Leaflet',
37
+ Media => 'Media',
38
+ LeadArtist => 'Lead Artist',
39
+ Artist => 'Artist',
40
+ Conductor => 'Conductor',
41
+ Band => 'Band',
42
+ Composer => 'Composer',
43
+ Lyricist => 'Lyricist',
44
+ RecordingLocation => 'Recording Location',
45
+ DuringRecording => 'During Recording',
46
+ DuringPerformance => 'During Performance',
47
+ MovieScreenCapture => 'Video Capture',
48
+ ColouredFish => 'A Bright Colored Fish',
49
+ Illustration => 'Illustration',
50
+ BandLogo => 'Band Logo',
51
+ PublisherLogo => 'Publisher Logo'
52
+ }
53
+
54
+ end
55
+
56
+ class Image
57
+ attr_reader :width, :height, :size
58
+ attr_accessor :data, :desc, :mime_type, :type, :type_s
59
+
60
+ def initialize(data)
61
+ self.data = data
62
+ type = ImageType::None
63
+ end
64
+
65
+ def type_s
66
+ ImageType::IMAGE_TYPE_TO_STRING_MAP[type]
67
+ end
68
+
69
+ def data=(data)
70
+ @data = data
71
+ @size = @data.length
72
+
73
+ begin
74
+ spec = ImageSpec.new(StringIO.new(data, 'rb'))
75
+ @mime_type = spec.content_type
76
+ @width = spec.width
77
+ @height = spec.height
78
+ rescue ImageSpec::Error => e
79
+ nil
80
+ end
81
+ end
82
+ end
83
+ end
84
+
@@ -0,0 +1,18 @@
1
+ require 'easytag/image'
2
+ require 'easytag/interfaces/base'
3
+ require 'easytag/interfaces/mp3'
4
+ require 'easytag/interfaces/mp4'
5
+
6
+ module EasyTag::Interfaces
7
+ # get all audio interfaces
8
+ def self.all
9
+ # get all classes in Interfaces
10
+ classes = self.constants.map do |sym|
11
+ const = self.const_get(sym)
12
+ const if const.class == Class && const.method_defined?(:info)
13
+ end
14
+
15
+ # filter out nil's
16
+ classes.compact
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ module EasyTag
2
+ module Interfaces
3
+ class Base
4
+ attr_reader :info
5
+
6
+ # avoid returing empty objects
7
+ def self.obj_or_nil(o)
8
+ if o.class == String
9
+ ret = o.empty? ? nil : o
10
+ else
11
+ o
12
+ end
13
+ end
14
+
15
+ def close
16
+ @info.close
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,186 @@
1
+ require 'mp3info'
2
+
3
+ require 'easytag'
4
+
5
+ module EasyTag::Interfaces
6
+
7
+ class MP3 < Base
8
+ def initialize(file)
9
+ @info = TagLib::MPEG::File.new(file)
10
+ @id3v1 = @info.id3v1_tag
11
+ @id3v2 = @info.id3v2_tag
12
+
13
+ # this is required because taglib hash issues with the TDAT/TYER
14
+ # frame (https://github.com/taglib/taglib/issues/127)
15
+ @id3v2_hash = ID3v2.new
16
+
17
+ File.open(file) do |fp|
18
+ fp.read(3) # read past ID3 identifier
19
+ begin
20
+ @id3v2_hash.from_io(fp)
21
+ rescue ID3v2Error => e
22
+ warn 'no id3v2 tags found'
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ def title
29
+ obj_for_frame_id('TIT2') or Base.obj_or_nil(@id3v1.title)
30
+ end
31
+
32
+ def title_sort_order
33
+ # TSOT - (v2.4 only)
34
+ # XSOT - Musicbrainz Picard custom
35
+ obj_for_frame_id('TSOT') || obj_for_frame_id('XSOT')
36
+ end
37
+
38
+ def artist
39
+ obj_for_frame_id('TPE1') or Base.obj_or_nil(@id3v1.artist)
40
+ end
41
+
42
+ def artist_sort_order
43
+ # TSOP - (v2.4 only)
44
+ # XSOP - Musicbrainz Picard custom
45
+ obj_for_frame_id('TSOP') || obj_for_frame_id('XSOP')
46
+ end
47
+
48
+ def album_artist
49
+ obj_for_frame_id('TPE2')
50
+ end
51
+
52
+ def album_artist_sort_order
53
+ user_info[:albumartistsort]
54
+ end
55
+
56
+ def album
57
+ obj_for_frame_id('TALB') or Base.obj_or_nil(@id3v1.album)
58
+ end
59
+
60
+ def album_sort_order
61
+ # TSOA - (v2.4 only)
62
+ # XSOA - Musicbrainz Picard custom
63
+ obj_for_frame_id('TSOA') || obj_for_frame_id('XSOA')
64
+ end
65
+
66
+ # REVIEW: TCON supports genre refining, which we currently don't utilize
67
+ def genre
68
+ obj_for_frame_id('TCON') or Base.obj_or_nil(@id3v1.genre)
69
+ end
70
+
71
+ def comments
72
+ return @comments unless @comments.nil?
73
+
74
+ comm_frame = lookup_frames('COMM').first
75
+ comm_str = comm_frame ? comm_frame.text : @id3v1.comment
76
+
77
+ @comments = Base.obj_or_nil(comm_str)
78
+ end
79
+
80
+ def year
81
+ date.nil? ? 0 : date.year
82
+ end
83
+
84
+ def date
85
+ return @date unless @date.nil?
86
+
87
+ v10_year = @id3v1.year.to_s if @id3v1.year > 0
88
+ v23_year = obj_for_frame_id('TYER')
89
+ v23_date = Base.obj_or_nil(@id3v2_hash['TDAT'])
90
+ v24_date = obj_for_frame_id('TDRC')
91
+
92
+ # check variables in order of importance
93
+ date_str = v24_date || v23_year || v10_year
94
+ # only append v23_date if date_str is currently a year
95
+ date_str << v23_date unless v23_date.nil? or date_str.length > 4
96
+ puts "MP3#date: date_str = \"#{date_str}\"" if $DEBUG
97
+
98
+ @date = EasyTag::Utilities.get_datetime(date_str)
99
+ end
100
+
101
+ def original_date
102
+ return @original_date unless @original_date.nil?
103
+
104
+ # TDOR - orig release date (v2.4 only)
105
+ # TORY - orig release year (v2.3)
106
+ date_str = obj_for_frame_id('TDOR') || obj_for_frame_id('TORY')
107
+ @original_date ||= EasyTag::Utilities.get_datetime(date_str)
108
+ end
109
+
110
+ def album_art
111
+ return @album_art unless @album_art.nil?
112
+
113
+ @album_art = []
114
+ @id3v2.frame_list('APIC').each do |apic|
115
+ img = EasyTag::Image.new(apic.picture)
116
+ img.desc = apic.description
117
+ img.type = apic.type
118
+ img.mime_type = apic.mime_type
119
+
120
+ @album_art << img
121
+ end
122
+
123
+ @album_art
124
+ end
125
+
126
+ def track_num
127
+ return @track_num unless @track_num.nil?
128
+
129
+ pair = int_pair_for_frame_id('TRCK')
130
+ @track_num = (pair == [0, 0]) ? [@id3v1.track, 0] : pair
131
+ end
132
+
133
+ def disc_num
134
+ int_pair_for_frame_id('TPOS')
135
+ end
136
+
137
+ def user_info
138
+ return @user_info unless @user_info.nil?
139
+
140
+ @user_info = {}
141
+ lookup_frames('TXXX').each do |frame|
142
+ key, value = frame.field_list
143
+ key = EasyTag::Utilities.normalize_string(key)
144
+ @user_info[key.to_sym] = value
145
+ end
146
+
147
+ @user_info
148
+ end
149
+
150
+ def disc_subtitle
151
+ obj_for_frame_id('TSST')
152
+ end
153
+
154
+ def media
155
+ obj_for_frame_id('TMED')
156
+ end
157
+
158
+ def label
159
+ obj_for_frame_id('TPUB')
160
+ end
161
+
162
+ private
163
+
164
+ # for TPOS and TRCK
165
+ def int_pair_for_frame_id(frame_id)
166
+ str = obj_for_frame_id(frame_id)
167
+ EasyTag::Utilities.get_int_pair(str)
168
+ end
169
+
170
+ def obj_for_frame_id(frame_id)
171
+ Base.obj_or_nil(lookup_first_field(frame_id))
172
+ end
173
+
174
+ def lookup_frames(frame_id)
175
+ frames = @id3v2.frame_list(frame_id)
176
+ end
177
+
178
+ # get the first field in the first frame
179
+ def lookup_first_field(frame_id)
180
+ frame = lookup_frames(frame_id).first
181
+ warn "frame '#{frame_id}' is not present" if frame.nil?
182
+ frame.field_list.first unless frame.nil?
183
+ end
184
+ end
185
+ end
186
+
@@ -0,0 +1,188 @@
1
+ # encoding: UTF-8
2
+
3
+ module EasyTag::Interfaces
4
+ class MP4 < Base
5
+ # type of TagLib::MP4::Item
6
+ module ItemType
7
+ STRING = 0 # not part of TagLib::MP4::Item, just for convenience
8
+ STRING_LIST = 1
9
+ BOOL = 2
10
+ INT = 3
11
+ INT_PAIR = 4
12
+ COVER_ART_LIST = 5
13
+ end
14
+
15
+ ITEM_LIST_KEY_MAP = {
16
+ :aart => 'aART', # Album Artist
17
+ :akid => 'akID',
18
+ :apid => 'apID', # Apple ID
19
+ :atid => 'atID',
20
+ :cnid => 'cnID',
21
+ :covr => 'covr', # Cover Art
22
+ :cpil => 'cpil',
23
+ :cprt => 'cprt',
24
+ :disk => 'disk', # Disk [num, total]
25
+ :geid => 'geID',
26
+ :pgap => 'pgap',
27
+ :plid => 'plID',
28
+ :purd => 'purd', # Purchase Date
29
+ :rtng => 'rtng',
30
+ :sfid => 'sfID',
31
+ :stik => 'stik',
32
+ :trkn => 'trkn', # Track [num, total]
33
+ :art => '©ART', # Track Artist
34
+ :alb => '©alb', # Album
35
+ :cmt => '©cmt', # Comments
36
+ :day => '©day', # Release Date
37
+ # (ex: 2006, 2004-08-10, 2007-11-27T08:00:00Z)
38
+ :gen => '©gen', # Genre
39
+ :nam => '©nam', # Title
40
+ :too => '©too', # Encoder
41
+ :soaa => 'soaa', # Sort Order - Album Artist
42
+ :soar => 'soar', # Sort Order - Track Artist
43
+ }
44
+
45
+ def initialize(file)
46
+ @info = TagLib::MP4::File.new(file)
47
+ @tag = @info.tag
48
+ end
49
+
50
+ def title
51
+ obj_for_item_key(:nam, ItemType::STRING)
52
+ end
53
+
54
+ def title_sort_order
55
+ obj_for_item_key(:sonm, ItemType::STRING)
56
+ end
57
+
58
+ def artist
59
+ obj_for_item_key(:art, ItemType::STRING)
60
+ end
61
+
62
+ def artist_sort_order
63
+ obj_for_item_key(:soar, ItemType::STRING)
64
+ end
65
+
66
+ def album_artist
67
+ obj_for_item_key(:aart, ItemType::STRING)
68
+ end
69
+
70
+ def album_artist_sort_order
71
+ obj_for_item_key(:soaa, ItemType::STRING)
72
+ end
73
+
74
+ def album
75
+ obj_for_item_key(:alb, ItemType::STRING)
76
+ end
77
+
78
+ def album_sort_order
79
+ obj_for_item_key(:soal, ItemType::STRING)
80
+ end
81
+
82
+ def genre
83
+ obj_for_item_key(:gen, ItemType::STRING)
84
+ end
85
+
86
+ def comments
87
+ obj_for_item_key(:cmt, ItemType::STRING)
88
+ end
89
+
90
+ # because :day can be anything, including a year, or an exact date,
91
+ # we'll let #date do the parsing, and just grab the year from it
92
+ def year
93
+ date.nil? ? 0 : date.year
94
+ end
95
+
96
+ def date
97
+ return @date unless @date.nil?
98
+
99
+ date_str = obj_for_item_key(:day, ItemType::STRING)
100
+ @date ||= EasyTag::Utilities.get_datetime(date_str)
101
+ end
102
+
103
+ def album_art
104
+ return @album_art unless @album_art.nil?
105
+
106
+ @album_art = []
107
+ covers = obj_for_item_key(:covr, ItemType::COVER_ART_LIST) || []
108
+ covers.each do |cover|
109
+ @album_art << EasyTag::Image.new(cover.data)
110
+ end
111
+
112
+ @album_art
113
+ end
114
+
115
+ def apple_id
116
+ obj_for_item_key(:apid, ItemType::STRING)
117
+ end
118
+
119
+ def track_num
120
+ obj_for_item_key(:trkn, ItemType::INT_PAIR) || [0, 0]
121
+ end
122
+
123
+ def disc_num
124
+ obj_for_item_key(:disk, ItemType::INT_PAIR) || [0, 0]
125
+ end
126
+
127
+ def user_info
128
+ return @user_info unless @user_info.nil?
129
+
130
+ @user_info = {}
131
+ @tag.item_list_map.to_a.each do |key, value|
132
+ match_data = key.match(/\:com.apple.iTunes\:(.*)/)
133
+ if match_data
134
+ key = EasyTag::Utilities.normalize_string(match_data[1])
135
+ @user_info[key.to_sym] = value.to_string_list[0]
136
+ end
137
+ end
138
+
139
+ @user_info
140
+ end
141
+
142
+ def disc_subtitle
143
+ user_info[:discsubtitle]
144
+ end
145
+
146
+ def media
147
+ user_info[:media]
148
+ end
149
+
150
+ def label
151
+ user_info[:label]
152
+ end
153
+
154
+ private
155
+ def lookup_item(key)
156
+ item_id = ITEM_LIST_KEY_MAP.fetch(key, nil)
157
+ @tag.item_list_map.fetch(item_id) unless item_id.nil?
158
+ end
159
+
160
+ def item_to_obj(item, item_type)
161
+ case item_type
162
+ when ItemType::STRING
163
+ item.to_string_list[0]
164
+ when ItemType::STRING_LIST
165
+ item.to_string_list
166
+ when ItemType::BOOL
167
+ item.to_bool
168
+ when ItemType::INT
169
+ item.to_int
170
+ when ItemType::INT_PAIR
171
+ item.to_int_pair
172
+ when ItemType::COVER_ART_LIST
173
+ item.to_cover_art_list
174
+ else
175
+ nil
176
+ end
177
+ end
178
+
179
+ def obj_for_item_key(item_key, item_type)
180
+ return nil if @tag.nil?
181
+
182
+ item = lookup_item(item_key)
183
+ o = item_to_obj(item, item_type) unless item.nil?
184
+
185
+ Base.obj_or_nil(o)
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,63 @@
1
+ require 'date'
2
+
3
+ module EasyTag
4
+ module Utilities
5
+
6
+ # get_datetime
7
+ #
8
+ # return a DateTime object for a given string
9
+ def self.get_datetime(date_str)
10
+ return nil if date_str.nil?
11
+
12
+ # check for known possible formats
13
+ case date_str
14
+ when /^\d{4}$/ # YYYY
15
+ datetime = DateTime.strptime(date_str, '%Y')
16
+ when /^\d{4}\-\d{2}$/ # YYYY-MM
17
+ datetime = DateTime.strptime(date_str, '%Y-%m')
18
+ when /^\d{4}[0-3]\d[0-1]\d$/ # YYYYDDMM (TYER+TDAT)
19
+ datetime = DateTime.strptime(date_str, '%Y%d%m')
20
+ else
21
+ datetime = nil
22
+ end
23
+
24
+ # let DateTime try to parse the stored date as a last resort
25
+ if datetime.nil?
26
+ begin
27
+ datetime = DateTime.parse(date_str)
28
+ rescue ArgumentError
29
+ warn "DateTime couldn't parse '#{date_str}'"
30
+ end
31
+ end
32
+
33
+ datetime
34
+ end
35
+
36
+ # get_int_pair
37
+ #
38
+ # Parses a pos/total string and returns a pair of ints
39
+ def self.get_int_pair(str)
40
+ pair = [0, 0]
41
+
42
+ unless str.nil? || str.empty?
43
+ if str.include?('/')
44
+ pair = str.split('/').map { |it| it.to_i }
45
+ else
46
+ pair[0] = str.to_i
47
+ end
48
+ end
49
+
50
+ pair
51
+ end
52
+
53
+ def self.normalize_string(str)
54
+ # downcase string
55
+ str.downcase!
56
+ # we want snakecase
57
+ str.gsub!(/\s/, '_')
58
+ # we only want alphanumeric characters
59
+ str.gsub(/\W/, '')
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,10 @@
1
+ module EasyTag
2
+ class Version
3
+ MAJOR = 0
4
+ MINOR = 2
5
+ TINY = 0
6
+ end
7
+
8
+ VERSION = [Version::MAJOR, Version::MINOR, Version::TINY].join('.')
9
+
10
+ end
Binary file
Binary file
Binary file
Binary file
Binary file
data/test/no_tags.m4a ADDED
Binary file
data/test/no_tags.mp3 ADDED
Binary file
Binary file
Binary file
@@ -0,0 +1,183 @@
1
+ require 'digest/sha1'
2
+ require 'test/unit'
3
+
4
+ require 'easytag'
5
+
6
+ TEST_DIR = File.dirname(File.absolute_path(__FILE__)) << File::SEPARATOR
7
+
8
+ class TestConsistencyMP301 < Test::Unit::TestCase
9
+ def setup
10
+ @mp3 = EasyTag::File.new("#{TEST_DIR}consistency.01.mp3")
11
+ end
12
+
13
+ def test_tags
14
+ assert_equal('Track Title', @mp3.title)
15
+ assert_equal('Artist Name Here', @mp3.artist)
16
+ assert_equal('Album Artist Here', @mp3.album_artist)
17
+ assert_equal('Album Name Here', @mp3.album)
18
+ assert_equal('This is my comment.', @mp3.comments)
19
+ assert_equal('Polka', @mp3.genre)
20
+ assert_equal(1941, @mp3.year)
21
+ assert_equal([5, 0], @mp3.track_num)
22
+ assert_equal([3, 0], @mp3.disc_num)
23
+ end
24
+
25
+ def test_date
26
+ assert_equal(1941, @mp3.date.year)
27
+ assert_equal(12, @mp3.date.month)
28
+ assert_equal(7, @mp3.date.day)
29
+ end
30
+
31
+ def test_album_art
32
+ assert_equal(2, @mp3.album_art.count)
33
+
34
+ sha1 = Digest::SHA1.hexdigest(@mp3.album_art[0].data)
35
+ assert_equal('this is the front cover', @mp3.album_art[0].desc)
36
+ assert_equal('Cover (front)', @mp3.album_art[0].type_s)
37
+ assert_equal('image/jpeg', @mp3.album_art[0].mime_type)
38
+ assert_equal(10, @mp3.album_art[0].width)
39
+ assert_equal(5, @mp3.album_art[0].height)
40
+ assert_equal('6555697ca1bf96117608bfb5a44c05cb622f88eb', sha1)
41
+
42
+ sha1 = Digest::SHA1.hexdigest(@mp3.album_art[1].data)
43
+ assert_equal('this is the artist', @mp3.album_art[1].desc)
44
+ assert_equal('Artist', @mp3.album_art[1].type_s)
45
+ assert_equal('image/png', @mp3.album_art[1].mime_type)
46
+ assert_equal(10, @mp3.album_art[1].width)
47
+ assert_equal(5, @mp3.album_art[1].height)
48
+ assert_equal('9db2996823fb44ed64dfc8f57dc9df3b970c6b22', sha1)
49
+ end
50
+ end
51
+
52
+ class TestConsistency01 < Test::Unit::TestCase
53
+ def setup
54
+ @mp3 = EasyTag::File.new("#{TEST_DIR}consistency.01.mp3")
55
+ @mp4 = EasyTag::File.new("#{TEST_DIR}consistency.01.m4a")
56
+ end
57
+
58
+ def test_tags
59
+ assert_equal(@mp3.title, @mp4.title)
60
+ assert_equal(@mp3.artist, @mp4.artist)
61
+ assert_equal(@mp3.album_artist, @mp4.album_artist)
62
+ assert_equal(@mp3.album, @mp4.album)
63
+ assert_equal(@mp3.comments, @mp4.comments)
64
+ assert_equal(@mp3.genre, @mp4.genre)
65
+ assert_equal(@mp3.year, @mp4.year)
66
+ end
67
+
68
+ def test_date
69
+ # MP4 only supports year, so thats all we can test
70
+ assert_equal(@mp3.date.year, @mp4.date.year)
71
+ end
72
+
73
+ def test_album_art
74
+ # we're limited to what we can compare here, since mp4 is so limited
75
+ img_mp3 = @mp3.album_art[0]
76
+ img_mp4 = @mp4.album_art[0]
77
+ assert_equal(img_mp3.mime_type, img_mp4.mime_type)
78
+ assert_equal(img_mp3.data, img_mp4.data)
79
+ end
80
+
81
+ end
82
+
83
+ class TestConsistencyMP302 < Test::Unit::TestCase
84
+ def setup
85
+ @mp3 = EasyTag::File.new("#{TEST_DIR}consistency.02.mp3")
86
+ end
87
+
88
+ def test_tags
89
+ cases = [
90
+ ['She Lives in My Lap', @mp3.title],
91
+ [nil, @mp3.title_sort_order],
92
+ ['OutKast feat. Rosario Dawson', @mp3.artist],
93
+ ['OutKast feat. Dawson, Rosario', @mp3.artist_sort_order],
94
+ ['OutKast', @mp3.album_artist],
95
+ #[nil, @mp3.album_artist_sort_order],
96
+ ['Speakerboxxx / The Love Below', @mp3.album],
97
+ [nil, @mp3.album_sort_order],
98
+ [nil, @mp3.comments],
99
+ [nil, @mp3.genre],
100
+ [2003, @mp3.year],
101
+ [[8, 21], @mp3.track_num],
102
+ [[2, 2], @mp3.disc_num],
103
+ ['The Love Below', @mp3.disc_subtitle],
104
+ ['CD', @mp3.media],
105
+ ['Arista', @mp3.label],
106
+ ]
107
+
108
+ cases.each do |c|
109
+ expected, actual = c
110
+ assert_equal(expected, actual)
111
+ end
112
+ end
113
+
114
+ def test_date
115
+ assert_equal(2003, @mp3.date.year)
116
+ assert_equal(9, @mp3.date.month)
117
+ assert_equal(23, @mp3.date.day)
118
+
119
+ assert_equal(2003, @mp3.original_date.year)
120
+ assert_equal(9, @mp3.original_date.month)
121
+ assert_equal(23, @mp3.original_date.day)
122
+ end
123
+
124
+ def test_album_art
125
+ assert_equal(1, @mp3.album_art.count)
126
+
127
+ #sha1 = Digest::SHA1.hexdigest(@mp3.album_art[0].data)
128
+ #assert_equal('this is the front cover', @mp3.album_art[0].desc)
129
+ #assert_equal('Cover (front)', @mp3.album_art[0].type_s)
130
+ #assert_equal('image/jpeg', @mp3.album_art[0].mime_type)
131
+ #assert_equal(10, @mp3.album_art[0].width)
132
+ #assert_equal(5, @mp3.album_art[0].height)
133
+ #assert_equal('6555697ca1bf96117608bfb5a44c05cb622f88eb', sha1)
134
+ end
135
+ end
136
+
137
+ class TestConsistency02 < Test::Unit::TestCase
138
+ def setup
139
+ @mp3 = EasyTag::File.new("#{TEST_DIR}consistency.02.mp3")
140
+ @mp4 = EasyTag::File.new("#{TEST_DIR}consistency.02.m4a")
141
+ end
142
+
143
+ def test_tags
144
+ cases = [
145
+ :title,
146
+ :title_sort_order,
147
+ :artist,
148
+ :artist_sort_order,
149
+ #:album_artist_sort_order, # MP3 isn't working
150
+ :album,
151
+ :album_sort_order,
152
+ :comments,
153
+ :genre,
154
+ :year,
155
+ :track_num,
156
+ :disc_num,
157
+ :disc_subtitle,
158
+ :media,
159
+ :label,
160
+ ]
161
+
162
+ cases.each do |c|
163
+ puts c
164
+ assert_equal(@mp3.send(c), @mp4.send(c))
165
+ end
166
+ end
167
+
168
+ def test_date
169
+ assert_equal(@mp3.date.year, @mp4.date.year)
170
+ assert_equal(@mp3.date.month, @mp4.date.month)
171
+ assert_equal(@mp3.date.day, @mp4.date.day)
172
+ end
173
+
174
+ def test_album_art
175
+ # we're limited to what we can compare here, since mp4 is so limited
176
+ img_mp3 = @mp3.album_art[0]
177
+ img_mp4 = @mp4.album_art[0]
178
+ assert_equal(img_mp3.mime_type, img_mp4.mime_type)
179
+ assert_equal(img_mp3.data, img_mp4.data)
180
+ end
181
+
182
+ end
183
+
data/test/test_mp3.rb ADDED
@@ -0,0 +1,69 @@
1
+ require 'test/unit'
2
+
3
+ require 'easytag'
4
+
5
+ TEST_DIR = File.dirname(File.absolute_path(__FILE__)) << File::SEPARATOR
6
+
7
+ class TestID3v1OnlyMP3 < Test::Unit::TestCase
8
+ def setup
9
+ @f = EasyTag::File.new("#{TEST_DIR}only_id3v1.mp3")
10
+ end
11
+
12
+ def test_tags
13
+ assert_equal('Track Title', @f.title)
14
+ assert_equal('Track Artist', @f.artist)
15
+ assert_equal('Album Name', @f.album)
16
+ assert_equal('this is a comment', @f.comments)
17
+ assert_equal('Swing', @f.genre)
18
+ assert_equal(1988, @f.year)
19
+ assert_equal(1988, @f.date.year)
20
+ assert_equal(true, @f.album_art.empty?)
21
+ assert_equal(nil, @f.apple_id)
22
+ assert_equal([3, 0], @f.track_num)
23
+ assert_equal([0, 0], @f.disc_num)
24
+
25
+ end
26
+ end
27
+
28
+ class TestID3v2OnlyMP3 < Test::Unit::TestCase
29
+ def setup
30
+ @f = EasyTag::File.new("#{TEST_DIR}only_id3v2.mp3")
31
+ end
32
+
33
+ def test_tags
34
+ assert_equal('Track Title', @f.title)
35
+ assert_equal('Track Artist', @f.artist)
36
+ assert_equal('Album Name', @f.album)
37
+ assert_equal('this is a comment', @f.comments)
38
+ assert_equal('Swing', @f.genre)
39
+ assert_equal(1988, @f.year)
40
+ assert_equal(1988, @f.date.year)
41
+ assert_equal(true, @f.album_art.empty?)
42
+ assert_equal(nil, @f.apple_id)
43
+ assert_equal([3, 0], @f.track_num)
44
+ assert_equal([0, 0], @f.disc_num)
45
+
46
+ end
47
+ end
48
+
49
+ class TestNoTagsMP3 < Test::Unit::TestCase
50
+ def setup
51
+ @f = EasyTag::File.new("#{TEST_DIR}no_tags.mp3")
52
+ end
53
+
54
+ def test_tags
55
+ assert_equal(nil, @f.title)
56
+ assert_equal(nil, @f.artist)
57
+ assert_equal(nil, @f.album)
58
+ assert_equal(nil, @f.album_artist)
59
+ assert_equal(nil, @f.comments)
60
+ assert_equal(nil, @f.genre)
61
+ assert_equal(0, @f.year)
62
+ assert_equal(nil, @f.date)
63
+ assert_equal(true, @f.album_art.empty?)
64
+ assert_equal(nil, @f.apple_id)
65
+ assert_equal([0, 0], @f.track_num)
66
+ assert_equal([0, 0], @f.disc_num)
67
+ end
68
+
69
+ end
data/test/test_mp4.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'test/unit'
2
+
3
+ require 'easytag'
4
+
5
+ TEST_DIR = File.dirname(File.absolute_path(__FILE__)) << File::SEPARATOR
6
+
7
+ class TestNoTagsMP4 < Test::Unit::TestCase
8
+ def setup
9
+ @f = EasyTag::File.new("#{TEST_DIR}no_tags.m4a")
10
+ end
11
+
12
+ def test_tags
13
+ assert_equal(nil, @f.title)
14
+ assert_equal(nil, @f.artist)
15
+ assert_equal(nil, @f.album)
16
+ assert_equal(nil, @f.album_artist)
17
+ assert_equal(nil, @f.comments)
18
+ assert_equal(nil, @f.genre)
19
+ assert_equal(0, @f.year)
20
+ assert_equal(nil, @f.date)
21
+ assert_equal(true, @f.album_art.empty?)
22
+ assert_equal(nil, @f.apple_id)
23
+ assert_equal([0, 0], @f.track_num)
24
+ assert_equal([0, 0], @f.disc_num)
25
+ end
26
+
27
+ end
data/test/test_util.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'test/unit'
2
+
3
+ require 'easytag/util'
4
+
5
+ class TestUtilities < Test::Unit::TestCase
6
+ def test_get_datetime01
7
+ date = EasyTag::Utilities.get_datetime('2006')
8
+ assert_equal(2006, date.year)
9
+ assert_equal(1, date.month)
10
+ assert_equal(1, date.day)
11
+ end
12
+
13
+ def test_get_datetime02
14
+ date = EasyTag::Utilities.get_datetime('1955-05')
15
+ assert_equal(1955, date.year)
16
+ assert_equal(5, date.month)
17
+ assert_equal(1, date.day)
18
+ end
19
+
20
+ def test_get_datetime03
21
+ date = EasyTag::Utilities.get_datetime('2012-12-07T08:00:00Z')
22
+ assert_equal(2012, date.year)
23
+ assert_equal(12, date.month)
24
+ assert_equal(7, date.day)
25
+ end
26
+
27
+ def test_get_datetime03
28
+ date = EasyTag::Utilities.get_datetime('19880711')
29
+ assert_equal(1988, date.year)
30
+ assert_equal(11, date.month)
31
+ assert_equal(7, date.day)
32
+ end
33
+
34
+ def test_get_int_pair
35
+ assert_equal([0, 0], EasyTag::Utilities.get_int_pair(''))
36
+ assert_equal([0, 0], EasyTag::Utilities.get_int_pair(nil))
37
+ assert_equal([3, 0], EasyTag::Utilities.get_int_pair('3'))
38
+ assert_equal([0, 9], EasyTag::Utilities.get_int_pair('/9'))
39
+ assert_equal([5, 10], EasyTag::Utilities.get_int_pair('5/10'))
40
+ end
41
+
42
+ def test_normalize_string
43
+ cases = [
44
+ ['MusicBrainz Album Artist Id', 'musicbrainz_album_artist_id'],
45
+ ['\'hi ^''$there #guys\"', 'hi_there_guys'],
46
+ ]
47
+ cases.each do |c|
48
+ orig, result = c
49
+ assert_equal(result, EasyTag::Utilities.normalize_string(orig))
50
+ end
51
+ end
52
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: easytag
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Lucas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-05-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: taglib-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.6.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.6.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: ruby-imagespec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 0.3.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: 0.3.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: ruby-mp3info
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0.8'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: |
70
+ EasyTag is an abstraction layer to the TagLib audio tagging library. It is designed to provide a simple and consistent API regardless of file format being read.
71
+ email:
72
+ - chris@chrisjlucas.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - CHANGELOG.md
78
+ - Gemfile
79
+ - LICENSE
80
+ - README.md
81
+ - Rakefile
82
+ - easytag.gemspec
83
+ - lib/easytag.rb
84
+ - lib/easytag/base.rb
85
+ - lib/easytag/file.rb
86
+ - lib/easytag/image.rb
87
+ - lib/easytag/interfaces.rb
88
+ - lib/easytag/interfaces/base.rb
89
+ - lib/easytag/interfaces/mp3.rb
90
+ - lib/easytag/interfaces/mp4.rb
91
+ - lib/easytag/util.rb
92
+ - lib/easytag/version.rb
93
+ - test/consistency.01.m4a
94
+ - test/consistency.01.mp3
95
+ - test/consistency.02.m4a
96
+ - test/consistency.02.mp3
97
+ - test/musicbrainz.m4a
98
+ - test/no_tags.m4a
99
+ - test/no_tags.mp3
100
+ - test/only_id3v1.mp3
101
+ - test/only_id3v2.mp3
102
+ - test/test_consistency.rb
103
+ - test/test_mp3.rb
104
+ - test/test_mp4.rb
105
+ - test/test_util.rb
106
+ homepage: https://github.com/cjlucas/ruby-easytag
107
+ licenses:
108
+ - MIT
109
+ metadata: {}
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - '>='
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 2.0.3
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: A simple audio metadata tagging interface
130
+ test_files:
131
+ - test/test_consistency.rb
132
+ - test/test_mp3.rb
133
+ - test/test_mp4.rb
134
+ - test/test_util.rb
135
+ has_rdoc: