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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/Gemfile +7 -2
- data/README.md +33 -27
- data/Rakefile +7 -7
- data/easytag.gemspec +2 -4
- data/lib/easytag.rb +12 -2
- data/lib/easytag/attributes/base.rb +32 -128
- data/lib/easytag/attributes/flac.rb +25 -0
- data/lib/easytag/attributes/mp3.rb +91 -554
- data/lib/easytag/attributes/mp4.rb +22 -492
- data/lib/easytag/attributes/ogg.rb +27 -0
- data/lib/easytag/attributes/vorbis.rb +18 -0
- data/lib/easytag/taggers/base.rb +19 -0
- data/lib/easytag/taggers/factory.rb +59 -0
- data/lib/easytag/taggers/flac.rb +23 -0
- data/lib/easytag/taggers/mp3.rb +77 -0
- data/lib/easytag/taggers/mp4.rb +73 -0
- data/lib/easytag/taggers/ogg.rb +25 -0
- data/lib/easytag/taggers/vorbis.rb +66 -0
- data/lib/easytag/util.rb +9 -31
- data/lib/easytag/version.rb +3 -4
- data/scripts/build_attributes_table.rb +34 -0
- data/{test → spec/data}/consistency.01.m4a +0 -0
- data/{test → spec/data}/consistency.01.mp3 +0 -0
- data/spec/data/consistency.flac +0 -0
- data/{test/consistency.02.m4a → spec/data/consistency.m4a} +0 -0
- data/{test/consistency.02.mp3 → spec/data/consistency.mp3} +0 -0
- data/spec/data/consistency.multiple_images.flac +0 -0
- data/{test/no_tags.m4a → spec/data/consistency.multiple_images.m4a} +0 -0
- data/spec/data/consistency.multiple_images.mp3 +0 -0
- data/spec/data/consistency.multiple_images.ogg +0 -0
- data/spec/data/consistency.ogg +0 -0
- data/{test → spec/data}/musicbrainz.m4a +0 -0
- data/spec/data/no_tags.flac +0 -0
- data/spec/data/no_tags.m4a +0 -0
- data/{test → spec/data}/no_tags.mp3 +0 -0
- data/spec/data/no_tags.ogg +0 -0
- data/{test → spec/data}/only_id3v1.mp3 +0 -0
- data/{test → spec/data}/only_id3v2.mp3 +0 -0
- data/spec/flac_tagger_spec.rb +24 -0
- data/spec/mp3_tagger_spec.rb +64 -0
- data/spec/mp4_tagger_spec.rb +22 -0
- data/spec/ogg_tagger_spec.rb +21 -0
- data/spec/shared_examples.rb +115 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/tagger_factory_spec.rb +27 -0
- data/spec/util_spec.rb +47 -0
- metadata +47 -48
- data/lib/easytag/attributes.rb +0 -0
- data/lib/easytag/base.rb +0 -28
- data/lib/easytag/file.rb +0 -44
- data/lib/easytag/interfaces.rb +0 -18
- data/lib/easytag/interfaces/base.rb +0 -24
- data/lib/easytag/interfaces/mp3.rb +0 -48
- data/lib/easytag/interfaces/mp4.rb +0 -14
- data/test/test_consistency.rb +0 -233
- data/test/test_mp3.rb +0 -98
- data/test/test_mp4.rb +0 -49
- data/test/test_util.rb +0 -72
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16662d29b534ff4cc4ec6e52e35b1f725387c01d
|
4
|
+
data.tar.gz: f7b35f5fa59150c4937a3781fc206420741e6c8f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 23e58b9d0726dff60df794e0595fc6fbeac6a8b8a07c3bc82c47a13094cbdc44899886393ba14f491f3c732758866a4d163154c7aa9a6ad313beac9342668ddd
|
7
|
+
data.tar.gz: b2b80e1092243a8fa6f0ff6969ea29ef38b3dfa744fff6502376cb4dac412c9b886225351e8782861a0b357cddf69db2146ecda7c3825faf3ed29c2c2267c969
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
##### v1.0.0 (2013-05-26) #####
|
2
|
+
* added: detecting file type from file signature
|
3
|
+
* first major release
|
4
|
+
|
5
|
+
##### v0.6.0 (2013-05-25) #####
|
6
|
+
* complete overhaul, this release is not compatible with any previous release
|
7
|
+
* FLAC, Ogg support added
|
8
|
+
|
9
|
+
##### v0.4.3 (2013-08-20) #####
|
10
|
+
* fixed: uninitialized constant error with regard to file unsupported exception
|
11
|
+
|
1
12
|
##### v0.4.2 (2013-06-23) #####
|
2
13
|
* added: interal support for `TagLib::ID3v2::UnknownFrame`
|
3
14
|
* info: because a new version of taglib-ruby hasn't been officially released
|
data/Gemfile
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
source 'http://rubygems.org'
|
4
|
+
|
5
|
+
group :test do
|
6
|
+
gem 'rake'
|
7
|
+
gem 'rspec'
|
8
|
+
gem 'coveralls'
|
9
|
+
end
|
5
10
|
|
6
11
|
# read dependencies from gemspec file
|
7
12
|
gemspec
|
data/README.md
CHANGED
@@ -1,52 +1,58 @@
|
|
1
1
|
EasyTag is an abstract interface 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
2
|
|
3
|
+
Currently support file formats are: MP3, MP4, FLAC, and Ogg.
|
3
4
|
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
|
|
5
6
|
[](https://travis-ci.org/cjlucas/ruby-easytag)
|
6
7
|
[](https://travis-ci.org/cjlucas/ruby-easytag)
|
7
8
|
|
9
|
+
NOTE: v0.6.0+ is not compatible with any version prior to v0.6.0.
|
10
|
+
|
8
11
|
---
|
9
12
|
## Synopsis ##
|
13
|
+
|
10
14
|
```ruby
|
11
15
|
require 'easytag'
|
12
16
|
|
13
|
-
|
14
|
-
|
15
|
-
#
|
16
|
-
|
17
|
-
# => "
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
# =>
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
#
|
26
|
-
|
27
|
-
#
|
28
|
-
|
29
|
-
# =>
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
17
|
+
tagger = EasyTag.open('audio.flac') # calls EasyTag::TaggerFactory::open
|
18
|
+
tagger # => <EasyTag::FLACTagger:0x007fc28aba4c90>
|
19
|
+
tagger.title # => "She Lives in My Lap"
|
20
|
+
tagger.artist # => "OutKast feat. Rosario Dawson"
|
21
|
+
tagger.album_artist # => "OutKast"
|
22
|
+
tagger.musicbrainz_artist_id # => ["73fdb566-a9b1-494c-9f32-51768ec9fd27", "9facf8dc-df23-4561-85c5-ece75d692f21"]
|
23
|
+
|
24
|
+
# The backing taglib object is also available
|
25
|
+
f.taglib # => <TagLib::FLAC::File:0x007fc28aba4c68 @__swigtype__="_p_TagLib__FLAC__File">
|
26
|
+
|
27
|
+
f.close
|
28
|
+
|
29
|
+
# A block can also be specified
|
30
|
+
EasyTag.open('audio.mp3') do |tagger|
|
31
|
+
tagger.title # => "She Lives in My Lap"
|
32
|
+
tagger.artist # => "OutKast feat. Rosario Dawson"
|
33
|
+
tagger.album_artist # => "OutKast"
|
34
|
+
|
35
|
+
# tagger.close will be called after the block is executed
|
36
|
+
end
|
37
|
+
|
38
|
+
# Taggers also return consistent defaults for missing attributes
|
39
|
+
EasyTag..open('no_tags.ogg') do |tagger|
|
40
|
+
tagger.year # => 0 (for any int attribute)
|
41
|
+
tagger.album_art # => [] (for any list attribute)
|
42
|
+
tagger.title # => nil (for any non-int, non-list attribute)
|
37
43
|
end
|
38
44
|
```
|
45
|
+
|
46
|
+
|
39
47
|
## Requirements ##
|
40
48
|
- Ruby versions
|
41
|
-
- 1.9
|
42
49
|
- 2.0
|
50
|
+
- 2.1
|
43
51
|
- Dependencies
|
44
52
|
- [TagLib](http://taglib.github.io/) (1.8+)
|
45
53
|
- [taglib-ruby](https://github.com/robinst/taglib-ruby) (0.6.0+)
|
46
|
-
- [ruby-mp3info](https://github.com/moumar/ruby-mp3info)
|
47
54
|
- [ruby-imagespec](https://github.com/andersonbrandon/ruby-imagespec)
|
48
55
|
|
49
56
|
## TODO ##
|
50
57
|
- API Documentation
|
51
|
-
- FLAC and OGG support
|
52
58
|
- Write support
|
data/Rakefile
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
$LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'rspec/core/rake_task'
|
4
4
|
|
5
5
|
require 'easytag/version'
|
6
6
|
|
7
|
-
task :default => [:
|
7
|
+
task :default => [:spec]
|
8
|
+
task :test => :spec
|
8
9
|
|
9
|
-
task :
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
t.verbose = false
|
10
|
+
task :spec do
|
11
|
+
RSpec::Core::RakeTask.new do |task|
|
12
|
+
task.verbose = false
|
13
|
+
task.rspec_opts = '--color'
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
data/easytag.gemspec
CHANGED
@@ -19,9 +19,7 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.test_files = s.files.select { |f| f.match(/^test\/.*\.rb$/) }
|
20
20
|
s.platform = Gem::Platform::RUBY
|
21
21
|
|
22
|
-
|
22
|
+
s.add_runtime_dependency('taglib-ruby', '>= 0.6.0')
|
23
23
|
s.add_runtime_dependency('ruby-imagespec', '>= 0.3.1')
|
24
|
-
s.add_runtime_dependency('ruby-mp3info', '>= 0.8')
|
25
|
-
|
26
|
-
s.add_development_dependency('rake')
|
27
24
|
end
|
25
|
+
|
data/lib/easytag.rb
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
require 'bundler/setup'
|
2
2
|
|
3
3
|
require 'easytag/util'
|
4
|
-
require 'easytag/
|
5
|
-
require 'easytag/
|
4
|
+
require 'easytag/taggers/mp3'
|
5
|
+
require 'easytag/taggers/mp4'
|
6
|
+
require 'easytag/taggers/vorbis'
|
7
|
+
require 'easytag/taggers/flac'
|
8
|
+
require 'easytag/taggers/ogg'
|
9
|
+
require 'easytag/taggers/factory'
|
10
|
+
|
11
|
+
module EasyTag
|
12
|
+
def self.open(file, &block)
|
13
|
+
TaggerFactory.open(file, &block)
|
14
|
+
end
|
15
|
+
end
|
@@ -1,143 +1,47 @@
|
|
1
|
-
module EasyTag
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
BOOLEAN = 5
|
10
|
-
DATETIME = 6
|
11
|
-
LIST = 7
|
12
|
-
end
|
13
|
-
class BaseAttribute
|
14
|
-
Utilities = EasyTag::Utilities
|
15
|
-
|
16
|
-
attr_reader :name, :aliases, :ivar
|
17
|
-
|
18
|
-
def initialize(args)
|
19
|
-
@name = args[:name]
|
20
|
-
@type = args[:type]
|
21
|
-
@default = args[:default] || self.class.default_for_type(@type)
|
22
|
-
@options = args[:options] || {}
|
23
|
-
@handler_opts = args[:handler_opts] || {}
|
24
|
-
@aliases = args[:aliases] || []
|
25
|
-
@ivar = BaseAttribute.name_to_ivar(@name)
|
26
|
-
|
27
|
-
if args[:handler].is_a?(Symbol)
|
28
|
-
@handler = method(args[:handler])
|
29
|
-
elsif args[:handler].is_a?(Proc)
|
30
|
-
@handler = args[:handler]
|
31
|
-
end
|
32
|
-
|
33
|
-
# fill default options
|
34
|
-
|
35
|
-
# Remove nil objects from array (post process)
|
36
|
-
@options[:compact] ||= false
|
37
|
-
# Delete empty objects in array (post process)
|
38
|
-
@options[:delete_empty] ||= false
|
39
|
-
# normalizes key (if hash) (handler or post process)
|
40
|
-
@options[:normalize] ||= false
|
41
|
-
# cast key (if hash) to symbol (handler or post process)
|
42
|
-
@options[:to_sym] ||= false
|
43
|
-
|
44
|
-
end
|
45
|
-
|
46
|
-
def self.can_clone?(obj)
|
47
|
-
obj.is_a?(String) || obj.is_a?(Array) || obj.is_a?(Hash)
|
48
|
-
end
|
49
|
-
|
50
|
-
def self.deep_copy(obj)
|
51
|
-
Marshal.load(Marshal.dump(obj))
|
52
|
-
end
|
53
|
-
|
54
|
-
def self.default_for_type(type)
|
55
|
-
case type
|
56
|
-
when Type::STRING
|
57
|
-
''
|
58
|
-
when Type::DATETIME
|
59
|
-
nil
|
60
|
-
when Type::INT
|
61
|
-
0
|
62
|
-
when Type::INT_LIST
|
63
|
-
[0, 0] # TODO: don't assume an INT_LIST is always an int pair
|
64
|
-
when Type::BOOLEAN
|
65
|
-
false
|
66
|
-
when Type::LIST
|
67
|
-
[]
|
1
|
+
module EasyTag
|
2
|
+
module BaseAttributeAccessors
|
3
|
+
|
4
|
+
def audio_prop_reader(attr_name, prop_name = nil, **opts)
|
5
|
+
prop_name = attr_name if prop_name.nil?
|
6
|
+
define_method(attr_name) do
|
7
|
+
v = self.class.read_audio_property(taglib, prop_name)
|
8
|
+
self.class.post_process(v, opts)
|
68
9
|
end
|
69
10
|
end
|
70
11
|
|
71
|
-
def
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
data = data.to_i
|
86
|
-
when Type::DATETIME
|
87
|
-
data = Utilities.get_datetime(data.to_s)
|
88
|
-
when Type::LIST
|
89
|
-
data = Array(data)
|
12
|
+
def cast(data, key, **opts)
|
13
|
+
case opts[key]
|
14
|
+
when :int
|
15
|
+
data.to_i
|
16
|
+
when :int_pair
|
17
|
+
Utilities.get_int_pair(data)
|
18
|
+
when :datetime
|
19
|
+
Utilities.get_datetime(data.to_s)
|
20
|
+
when :list
|
21
|
+
Array(data)
|
22
|
+
when :bool
|
23
|
+
[1, '1', true].include?(data)
|
24
|
+
else
|
25
|
+
data
|
90
26
|
end
|
91
|
-
|
92
|
-
data
|
93
27
|
end
|
94
28
|
|
95
|
-
def
|
96
|
-
if
|
97
|
-
data = data
|
98
|
-
end
|
99
|
-
|
100
|
-
# fall back to default if data is nil
|
101
|
-
data ||= default
|
102
|
-
|
103
|
-
# REVIEW: the compact option may not be needed anymore
|
104
|
-
# since we've done away with casting empty strings to nil
|
105
|
-
data.compact! if @options[:compact] && data.respond_to?(:compact!)
|
106
|
-
|
107
|
-
if @options[:delete_empty]
|
108
|
-
data.select! { |item| !item.empty? if item.respond_to?(:empty?) }
|
109
|
-
end
|
110
|
-
|
111
|
-
data = Utilities.normalize_object(data) if @options[:normalize]
|
112
|
-
|
113
|
-
# TODO: roll this out to a method that supports more than just array
|
114
|
-
if @options[:to_sym] && data.is_a?(Array)
|
115
|
-
data.map! { |item| item.to_sym if item.respond_to?(:to_sym) }
|
29
|
+
def extract(data, **opts)
|
30
|
+
if opts.has_key?(:extract_list_pos)
|
31
|
+
data = data[opts[:extract_list_pos]]
|
116
32
|
end
|
117
|
-
|
118
33
|
data
|
119
34
|
end
|
120
|
-
|
121
|
-
def self.name_to_ivar(name)
|
122
|
-
name.to_s.gsub(/\?/, '').insert(0, '@').to_sym
|
123
|
-
end
|
124
|
-
|
125
|
-
# read handlers
|
126
35
|
|
127
|
-
def
|
128
|
-
|
36
|
+
def post_process(data, opts)
|
37
|
+
data = cast(data, :cast, **opts)
|
38
|
+
data = extract(data, **opts)
|
39
|
+
cast(data, :returns, **opts)
|
129
40
|
end
|
130
41
|
|
131
|
-
def read_audio_property(
|
132
|
-
key
|
133
|
-
warn "@handler_opts[:key] doesn't exist" if key.nil?
|
134
|
-
iface.info.audio_properties.send(key)
|
135
|
-
end
|
136
|
-
|
137
|
-
def user_info_lookup(iface)
|
138
|
-
key = @handler_opts[:key]
|
139
|
-
warn "@handler_opts[:key] doesn't exist" if key.nil?
|
140
|
-
iface.user_info_normalized[key]
|
42
|
+
def read_audio_property(taglib, key)
|
43
|
+
taglib.audio_properties.send(key)
|
141
44
|
end
|
142
45
|
end
|
143
46
|
end
|
47
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative 'vorbis'
|
2
|
+
|
3
|
+
module EasyTag
|
4
|
+
module FLACAttributeAccessors
|
5
|
+
def field_reader(attr_name, fields = nil, **opts)
|
6
|
+
fields = attr_name.to_s.upcase if fields.nil?
|
7
|
+
define_method(attr_name) do
|
8
|
+
v = self.class.read_fields(taglib.xiph_comment, fields, **opts)
|
9
|
+
self.class.post_process(v, **opts)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def album_art_reader(attr_name, **opts)
|
14
|
+
define_method(attr_name) do
|
15
|
+
taglib.picture_list.collect do |pic|
|
16
|
+
EasyTag::Image.new(pic.data).tap do |img|
|
17
|
+
img.mime_type = pic.mime_type
|
18
|
+
img.type = pic.type
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -4,605 +4,142 @@ require 'easytag/image'
|
|
4
4
|
require 'easytag/util'
|
5
5
|
require 'easytag/attributes/base'
|
6
6
|
|
7
|
-
module EasyTag
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
# ID3 stores boolean values as numeric strings
|
19
|
-
# set to true to enable type casting (post process)
|
20
|
-
@options[:is_flag] ||= false
|
21
|
-
# return entire field list instead of first item in field list
|
22
|
-
@options[:field_list] ||= false
|
7
|
+
module EasyTag
|
8
|
+
module MP3AttributeAccessors
|
9
|
+
include BaseAttributeAccessors
|
10
|
+
|
11
|
+
def single_tag_reader(attr_name, id3v2_frames = nil, id3v1_tag = nil, **opts)
|
12
|
+
id3v2_frames = Array(id3v2_frames)
|
13
|
+
define_method(attr_name) do
|
14
|
+
v = self.class.read_first_tag(taglib, id3v2_frames, id3v1_tag, opts)
|
15
|
+
self.class.post_process(v, opts)
|
16
|
+
end
|
23
17
|
end
|
24
18
|
|
25
|
-
|
26
|
-
|
27
|
-
|
19
|
+
def all_tags_reader(attr_name, id3v2_frames = nil, id3v1_tag = nil, **opts)
|
20
|
+
id3v2_frames = Array(id3v2_frames)
|
21
|
+
define_method(attr_name) do
|
22
|
+
v = self.class.read_all_tags(taglib, id3v2_frames, id3v1_tag, opts)
|
23
|
+
self.class.post_process(v, opts)
|
24
|
+
end
|
28
25
|
end
|
29
26
|
|
30
|
-
def
|
31
|
-
|
27
|
+
def user_info_reader(attr_name, key = nil, **opts)
|
28
|
+
key = attr_name if key.nil?
|
29
|
+
define_method(attr_name) do
|
30
|
+
@user_info = self.class.read_user_info(taglib, **opts) if @user_info.nil?
|
31
|
+
self.class.post_process(@user_info[key], opts)
|
32
|
+
end
|
32
33
|
end
|
33
34
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
data = @options[:field_list] ? field_list : field_list.first
|
39
|
-
elsif frame.is_a?(TagLib::ID3v2::UnsynchronizedLyricsFrame)
|
40
|
-
data = frame.text
|
41
|
-
elsif frame.is_a?(TagLib::ID3v2::CommentsFrame)
|
42
|
-
data = frame.text
|
43
|
-
elsif frame.is_a?(TagLib::ID3v2::AttachedPictureFrame)
|
44
|
-
data = EasyTag::Image.new(frame.picture)
|
45
|
-
data.desc = frame.description
|
46
|
-
data.type = frame.type
|
47
|
-
data.mime_type = frame.mime_type
|
48
|
-
elsif frame.is_a?(TagLib::ID3v2::UnknownFrame)
|
49
|
-
nil
|
50
|
-
else
|
51
|
-
warn 'no defined frames match the given frame'
|
35
|
+
def ufid_reader(attr_name, owner, **opts)
|
36
|
+
define_method(attr_name) do
|
37
|
+
v = self.class.read_ufid(taglib, owner, opts)
|
38
|
+
self.class.post_process(v, opts)
|
52
39
|
end
|
40
|
+
end
|
53
41
|
|
54
|
-
|
42
|
+
def date_reader(attr_name, **opts)
|
43
|
+
opts[:returns] = :datetime unless opts.has_key?(:returns)
|
44
|
+
define_method(attr_name) do
|
45
|
+
v = self.class.read_date(taglib, opts)
|
46
|
+
self.class.post_process(v, opts)
|
47
|
+
end
|
55
48
|
end
|
56
49
|
|
57
|
-
#
|
58
|
-
#
|
59
|
-
|
60
|
-
|
61
|
-
# read_all_id3
|
62
|
-
#
|
63
|
-
# gets data from each frame id given
|
64
|
-
# only falls back to the id3v1 tag if none found
|
65
|
-
def read_all_id3(iface)
|
50
|
+
# gets data from each frame id given only falls back
|
51
|
+
# to the id3v1 tag if no id3v2 frames were found
|
52
|
+
def read_all_tags(taglib, id3v2_frames, id3v1_tag = nil, **opts)
|
66
53
|
frames = []
|
67
|
-
|
68
|
-
frames += frames_for_id(f, iface)
|
69
|
-
end
|
54
|
+
id3v2_frames.each { |frame_id| frames += id3v2_frames(taglib, frame_id) }
|
70
55
|
|
71
56
|
data = []
|
72
57
|
# only check id3v1 if no id3v2 frames found
|
73
58
|
if frames.empty?
|
74
|
-
data <<
|
59
|
+
data << id3v1_tag(taglib, id3v1_tag) unless id3v1_tag.nil?
|
75
60
|
else
|
76
|
-
frames.each { |frame| data << data_from_frame(frame) }
|
61
|
+
frames.each { |frame| data << data_from_frame(frame, **opts) }
|
77
62
|
end
|
78
63
|
|
79
|
-
data
|
64
|
+
data.compact
|
80
65
|
end
|
81
66
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
def read_first_id3(iface)
|
86
|
-
frame = nil
|
87
|
-
@id3v2_frames.each do |f|
|
88
|
-
frame = first_frame_for_id(f, iface) if frame.nil?
|
89
|
-
end
|
67
|
+
def read_first_tag(taglib, id3v2_frames, id3v1_tag = nil, **opts)
|
68
|
+
read_all_tags(taglib, id3v2_frames, id3v1_tag, **opts).first
|
69
|
+
end
|
90
70
|
|
91
|
-
|
92
|
-
|
71
|
+
def id3v1_tag(taglib, tag_name)
|
72
|
+
return nil if taglib.id3v1_tag.empty?
|
73
|
+
v = taglib.id3v1_tag.send(tag_name)
|
74
|
+
# TEMPFIX: nonexistent id3v1 tags return an empty string (taglib-ruby issue #49)
|
75
|
+
case
|
76
|
+
when v.is_a?(Fixnum) && v.zero?
|
77
|
+
nil
|
78
|
+
when v.is_a?(String) && v.empty?
|
79
|
+
nil
|
93
80
|
else
|
94
|
-
|
81
|
+
v
|
95
82
|
end
|
83
|
+
end
|
96
84
|
|
97
|
-
|
85
|
+
def id3v2_frames(taglib, frame_id)
|
86
|
+
taglib.id3v2_tag.frame_list(frame_id)
|
98
87
|
end
|
99
88
|
|
100
|
-
def
|
101
|
-
|
102
|
-
|
89
|
+
def data_from_frame(frame, **opts)
|
90
|
+
case
|
91
|
+
when frame.is_a?(TagLib::ID3v2::TextIdentificationFrame)
|
92
|
+
field_list = frame.field_list
|
93
|
+
opts[:field_list] ? field_list : field_list.first
|
94
|
+
when frame.is_a?(TagLib::ID3v2::UnsynchronizedLyricsFrame)
|
95
|
+
frame.text
|
96
|
+
when frame.is_a?(TagLib::ID3v2::CommentsFrame)
|
97
|
+
frame.text
|
98
|
+
when frame.is_a?(TagLib::ID3v2::AttachedPictureFrame)
|
99
|
+
EasyTag::Image.new(frame.picture).tap do |img|
|
100
|
+
img.desc = frame.description
|
101
|
+
img.type = frame.type
|
102
|
+
img.mime_type = frame.mime_type
|
103
|
+
end
|
104
|
+
else
|
105
|
+
nil
|
106
|
+
end
|
103
107
|
end
|
104
108
|
|
105
|
-
def
|
106
|
-
|
107
|
-
frame_data =
|
109
|
+
def read_user_info(taglib, **opts)
|
110
|
+
user_info = {}
|
111
|
+
frame_data = read_all_tags(taglib, ['TXXX'], nil, {field_list: true})
|
108
112
|
|
109
113
|
frame_data.each do |data|
|
110
114
|
key = data[0]
|
111
115
|
values = data[1..-1]
|
112
116
|
|
113
|
-
key =
|
114
|
-
key = key.to_sym if @options[:to_sym]
|
115
|
-
kv_hash[key] = values.count > 1 ? values : values.first
|
117
|
+
user_info[key] = values.count > 1 ? values : values.first
|
116
118
|
end
|
117
119
|
|
118
|
-
|
120
|
+
user_info
|
119
121
|
end
|
120
122
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
v24_date =
|
123
|
+
# NOTE: id3v2.3 tags (TYER+TDAT) will lose month/day information due to taglib's
|
124
|
+
# internal frame conversion. During the conversion, the TDAT frame is
|
125
|
+
# dropped and only the TYER frame is used in the conversion to TDRC.
|
126
|
+
# (see: https://github.com/taglib/taglib/issues/127)
|
127
|
+
def read_date(taglib, **opts)
|
128
|
+
v10_year = taglib.id3v1_tag.year.to_s if taglib.id3v1_tag.year > 0
|
129
|
+
v24_date = read_first_tag(taglib, ['TDRC'])
|
128
130
|
|
129
131
|
# check variables in order of importance
|
130
|
-
date_str = v24_date ||
|
131
|
-
# only append v23_date if date_str is currently a year
|
132
|
-
date_str << v23_date unless v23_date.nil? or date_str.length > 4
|
132
|
+
date_str = v24_date || v10_year
|
133
133
|
puts "MP3#date: date_str = \"#{date_str}\"" if $DEBUG
|
134
134
|
|
135
135
|
date_str
|
136
136
|
end
|
137
137
|
|
138
|
-
def read_ufid(
|
139
|
-
frames =
|
140
|
-
|
141
|
-
|
142
|
-
frames.each do |frame|
|
143
|
-
if @handler_opts[:owner].eql?(frame.owner)
|
144
|
-
ufid = frame.identifier
|
145
|
-
break
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
ufid
|
138
|
+
def read_ufid(taglib, owner, opts)
|
139
|
+
frames = taglib.id3v2_tag.frame_list('UFID')
|
140
|
+
frames.each { |frame| return frame.identifier if owner.eql?(frame.owner) }
|
141
|
+
nil
|
150
142
|
end
|
151
143
|
end
|
152
144
|
end
|
153
145
|
|
154
|
-
module EasyTag::Attributes
|
155
|
-
MP3_ATTRIB_ARGS = [
|
156
|
-
# title
|
157
|
-
{
|
158
|
-
:name => :title,
|
159
|
-
:id3v2_frames => ['TIT2'],
|
160
|
-
:id3v1_tag => :title,
|
161
|
-
:handler => :read_first_id3,
|
162
|
-
:type => Type::STRING,
|
163
|
-
},
|
164
|
-
|
165
|
-
# title_sort_order
|
166
|
-
# TSOT - (v2.4 only)
|
167
|
-
# XSOT - Musicbrainz Picard custom
|
168
|
-
{
|
169
|
-
:name => :title_sort_order,
|
170
|
-
:id3v2_frames => ['TSOT', 'XSOT'],
|
171
|
-
:handler => :read_first_id3,
|
172
|
-
:type => Type::STRING,
|
173
|
-
},
|
174
|
-
|
175
|
-
# subtitle
|
176
|
-
{
|
177
|
-
:name => :subtitle,
|
178
|
-
:id3v2_frames => ['TIT3'],
|
179
|
-
:handler => :read_first_id3,
|
180
|
-
:type => Type::STRING,
|
181
|
-
},
|
182
|
-
|
183
|
-
# artist
|
184
|
-
{
|
185
|
-
:name => :artist,
|
186
|
-
:id3v2_frames => ['TPE1'],
|
187
|
-
:id3v1_tag => :artist,
|
188
|
-
:handler => :read_first_id3,
|
189
|
-
:type => Type::STRING,
|
190
|
-
},
|
191
|
-
|
192
|
-
# artist_sort_order
|
193
|
-
# TSOP - (v2.4 only)
|
194
|
-
# XSOP - Musicbrainz Picard custom
|
195
|
-
{
|
196
|
-
:name => :artist_sort_order,
|
197
|
-
:id3v2_frames => ['TSOP', 'XSOP'],
|
198
|
-
:handler => :read_first_id3,
|
199
|
-
:type => Type::STRING,
|
200
|
-
},
|
201
|
-
|
202
|
-
# album_artist
|
203
|
-
{
|
204
|
-
:name => :album_artist,
|
205
|
-
:id3v2_frames => ['TPE2'],
|
206
|
-
:handler => :read_first_id3,
|
207
|
-
:type => Type::STRING,
|
208
|
-
},
|
209
|
-
|
210
|
-
# album_artist_sort_order
|
211
|
-
{
|
212
|
-
:name => :album_artist_sort_order,
|
213
|
-
:handler => :user_info_lookup,
|
214
|
-
:handler_opts => {:key => :albumartistsort},
|
215
|
-
:type => Type::STRING,
|
216
|
-
},
|
217
|
-
|
218
|
-
# album
|
219
|
-
{
|
220
|
-
:name => :album,
|
221
|
-
:id3v2_frames => ['TALB'],
|
222
|
-
:id3v1_tag => :album,
|
223
|
-
:handler => :read_first_id3,
|
224
|
-
:type => Type::STRING,
|
225
|
-
},
|
226
|
-
|
227
|
-
# compilation?
|
228
|
-
{
|
229
|
-
:name => :compilation?,
|
230
|
-
:id3v2_frames => ['TCMP'],
|
231
|
-
:handler => :read_first_id3,
|
232
|
-
:type => Type::BOOLEAN,
|
233
|
-
# TODO: remove is_flag option, determine boolean value implicitly
|
234
|
-
:options => {:is_flag => true},
|
235
|
-
},
|
236
|
-
|
237
|
-
# album_sort_order
|
238
|
-
# TSOA - (v2.4 only)
|
239
|
-
# XSOA - Musicbrainz Picard custom
|
240
|
-
{
|
241
|
-
:name => :album_sort_order,
|
242
|
-
:id3v2_frames => ['TSOA', 'XSOA'],
|
243
|
-
:handler => :read_first_id3,
|
244
|
-
:type => Type::STRING,
|
245
|
-
},
|
246
|
-
|
247
|
-
# genre
|
248
|
-
{
|
249
|
-
:name => :genre,
|
250
|
-
:id3v2_frames => ['TCON'],
|
251
|
-
:id3v1_tag => :genre,
|
252
|
-
:handler => :read_first_id3,
|
253
|
-
:type => Type::STRING,
|
254
|
-
},
|
255
|
-
|
256
|
-
# disc_subtitle
|
257
|
-
{
|
258
|
-
:name => :disc_subtitle,
|
259
|
-
:id3v2_frames => ['TSST'],
|
260
|
-
:handler => :read_first_id3,
|
261
|
-
:type => Type::STRING,
|
262
|
-
},
|
263
|
-
|
264
|
-
# media
|
265
|
-
{
|
266
|
-
:name => :media,
|
267
|
-
:id3v2_frames => ['TMED'],
|
268
|
-
:handler => :read_first_id3,
|
269
|
-
:type => Type::STRING,
|
270
|
-
},
|
271
|
-
|
272
|
-
# label
|
273
|
-
{
|
274
|
-
:name => :label,
|
275
|
-
:id3v2_frames => ['TPUB'],
|
276
|
-
:handler => :read_first_id3,
|
277
|
-
:type => Type::STRING,
|
278
|
-
},
|
279
|
-
|
280
|
-
# encoded_by
|
281
|
-
{
|
282
|
-
:name => :encoded_by,
|
283
|
-
:id3v2_frames => ['TENC'],
|
284
|
-
:handler => :read_first_id3,
|
285
|
-
:type => Type::STRING,
|
286
|
-
},
|
287
|
-
|
288
|
-
# encoder_settings
|
289
|
-
{
|
290
|
-
:name => :encoder_settings,
|
291
|
-
:id3v2_frames => ['TSSE'],
|
292
|
-
:handler => :read_first_id3,
|
293
|
-
:type => Type::STRING,
|
294
|
-
},
|
295
|
-
|
296
|
-
# group
|
297
|
-
{
|
298
|
-
:name => :group,
|
299
|
-
:id3v2_frames => ['TIT1'],
|
300
|
-
:handler => :read_first_id3,
|
301
|
-
:type => Type::STRING,
|
302
|
-
},
|
303
|
-
|
304
|
-
# composer
|
305
|
-
{
|
306
|
-
:name => :composer,
|
307
|
-
:id3v2_frames => ['TCOM'],
|
308
|
-
:handler => :read_first_id3,
|
309
|
-
:type => Type::STRING,
|
310
|
-
},
|
311
|
-
|
312
|
-
# conductor
|
313
|
-
{
|
314
|
-
:name => :conductor,
|
315
|
-
:id3v2_frames => ['TPE3'],
|
316
|
-
:handler => :read_first_id3,
|
317
|
-
:type => Type::STRING,
|
318
|
-
},
|
319
|
-
|
320
|
-
# remixer
|
321
|
-
{
|
322
|
-
:name => :remixer,
|
323
|
-
:id3v2_frames => ['TPE4'],
|
324
|
-
:handler => :read_first_id3,
|
325
|
-
:type => Type::STRING,
|
326
|
-
},
|
327
|
-
|
328
|
-
# lyrics
|
329
|
-
{
|
330
|
-
:name => :lyrics,
|
331
|
-
:id3v2_frames => ['USLT'],
|
332
|
-
:handler => :read_first_id3,
|
333
|
-
:type => Type::STRING,
|
334
|
-
},
|
335
|
-
|
336
|
-
# lyricist
|
337
|
-
{
|
338
|
-
:name => :lyricist,
|
339
|
-
:id3v2_frames => ['TEXT'],
|
340
|
-
:handler => :read_first_id3,
|
341
|
-
:type => Type::STRING,
|
342
|
-
},
|
343
|
-
|
344
|
-
# copyright
|
345
|
-
{
|
346
|
-
:name => :copyright,
|
347
|
-
:id3v2_frames => ['TCOP'],
|
348
|
-
:handler => :read_first_id3,
|
349
|
-
:type => Type::STRING,
|
350
|
-
},
|
351
|
-
|
352
|
-
# bpm
|
353
|
-
{
|
354
|
-
:name => :bpm,
|
355
|
-
:id3v2_frames => ['TBPM'],
|
356
|
-
:handler => :read_first_id3,
|
357
|
-
:type => Type::INT,
|
358
|
-
},
|
359
|
-
|
360
|
-
# mood
|
361
|
-
{
|
362
|
-
:name => :mood,
|
363
|
-
:id3v2_frames => ['TMOO'],
|
364
|
-
:handler => :read_first_id3,
|
365
|
-
:type => Type::STRING,
|
366
|
-
},
|
367
|
-
|
368
|
-
# track_num
|
369
|
-
{
|
370
|
-
:name => :track_num,
|
371
|
-
:id3v2_frames => ['TRCK'],
|
372
|
-
:id3v1_tag => :track,
|
373
|
-
:default => [0, 0],
|
374
|
-
:handler => :read_int_pair,
|
375
|
-
:type => Type::INT_LIST, # don't know if this will ever be useful
|
376
|
-
},
|
377
|
-
|
378
|
-
# disc_num
|
379
|
-
{
|
380
|
-
:name => :disc_num,
|
381
|
-
:id3v2_frames => ['TPOS'],
|
382
|
-
:default => [0, 0],
|
383
|
-
:handler => :read_int_pair,
|
384
|
-
:type => Type::INT_LIST, # don't know if this will ever be useful
|
385
|
-
},
|
386
|
-
|
387
|
-
# original_date
|
388
|
-
# TDOR - orig release date (v2.4 only)
|
389
|
-
# TORY - orig release year (v2.3)
|
390
|
-
{
|
391
|
-
:name => :original_date,
|
392
|
-
:id3v2_frames => ['TDOR', 'TORY'],
|
393
|
-
:handler => :read_first_id3,
|
394
|
-
:type => Type::DATETIME,
|
395
|
-
},
|
396
|
-
|
397
|
-
# comments
|
398
|
-
{
|
399
|
-
:name => :comments,
|
400
|
-
:id3v2_frames => ['COMM'],
|
401
|
-
:id3v1_tag => :comment,
|
402
|
-
:handler => :read_all_id3,
|
403
|
-
:default => [],
|
404
|
-
:options => { :compact => true, :delete_empty => true }
|
405
|
-
},
|
406
|
-
|
407
|
-
# comment
|
408
|
-
{
|
409
|
-
:name => :comment,
|
410
|
-
:handler => lambda { |iface| iface.comments.first },
|
411
|
-
:type => Type::STRING,
|
412
|
-
},
|
413
|
-
|
414
|
-
# album_art
|
415
|
-
{
|
416
|
-
:name => :album_art,
|
417
|
-
:id3v2_frames => ['APIC'],
|
418
|
-
:handler => :read_all_id3,
|
419
|
-
:default => [],
|
420
|
-
},
|
421
|
-
|
422
|
-
# date
|
423
|
-
{
|
424
|
-
:name => :date,
|
425
|
-
:handler => :read_date,
|
426
|
-
:type => Type::DATETIME,
|
427
|
-
},
|
428
|
-
|
429
|
-
# year
|
430
|
-
{
|
431
|
-
:name => :year,
|
432
|
-
:handler => lambda { |iface| iface.date.nil? ? 0 : iface.date.year }
|
433
|
-
},
|
434
|
-
|
435
|
-
# apple_id
|
436
|
-
{
|
437
|
-
:name => :apple_id,
|
438
|
-
:handler => :read_default,
|
439
|
-
:type => Type::STRING,
|
440
|
-
},
|
441
|
-
|
442
|
-
# user_info
|
443
|
-
{
|
444
|
-
:name => :user_info,
|
445
|
-
:id3v2_frames => ['TXXX'],
|
446
|
-
:handler => :read_field_list_as_key_value,
|
447
|
-
:default => {},
|
448
|
-
:options => {:field_list => true},
|
449
|
-
},
|
450
|
-
|
451
|
-
# user_info_normalized
|
452
|
-
{
|
453
|
-
:name => :user_info_normalized,
|
454
|
-
:id3v2_frames => ['TXXX'],
|
455
|
-
:handler => :read_field_list_as_key_value,
|
456
|
-
:default => {},
|
457
|
-
:options => {:normalize => true,
|
458
|
-
:to_sym => true,
|
459
|
-
:field_list => true },
|
460
|
-
},
|
461
|
-
|
462
|
-
# asin
|
463
|
-
{
|
464
|
-
:name => :asin,
|
465
|
-
:handler => :user_info_lookup,
|
466
|
-
:handler_opts => {:key => :asin},
|
467
|
-
:type => Type::STRING,
|
468
|
-
},
|
469
|
-
|
470
|
-
#
|
471
|
-
# MusicBrainz Attributes
|
472
|
-
#
|
473
|
-
|
474
|
-
# musicbrainz_track_id
|
475
|
-
{
|
476
|
-
:name => :musicbrainz_track_id,
|
477
|
-
:handler => :read_ufid,
|
478
|
-
:handler_opts => {:owner => 'http://musicbrainz.org'},
|
479
|
-
:type => Type::STRING,
|
480
|
-
},
|
481
|
-
|
482
|
-
# musicbrainz_album_artist_id
|
483
|
-
{
|
484
|
-
:name => :musicbrainz_album_artist_id,
|
485
|
-
:handler => :user_info_lookup,
|
486
|
-
:handler_opts => {:key => :musicbrainz_album_artist_id},
|
487
|
-
:type => Type::STRING,
|
488
|
-
},
|
489
|
-
|
490
|
-
# musicbrainz_artist_id
|
491
|
-
{
|
492
|
-
:name => :musicbrainz_artist_id,
|
493
|
-
:handler => :user_info_lookup,
|
494
|
-
:handler_opts => {:key => :musicbrainz_artist_id},
|
495
|
-
:type => Type::LIST,
|
496
|
-
},
|
497
|
-
|
498
|
-
# musicbrainz_album_id
|
499
|
-
{
|
500
|
-
:name => :musicbrainz_album_id,
|
501
|
-
:handler => :user_info_lookup,
|
502
|
-
:handler_opts => {:key => :musicbrainz_album_id},
|
503
|
-
:type => Type::STRING,
|
504
|
-
},
|
505
|
-
|
506
|
-
# musicbrainz_album_status
|
507
|
-
{
|
508
|
-
:name => :musicbrainz_album_status,
|
509
|
-
:handler => :user_info_lookup,
|
510
|
-
:handler_opts => {:key => :musicbrainz_album_status},
|
511
|
-
:type => Type::STRING,
|
512
|
-
},
|
513
|
-
|
514
|
-
# musicbrainz_album_type
|
515
|
-
{
|
516
|
-
:name => :musicbrainz_album_type,
|
517
|
-
:handler => :user_info_lookup,
|
518
|
-
:handler_opts => {:key => :musicbrainz_album_type},
|
519
|
-
:type => Type::LIST,
|
520
|
-
},
|
521
|
-
|
522
|
-
|
523
|
-
# musicbrainz_release_group_id
|
524
|
-
{
|
525
|
-
:name => :musicbrainz_release_group_id,
|
526
|
-
:handler => :user_info_lookup,
|
527
|
-
:handler_opts => {:key => :musicbrainz_release_group_id},
|
528
|
-
:type => Type::STRING,
|
529
|
-
},
|
530
|
-
|
531
|
-
# musicbrainz_album_release_country
|
532
|
-
{
|
533
|
-
:name => :musicbrainz_album_release_country,
|
534
|
-
:handler => :user_info_lookup,
|
535
|
-
:handler_opts => {:key => :musicbrainz_album_release_country},
|
536
|
-
:type => Type::STRING,
|
537
|
-
},
|
538
|
-
|
539
|
-
#
|
540
|
-
# Audio Properties
|
541
|
-
#
|
542
|
-
|
543
|
-
# length
|
544
|
-
{
|
545
|
-
:name => :length,
|
546
|
-
:aliases => [:duration],
|
547
|
-
:handler => :read_audio_property,
|
548
|
-
:handler_opts => {:key => :length},
|
549
|
-
:type => Type::INT,
|
550
|
-
},
|
551
|
-
|
552
|
-
# bitrate
|
553
|
-
{
|
554
|
-
:name => :bitrate,
|
555
|
-
:handler => :read_audio_property,
|
556
|
-
:handler_opts => {:key => :bitrate},
|
557
|
-
:type => Type::INT,
|
558
|
-
},
|
559
|
-
|
560
|
-
# sample_rate
|
561
|
-
{
|
562
|
-
:name => :sample_rate,
|
563
|
-
:handler => :read_audio_property,
|
564
|
-
:handler_opts => {:key => :sample_rate},
|
565
|
-
:type => Type::INT,
|
566
|
-
},
|
567
|
-
|
568
|
-
# channels
|
569
|
-
{
|
570
|
-
:name => :channels,
|
571
|
-
:handler => :read_audio_property,
|
572
|
-
:handler_opts => {:key => :channels},
|
573
|
-
:type => Type::INT,
|
574
|
-
},
|
575
|
-
|
576
|
-
# copyrighted?
|
577
|
-
{
|
578
|
-
:name => :copyrighted?,
|
579
|
-
:handler => :read_audio_property,
|
580
|
-
:handler_opts => {:key => :copyrighted?},
|
581
|
-
:type => Type::BOOLEAN,
|
582
|
-
},
|
583
|
-
|
584
|
-
# layer
|
585
|
-
{
|
586
|
-
:name => :layer,
|
587
|
-
:handler => :read_audio_property,
|
588
|
-
:handler_opts => {:key => :layer},
|
589
|
-
:type => Type::INT,
|
590
|
-
},
|
591
|
-
|
592
|
-
# original?
|
593
|
-
{
|
594
|
-
:name => :original?,
|
595
|
-
:handler => :read_audio_property,
|
596
|
-
:handler_opts => {:key => :original?},
|
597
|
-
:type => Type::BOOLEAN,
|
598
|
-
},
|
599
|
-
|
600
|
-
# protection_enabled?
|
601
|
-
{
|
602
|
-
:name => :protection_enabled?,
|
603
|
-
:handler => :read_audio_property,
|
604
|
-
:handler_opts => {:key => :protection_enabled},
|
605
|
-
:type => Type::BOOLEAN,
|
606
|
-
},
|
607
|
-
]
|
608
|
-
end
|