easytag 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +17 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +52 -0
- data/Rakefile +27 -0
- data/easytag.gemspec +23 -0
- data/lib/easytag.rb +5 -0
- data/lib/easytag/base.rb +30 -0
- data/lib/easytag/file.rb +42 -0
- data/lib/easytag/image.rb +84 -0
- data/lib/easytag/interfaces.rb +18 -0
- data/lib/easytag/interfaces/base.rb +21 -0
- data/lib/easytag/interfaces/mp3.rb +186 -0
- data/lib/easytag/interfaces/mp4.rb +188 -0
- data/lib/easytag/util.rb +63 -0
- data/lib/easytag/version.rb +10 -0
- data/test/consistency.01.m4a +0 -0
- data/test/consistency.01.mp3 +0 -0
- data/test/consistency.02.m4a +0 -0
- data/test/consistency.02.mp3 +0 -0
- data/test/musicbrainz.m4a +0 -0
- data/test/no_tags.m4a +0 -0
- data/test/no_tags.mp3 +0 -0
- data/test/only_id3v1.mp3 +0 -0
- data/test/only_id3v2.mp3 +0 -0
- data/test/test_consistency.rb +183 -0
- data/test/test_mp3.rb +69 -0
- data/test/test_mp4.rb +27 -0
- data/test/test_util.rb +52 -0
- metadata +135 -0
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
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
|
+
[](https://travis-ci.org/cjlucas/ruby-easytag)
|
6
|
+
[](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
data/lib/easytag/base.rb
ADDED
@@ -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
|
+
|
data/lib/easytag/file.rb
ADDED
@@ -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
|
data/lib/easytag/util.rb
ADDED
@@ -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
|
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
|
data/test/only_id3v1.mp3
ADDED
Binary file
|
data/test/only_id3v2.mp3
ADDED
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:
|