id3_tags 0.0.3 → 0.0.6
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/README.md +1 -0
- data/lib/id3_tags/fields_accessors.rb +32 -0
- data/lib/id3_tags/m4a/fields.rb +29 -0
- data/lib/id3_tags/m4a/fields_getter.rb +66 -0
- data/lib/id3_tags/m4a/fields_setter.rb +38 -0
- data/lib/id3_tags/m4a.rb +12 -112
- data/lib/id3_tags/mp3/fields.rb +28 -0
- data/lib/id3_tags/mp3/fields_getter.rb +60 -0
- data/lib/id3_tags/mp3/fields_setter.rb +74 -0
- data/lib/id3_tags/mp3.rb +14 -124
- data/lib/id3_tags/version.rb +1 -1
- metadata +16 -30
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 100b99f73f0b3018d0aba7803d11681004d2845b
|
|
4
|
+
data.tar.gz: 251c8755cd1a36c64fdbaf18a7d10a564b6f87cb
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3acecdb2b2f49a9e5930251c472f299ad37404190495839fd595d8ec68d658965680252fc7c4e1651de3bffbd495e9b70da76c9e33cbf420d1e31a520a9787bb
|
|
7
|
+
data.tar.gz: b8e32c131cfad5ca10df07b23aa9da99357604ef4b438ddfd02be2e7a8b1911b416f94a3207186fb3e58d7e8518e29942257d0a7de826231b622dd6aadf6db48
|
data/README.md
CHANGED
|
@@ -4,6 +4,7 @@ What is ID3 Tags
|
|
|
4
4
|
A ruby gem to read and write ID3 metadata from/to MP3/M4A files
|
|
5
5
|
|
|
6
6
|
[](https://travis-ci.org/topspin/id3_tags)
|
|
7
|
+
[](https://codeclimate.com/github/topspin/id3_tags)
|
|
7
8
|
|
|
8
9
|
Why ID3 Tags was born
|
|
9
10
|
=====================
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Id3Tags
|
|
2
|
+
module FieldsAccessors
|
|
3
|
+
private
|
|
4
|
+
def with_fields(type)
|
|
5
|
+
fields.select{|k,v| v[:on] == type}
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def read_tag_fields(file, attrs = {}, &block)
|
|
9
|
+
with_fields(:tag).each do |field, opts|
|
|
10
|
+
value = yield(opts)
|
|
11
|
+
attrs[field] = get_field opts[:type], value, opts[:default]
|
|
12
|
+
end unless file.tag.nil?
|
|
13
|
+
attrs
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def read_audio_properties_fields(file, attrs = {}, &block)
|
|
17
|
+
with_fields(:audio_properties).each do |field, opts|
|
|
18
|
+
value = yield(opts)
|
|
19
|
+
attrs[field] = get_field :integer, value
|
|
20
|
+
end unless file.audio_properties.nil?
|
|
21
|
+
attrs
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def read_id3v2_tag_fields(file, attrs = {}, &block)
|
|
25
|
+
with_fields(:id3v2_tag).each do |field, opts|
|
|
26
|
+
value = yield(opts)
|
|
27
|
+
attrs[field] = get_field opts[:type], value
|
|
28
|
+
end unless file.id3v2_tag.nil?
|
|
29
|
+
attrs
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
module Id3Tags
|
|
3
|
+
module M4AFields
|
|
4
|
+
private
|
|
5
|
+
def fields
|
|
6
|
+
{
|
|
7
|
+
album_artist: {on: :tag, frame_id: 'aART', type: :string},
|
|
8
|
+
artist: {on: :tag, frame_id: '©ART', type: :string},
|
|
9
|
+
title: {on: :tag, frame_id: '©nam', type: :string},
|
|
10
|
+
album: {on: :tag, frame_id: '©alb', type: :string},
|
|
11
|
+
comment: {on: :tag, frame_id: '©cmt', type: :string},
|
|
12
|
+
genre: {on: :tag, frame_id: '©gen', type: :string},
|
|
13
|
+
grouping: {on: :tag, frame_id: '©grp', type: :string},
|
|
14
|
+
lyrics: {on: :tag, frame_id: '©lyr', type: :string},
|
|
15
|
+
composer: {on: :tag, frame_id: '©wrt', type: :string},
|
|
16
|
+
year: {on: :tag, frame_id: '©day', type: :string, default: 0},
|
|
17
|
+
disk: {on: :tag, frame_id: 'disk', type: :pair},
|
|
18
|
+
track: {on: :tag, frame_id: 'trkn', type: :pair},
|
|
19
|
+
bpm: {on: :tag, frame_id: 'tmpo', type: :integer},
|
|
20
|
+
compilation: {on: :tag, frame_id: 'cpil', type: :boolean},
|
|
21
|
+
cover_art: {on: :tag, frame_id: 'covr', type: :image},
|
|
22
|
+
bitrate: {on: :audio_properties, method: :bitrate},
|
|
23
|
+
channels: {on: :audio_properties, method: :channels},
|
|
24
|
+
length: {on: :audio_properties, method: :length},
|
|
25
|
+
samplerate: {on: :audio_properties, method: :sample_rate},
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require 'taglib'
|
|
2
|
+
require 'active_support/core_ext/hash'
|
|
3
|
+
|
|
4
|
+
module Id3Tags
|
|
5
|
+
module M4AFieldsGetter
|
|
6
|
+
private
|
|
7
|
+
def get_tag_fields(file, attrs = {})
|
|
8
|
+
read_tag_fields(file, attrs) do |opts|
|
|
9
|
+
file.tag.item_list_map.fetch opts[:frame_id]
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def get_audio_properties_fields(file, attrs = {})
|
|
14
|
+
read_audio_properties_fields(file, attrs) do |opts|
|
|
15
|
+
file.audio_properties.send opts[:method]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get_field(type, value, default = nil)
|
|
20
|
+
case type
|
|
21
|
+
when :string then get_string_field value, default
|
|
22
|
+
when :integer then get_integer_field value
|
|
23
|
+
when :boolean then get_boolean_field value
|
|
24
|
+
when :pair then get_pair_field value
|
|
25
|
+
when :image then get_image_field value
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def get_string_field(value, default = nil)
|
|
30
|
+
string = value && value.to_string_list.first
|
|
31
|
+
if default.present? # then cast to integer (or use default if impossible)
|
|
32
|
+
string ? string.to_i : default
|
|
33
|
+
else
|
|
34
|
+
string
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def get_integer_field(value)
|
|
39
|
+
integer = value && value.to_int
|
|
40
|
+
integer == 0 ? nil : integer # Note: Because BPM = 0 is not valid
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def get_boolean_field(value)
|
|
44
|
+
value && value.to_bool
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def get_pair_field(value)
|
|
48
|
+
pair = value ? value.to_int_pair : [nil, nil]
|
|
49
|
+
{number: pair[0], count: pair[1]}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def get_image_field(value)
|
|
53
|
+
if value
|
|
54
|
+
image = value.to_cover_art_list.first
|
|
55
|
+
data = image.data
|
|
56
|
+
mime_type = case image.format
|
|
57
|
+
when TagLib::MP4::CoverArt::JPEG then 'image/jpeg'
|
|
58
|
+
when TagLib::MP4::CoverArt::PNG then 'image/png'
|
|
59
|
+
end
|
|
60
|
+
else
|
|
61
|
+
data, mime_type = [nil, nil]
|
|
62
|
+
end
|
|
63
|
+
{data: data, mime_type: mime_type}
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'taglib'
|
|
2
|
+
require 'active_support/core_ext/hash'
|
|
3
|
+
|
|
4
|
+
module Id3Tags
|
|
5
|
+
module M4AFieldsSetter
|
|
6
|
+
private
|
|
7
|
+
def set_tag_fields!(file, attrs = {})
|
|
8
|
+
with_fields(:tag).each do |field, opts|
|
|
9
|
+
file.tag.item_list_map.erase opts[:frame_id]
|
|
10
|
+
frame = new_frame_for attrs[field], opts[:frame_id], opts[:type]
|
|
11
|
+
file.tag.item_list_map.insert opts[:frame_id], frame if frame
|
|
12
|
+
end unless file.tag.nil?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def new_frame_for(content, frame_id, type)
|
|
16
|
+
return if content.nil?
|
|
17
|
+
case type
|
|
18
|
+
when :string
|
|
19
|
+
TagLib::MP4::Item.from_string_list [content.to_s]
|
|
20
|
+
when :integer
|
|
21
|
+
TagLib::MP4::Item.from_int content.to_i
|
|
22
|
+
when :boolean
|
|
23
|
+
TagLib::MP4::Item.from_bool content
|
|
24
|
+
when :pair
|
|
25
|
+
return unless content[:number]
|
|
26
|
+
TagLib::MP4::Item.from_int_pair content.values_at(:number, :count)
|
|
27
|
+
when :image
|
|
28
|
+
return unless content[:data]
|
|
29
|
+
mime_type = case content[:mime_type]
|
|
30
|
+
when 'image/jpeg' then TagLib::MP4::CoverArt::JPEG
|
|
31
|
+
when 'image/png' then TagLib::MP4::CoverArt::PNG
|
|
32
|
+
end
|
|
33
|
+
cover_art = TagLib::MP4::CoverArt.new mime_type, content[:data]
|
|
34
|
+
TagLib::MP4::Item.from_cover_art_list [cover_art]
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/lib/id3_tags/m4a.rb
CHANGED
|
@@ -1,130 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
require '
|
|
3
|
-
require '
|
|
1
|
+
require 'id3_tags/m4a/fields'
|
|
2
|
+
require 'id3_tags/fields_accessors'
|
|
3
|
+
require 'id3_tags/m4a/fields_setter'
|
|
4
|
+
require 'id3_tags/m4a/fields_getter'
|
|
4
5
|
|
|
5
6
|
module Id3Tags
|
|
6
7
|
class M4A
|
|
8
|
+
include Id3Tags::M4AFields
|
|
9
|
+
include Id3Tags::FieldsAccessors
|
|
10
|
+
include Id3Tags::M4AFieldsGetter
|
|
11
|
+
include Id3Tags::M4AFieldsSetter
|
|
12
|
+
|
|
7
13
|
def read_tags_from(file_path)
|
|
8
14
|
attrs = {}
|
|
9
15
|
TagLib::MP4::File.open(file_path) do |file|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
assign! attrs, field, value, opts[:type], opts[:default]
|
|
13
|
-
end unless file.tag.nil?
|
|
14
|
-
|
|
15
|
-
audio_properties_fields.each do |field, opts|
|
|
16
|
-
value = file.audio_properties.send opts[:method]
|
|
17
|
-
assign! attrs, field, value, :integer
|
|
18
|
-
end unless file.audio_properties.nil?
|
|
16
|
+
attrs.merge! get_tag_fields(file)
|
|
17
|
+
attrs.merge! get_audio_properties_fields(file)
|
|
19
18
|
end
|
|
20
19
|
attrs.symbolize_keys
|
|
21
20
|
end
|
|
22
21
|
|
|
23
22
|
def write_tags_to(file_path, attrs = {})
|
|
24
23
|
attrs.symbolize_keys!
|
|
25
|
-
|
|
26
24
|
TagLib::MP4::File.open(file_path) do |file|
|
|
27
|
-
|
|
28
|
-
file.tag.item_list_map.erase opts[:frame_id]
|
|
29
|
-
frame = new_frame_for attrs[field], opts[:frame_id], opts[:type]
|
|
30
|
-
file.tag.item_list_map.insert opts[:frame_id], frame if frame
|
|
31
|
-
end unless file.tag.nil?
|
|
32
|
-
|
|
25
|
+
set_tag_fields! file, attrs
|
|
33
26
|
file.save
|
|
34
27
|
end
|
|
35
28
|
end
|
|
36
|
-
|
|
37
|
-
private
|
|
38
|
-
|
|
39
|
-
def fields
|
|
40
|
-
{
|
|
41
|
-
album_artist: {on: :tag, frame_id: 'aART', type: :string},
|
|
42
|
-
artist: {on: :tag, frame_id: '©ART', type: :string},
|
|
43
|
-
title: {on: :tag, frame_id: '©nam', type: :string},
|
|
44
|
-
album: {on: :tag, frame_id: '©alb', type: :string},
|
|
45
|
-
comment: {on: :tag, frame_id: '©cmt', type: :string},
|
|
46
|
-
genre: {on: :tag, frame_id: '©gen', type: :string},
|
|
47
|
-
grouping: {on: :tag, frame_id: '©grp', type: :string},
|
|
48
|
-
lyrics: {on: :tag, frame_id: '©lyr', type: :string},
|
|
49
|
-
composer: {on: :tag, frame_id: '©wrt', type: :string},
|
|
50
|
-
year: {on: :tag, frame_id: '©day', type: :string, default: 0},
|
|
51
|
-
disk: {on: :tag, frame_id: 'disk', type: :pair},
|
|
52
|
-
track: {on: :tag, frame_id: 'trkn', type: :pair},
|
|
53
|
-
bpm: {on: :tag, frame_id: 'tmpo', type: :integer},
|
|
54
|
-
compilation: {on: :tag, frame_id: 'cpil', type: :boolean},
|
|
55
|
-
cover_art: {on: :tag, frame_id: 'covr', type: :image},
|
|
56
|
-
bitrate: {on: :audio_properties, method: :bitrate},
|
|
57
|
-
channels: {on: :audio_properties, method: :channels},
|
|
58
|
-
length: {on: :audio_properties, method: :length},
|
|
59
|
-
samplerate: {on: :audio_properties, method: :sample_rate},
|
|
60
|
-
}
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def assign!(attrs, field, value, type, default = nil)
|
|
64
|
-
case type
|
|
65
|
-
when :string
|
|
66
|
-
attrs[field] = value && value.to_string_list.first
|
|
67
|
-
if default
|
|
68
|
-
if attrs[field]
|
|
69
|
-
attrs[field] = attrs[field].to_i
|
|
70
|
-
else
|
|
71
|
-
attrs[field] = default
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
when :integer
|
|
75
|
-
attrs[field] = value && value.to_int
|
|
76
|
-
if attrs[field] == 0 # well... always? or just bpm?
|
|
77
|
-
attrs[field] = nil
|
|
78
|
-
end
|
|
79
|
-
when :boolean
|
|
80
|
-
attrs[field] = value && value.to_bool
|
|
81
|
-
when :pair
|
|
82
|
-
pair = value ? value.to_int_pair : [nil, nil]
|
|
83
|
-
attrs[field] = {number: pair[0], count: pair[1]}
|
|
84
|
-
when :image
|
|
85
|
-
if value
|
|
86
|
-
image = value.to_cover_art_list.first
|
|
87
|
-
data = image.data
|
|
88
|
-
mime_type = case image.format
|
|
89
|
-
when TagLib::MP4::CoverArt::JPEG then 'image/jpeg'
|
|
90
|
-
when TagLib::MP4::CoverArt::PNG then 'image/png'
|
|
91
|
-
end
|
|
92
|
-
else
|
|
93
|
-
data, mime_type = [nil, nil]
|
|
94
|
-
end
|
|
95
|
-
attrs[:cover_art] = {data: data, mime_type: mime_type}
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def tag_fields
|
|
100
|
-
fields.select{|k,v| v[:on] == :tag}
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def audio_properties_fields
|
|
104
|
-
fields.select{|k,v| v[:on] == :audio_properties }
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def new_frame_for(content, frame_id, type)
|
|
108
|
-
return if content.nil?
|
|
109
|
-
case type
|
|
110
|
-
when :string
|
|
111
|
-
TagLib::MP4::Item.from_string_list [content.to_s]
|
|
112
|
-
when :integer
|
|
113
|
-
TagLib::MP4::Item.from_int content.to_i
|
|
114
|
-
when :boolean
|
|
115
|
-
TagLib::MP4::Item.from_bool content
|
|
116
|
-
when :pair
|
|
117
|
-
return unless content.has_key? :number
|
|
118
|
-
TagLib::MP4::Item.from_int_pair content.values_at(:number, :count)
|
|
119
|
-
when :image
|
|
120
|
-
return unless content.has_key? :data
|
|
121
|
-
mime_type = case content[:mime_type]
|
|
122
|
-
when 'image/jpeg' then TagLib::MP4::CoverArt::JPEG
|
|
123
|
-
when 'image/png' then TagLib::MP4::CoverArt::PNG
|
|
124
|
-
end
|
|
125
|
-
cover_art = TagLib::MP4::CoverArt.new mime_type, content[:data]
|
|
126
|
-
TagLib::MP4::Item.from_cover_art_list [cover_art]
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
29
|
end
|
|
130
30
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Id3Tags
|
|
2
|
+
module MP3Fields
|
|
3
|
+
private
|
|
4
|
+
def fields
|
|
5
|
+
{
|
|
6
|
+
title: {on: :tag, method: :title, type: :string},
|
|
7
|
+
album: {on: :tag, method: :album, type: :string},
|
|
8
|
+
artist: {on: :tag, method: :artist, type: :string},
|
|
9
|
+
comment: {on: :tag, method: :comment, type: :string},
|
|
10
|
+
genre: {on: :tag, method: :genre, type: :string},
|
|
11
|
+
year: {on: :tag, method: :year, type: :integer, default: 0},
|
|
12
|
+
bpm: {on: :id3v2_tag, frame_id: 'TBPM', type: :integer},
|
|
13
|
+
lyrics: {on: :id3v2_tag, frame_id: 'USLT', type: :text},
|
|
14
|
+
composer: {on: :id3v2_tag, frame_id: 'TCOM', type: :string},
|
|
15
|
+
grouping: {on: :id3v2_tag, frame_id: 'TIT1', type: :string},
|
|
16
|
+
album_artist: {on: :id3v2_tag, frame_id: 'TPE2', type: :string},
|
|
17
|
+
compilation: {on: :id3v2_tag, frame_id: 'TCMP', type: :boolean},
|
|
18
|
+
track: {on: :id3v2_tag, frame_id: 'TRCK', type: :pair},
|
|
19
|
+
disk: {on: :id3v2_tag, frame_id: 'TPOS', type: :pair},
|
|
20
|
+
cover_art: {on: :id3v2_tag, frame_id: 'APIC', type: :image},
|
|
21
|
+
bitrate: {on: :audio_properties, method: :bitrate},
|
|
22
|
+
channels: {on: :audio_properties, method: :channels},
|
|
23
|
+
length: {on: :audio_properties, method: :length},
|
|
24
|
+
samplerate: {on: :audio_properties, method: :sample_rate},
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'taglib'
|
|
2
|
+
require 'active_support/core_ext/hash'
|
|
3
|
+
|
|
4
|
+
module Id3Tags
|
|
5
|
+
module MP3FieldsReader
|
|
6
|
+
private
|
|
7
|
+
def get_tag_fields(file, attrs = {})
|
|
8
|
+
read_tag_fields(file, attrs) do |opts|
|
|
9
|
+
file.tag.send opts[:method]
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def get_audio_properties_fields(file, attrs = {})
|
|
14
|
+
read_audio_properties_fields(file, attrs) do |opts|
|
|
15
|
+
file.audio_properties.send opts[:method]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get_id3v2_tag_fields(file, attrs = {})
|
|
20
|
+
read_id3v2_tag_fields(file, attrs) do |opts|
|
|
21
|
+
value = file.id3v2_tag.frame_list(opts[:frame_id]).first
|
|
22
|
+
value and opts[:type] != :image ? value.to_string : value
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def get_field(type, value, default = nil)
|
|
27
|
+
case type
|
|
28
|
+
when :string then get_string_field value
|
|
29
|
+
when :text then get_string_field value
|
|
30
|
+
when :integer then get_integer_field value
|
|
31
|
+
when :boolean then get_boolean_field value
|
|
32
|
+
when :pair then get_pair_field value
|
|
33
|
+
when :image then get_image_field value
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def get_string_field(value)
|
|
38
|
+
value && value.to_s
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def get_integer_field(value)
|
|
42
|
+
value && value.to_i
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def get_boolean_field(value)
|
|
46
|
+
value.present? && value.eql?('1')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def get_pair_field(value)
|
|
50
|
+
pair = value ? value.split('/').map(&:to_i) : [nil, nil]
|
|
51
|
+
{number: pair[0], count: pair[1]}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def get_image_field(value)
|
|
55
|
+
pair = [value && value.mime_type, value && value.picture]
|
|
56
|
+
pair = pair.map(&:presence)
|
|
57
|
+
{mime_type: pair[0], data: pair[1]}
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
require 'taglib'
|
|
2
|
+
require 'active_support/core_ext/hash'
|
|
3
|
+
|
|
4
|
+
module Id3Tags
|
|
5
|
+
module MP3FieldsWriter
|
|
6
|
+
private
|
|
7
|
+
def set_tag_fields!(file, attrs = {})
|
|
8
|
+
with_fields(:tag).each do |field, opts|
|
|
9
|
+
file.tag.send "#{opts[:method]}=", (attrs[field] || opts[:default])
|
|
10
|
+
end unless file.tag.nil?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def set_id3v2_tag_fields!(file, attrs = {})
|
|
14
|
+
with_fields(:id3v2_tag).each do |field, opts|
|
|
15
|
+
file.id3v2_tag.remove_frames opts[:frame_id]
|
|
16
|
+
frame = new_frame_for opts[:type], attrs[field], opts[:frame_id]
|
|
17
|
+
file.id3v2_tag.add_frame frame if frame
|
|
18
|
+
end unless file.id3v2_tag.nil?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def new_frame_for(type, content, frame_id)
|
|
22
|
+
case type
|
|
23
|
+
when :string then new_frame_for_string content, frame_id
|
|
24
|
+
when :integer then new_frame_for_string content, frame_id
|
|
25
|
+
when :text then new_frame_for_text content, frame_id
|
|
26
|
+
when :boolean then new_frame_for_boolean content, frame_id
|
|
27
|
+
when :pair then new_frame_for_pair content, frame_id
|
|
28
|
+
when :image then new_frame_for_image content, frame_id
|
|
29
|
+
end unless content.nil?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def new_frame_for_string(content, frame_id)
|
|
33
|
+
new_string_frame(frame_id).tap {|frame| frame.text = content.to_s}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def new_frame_for_text(content, frame_id)
|
|
37
|
+
new_text_frame(frame_id).tap {|frame| frame.text = content.to_s}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def new_frame_for_boolean(content, frame_id)
|
|
41
|
+
return unless content.eql?(true)
|
|
42
|
+
new_string_frame(frame_id).tap {|frame| frame.text = '1'}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def new_frame_for_pair(content, frame_id)
|
|
46
|
+
return unless content.has_key? :number
|
|
47
|
+
new_string_frame(frame_id).tap do |frame|
|
|
48
|
+
frame.text = content.values_at(:number, :count).compact.join '/'
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def new_frame_for_image(content, frame_id)
|
|
53
|
+
return unless content.has_key? :data
|
|
54
|
+
new_image_frame(frame_id).tap do |frame|
|
|
55
|
+
frame.description = 'Cover'
|
|
56
|
+
frame.type = TagLib::ID3v2::AttachedPictureFrame::FrontCover
|
|
57
|
+
frame.mime_type = content[:mime_type]
|
|
58
|
+
frame.picture = content[:data]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def new_string_frame(frame_id)
|
|
63
|
+
TagLib::ID3v2::TextIdentificationFrame.new frame_id, TagLib::String::UTF8
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def new_text_frame(frame_id)
|
|
67
|
+
TagLib::ID3v2::UnsynchronizedLyricsFrame.new frame_id
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def new_image_frame(frame_id)
|
|
71
|
+
TagLib::ID3v2::AttachedPictureFrame.new frame_id
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
data/lib/id3_tags/mp3.rb
CHANGED
|
@@ -1,142 +1,32 @@
|
|
|
1
|
-
require '
|
|
2
|
-
require '
|
|
1
|
+
require 'id3_tags/mp3/fields'
|
|
2
|
+
require 'id3_tags/fields_accessors'
|
|
3
|
+
require 'id3_tags/mp3/fields_setter'
|
|
4
|
+
require 'id3_tags/mp3/fields_getter'
|
|
3
5
|
|
|
4
6
|
module Id3Tags
|
|
5
7
|
class MPEG
|
|
8
|
+
include Id3Tags::MP3Fields
|
|
9
|
+
include Id3Tags::FieldsAccessors
|
|
10
|
+
include Id3Tags::MP3FieldsReader
|
|
11
|
+
include Id3Tags::MP3FieldsWriter
|
|
12
|
+
|
|
6
13
|
def read_tags_from(file_path)
|
|
7
14
|
attrs = {}
|
|
8
15
|
TagLib::MPEG::File.open(file_path) do |file|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
end unless file.tag.nil?
|
|
13
|
-
|
|
14
|
-
audio_properties_fields.each do |field, opts|
|
|
15
|
-
value = file.audio_properties.send opts[:method]
|
|
16
|
-
assign! attrs, field, value, :integer
|
|
17
|
-
end unless file.audio_properties.nil?
|
|
18
|
-
|
|
19
|
-
id3v2_tag_fields.each do |field, opts|
|
|
20
|
-
value = file.id3v2_tag.frame_list(opts[:frame_id]).first
|
|
21
|
-
value = value.to_string if value and opts[:type] != :image
|
|
22
|
-
assign! attrs, field, value, opts[:type]
|
|
23
|
-
end unless file.id3v2_tag.nil?
|
|
16
|
+
attrs.merge! get_tag_fields(file)
|
|
17
|
+
attrs.merge! get_audio_properties_fields(file)
|
|
18
|
+
attrs.merge! get_id3v2_tag_fields(file)
|
|
24
19
|
end
|
|
25
|
-
|
|
26
20
|
attrs.symbolize_keys
|
|
27
21
|
end
|
|
28
22
|
|
|
29
23
|
def write_tags_to(file_path, attrs = {})
|
|
30
24
|
attrs.symbolize_keys!
|
|
31
|
-
|
|
32
25
|
TagLib::MPEG::File.open(file_path) do |file|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
end unless file.tag.nil?
|
|
36
|
-
|
|
37
|
-
id3v2_tag_fields.each do |field, opts|
|
|
38
|
-
file.id3v2_tag.remove_frames opts[:frame_id]
|
|
39
|
-
frame = new_frame_for attrs[field], opts[:frame_id], opts[:type]
|
|
40
|
-
file.id3v2_tag.add_frame frame if frame
|
|
41
|
-
end unless file.id3v2_tag.nil?
|
|
42
|
-
|
|
26
|
+
set_tag_fields!(file, attrs)
|
|
27
|
+
set_id3v2_tag_fields!(file, attrs)
|
|
43
28
|
file.save
|
|
44
29
|
end
|
|
45
30
|
end
|
|
46
|
-
|
|
47
|
-
private
|
|
48
|
-
|
|
49
|
-
def fields
|
|
50
|
-
{
|
|
51
|
-
title: {on: :tag, method: :title, type: :string},
|
|
52
|
-
album: {on: :tag, method: :album, type: :string},
|
|
53
|
-
artist: {on: :tag, method: :artist, type: :string},
|
|
54
|
-
comment: {on: :tag, method: :comment, type: :string},
|
|
55
|
-
genre: {on: :tag, method: :genre, type: :string},
|
|
56
|
-
year: {on: :tag, method: :year, type: :integer, default: 0},
|
|
57
|
-
bpm: {on: :id3v2_tag, frame_id: 'TBPM', type: :integer},
|
|
58
|
-
lyrics: {on: :id3v2_tag, frame_id: 'USLT', type: :text},
|
|
59
|
-
composer: {on: :id3v2_tag, frame_id: 'TCOM', type: :string},
|
|
60
|
-
grouping: {on: :id3v2_tag, frame_id: 'TIT1', type: :string},
|
|
61
|
-
album_artist: {on: :id3v2_tag, frame_id: 'TPE2', type: :string},
|
|
62
|
-
compilation: {on: :id3v2_tag, frame_id: 'TCMP', type: :boolean},
|
|
63
|
-
track: {on: :id3v2_tag, frame_id: 'TRCK', type: :pair},
|
|
64
|
-
disk: {on: :id3v2_tag, frame_id: 'TPOS', type: :pair},
|
|
65
|
-
cover_art: {on: :id3v2_tag, frame_id: 'APIC', type: :image},
|
|
66
|
-
bitrate: {on: :audio_properties, method: :bitrate},
|
|
67
|
-
channels: {on: :audio_properties, method: :channels},
|
|
68
|
-
length: {on: :audio_properties, method: :length},
|
|
69
|
-
samplerate: {on: :audio_properties, method: :sample_rate},
|
|
70
|
-
}
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def tag_fields
|
|
74
|
-
fields.select{|k,v| v[:on] == :tag}
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def audio_properties_fields
|
|
78
|
-
fields.select{|k,v| v[:on] == :audio_properties }
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def id3v2_tag_fields
|
|
82
|
-
fields.select{|k,v| v[:on] == :id3v2_tag }
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def assign!(attrs, field, value, type)
|
|
86
|
-
case type
|
|
87
|
-
when :string, :text
|
|
88
|
-
attrs[field] = value && value.to_s
|
|
89
|
-
when :integer
|
|
90
|
-
attrs[field] = value && value.to_i
|
|
91
|
-
when :boolean
|
|
92
|
-
attrs[field] = value.present? && value.eql?('1')
|
|
93
|
-
when :pair
|
|
94
|
-
pair = value ? value.split('/').map(&:to_i) : [nil, nil]
|
|
95
|
-
attrs[field] = {number: pair[0], count: pair[1]}
|
|
96
|
-
when :image
|
|
97
|
-
pair = [value && value.mime_type, value && value.picture].map(&:presence)
|
|
98
|
-
attrs[field] = {mime_type: pair[0], data: pair[1]}
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def new_frame_for(content, frame_id, type)
|
|
103
|
-
return if content.nil?
|
|
104
|
-
case type
|
|
105
|
-
when :string, :integer
|
|
106
|
-
frame = new_string_frame(frame_id)
|
|
107
|
-
frame.text = content.to_s
|
|
108
|
-
when :text
|
|
109
|
-
frame = new_text_frame(frame_id)
|
|
110
|
-
frame.text = content.to_s
|
|
111
|
-
when :boolean
|
|
112
|
-
return unless content.eql?(true)
|
|
113
|
-
frame = new_string_frame(frame_id)
|
|
114
|
-
frame.text = '1'
|
|
115
|
-
when :pair
|
|
116
|
-
return unless content.has_key? :number
|
|
117
|
-
frame = new_string_frame(frame_id)
|
|
118
|
-
frame.text = content.values_at(:number, :count).compact.join '/'
|
|
119
|
-
when :image
|
|
120
|
-
return unless content.has_key? :data
|
|
121
|
-
frame = new_image_frame(frame_id)
|
|
122
|
-
frame.description = 'Cover'
|
|
123
|
-
frame.type = TagLib::ID3v2::AttachedPictureFrame::FrontCover
|
|
124
|
-
frame.mime_type = content[:mime_type]
|
|
125
|
-
frame.picture = content[:data]
|
|
126
|
-
end
|
|
127
|
-
frame
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def new_string_frame(frame_id)
|
|
131
|
-
TagLib::ID3v2::TextIdentificationFrame.new frame_id, TagLib::String::UTF8
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def new_text_frame(frame_id)
|
|
135
|
-
TagLib::ID3v2::UnsynchronizedLyricsFrame.new frame_id
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
def new_image_frame(frame_id)
|
|
139
|
-
TagLib::ID3v2::AttachedPictureFrame.new frame_id
|
|
140
|
-
end
|
|
141
31
|
end
|
|
142
32
|
end
|
data/lib/id3_tags/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: id3_tags
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
5
|
-
prerelease:
|
|
4
|
+
version: 0.0.6
|
|
6
5
|
platform: ruby
|
|
7
6
|
authors:
|
|
8
7
|
- Claudio Baccigalupo
|
|
9
8
|
autorequire:
|
|
10
9
|
bindir: bin
|
|
11
10
|
cert_chain: []
|
|
12
|
-
date: 2013-
|
|
11
|
+
date: 2013-07-08 00:00:00.000000000 Z
|
|
13
12
|
dependencies:
|
|
14
13
|
- !ruby/object:Gem::Dependency
|
|
15
14
|
name: taglib-ruby
|
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
|
17
|
-
none: false
|
|
18
16
|
requirements:
|
|
19
17
|
- - ! '>='
|
|
20
18
|
- !ruby/object:Gem::Version
|
|
@@ -22,7 +20,6 @@ dependencies:
|
|
|
22
20
|
type: :runtime
|
|
23
21
|
prerelease: false
|
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
-
none: false
|
|
26
23
|
requirements:
|
|
27
24
|
- - ! '>='
|
|
28
25
|
- !ruby/object:Gem::Version
|
|
@@ -30,23 +27,20 @@ dependencies:
|
|
|
30
27
|
- !ruby/object:Gem::Dependency
|
|
31
28
|
name: activesupport
|
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
|
33
|
-
none: false
|
|
34
30
|
requirements:
|
|
35
|
-
- -
|
|
31
|
+
- - ~>
|
|
36
32
|
- !ruby/object:Gem::Version
|
|
37
|
-
version: '0'
|
|
33
|
+
version: '3.0'
|
|
38
34
|
type: :runtime
|
|
39
35
|
prerelease: false
|
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
41
|
-
none: false
|
|
42
37
|
requirements:
|
|
43
|
-
- -
|
|
38
|
+
- - ~>
|
|
44
39
|
- !ruby/object:Gem::Version
|
|
45
|
-
version: '0'
|
|
40
|
+
version: '3.0'
|
|
46
41
|
- !ruby/object:Gem::Dependency
|
|
47
42
|
name: mimemagic
|
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
|
49
|
-
none: false
|
|
50
44
|
requirements:
|
|
51
45
|
- - ! '>='
|
|
52
46
|
- !ruby/object:Gem::Version
|
|
@@ -54,7 +48,6 @@ dependencies:
|
|
|
54
48
|
type: :runtime
|
|
55
49
|
prerelease: false
|
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
-
none: false
|
|
58
51
|
requirements:
|
|
59
52
|
- - ! '>='
|
|
60
53
|
- !ruby/object:Gem::Version
|
|
@@ -62,7 +55,6 @@ dependencies:
|
|
|
62
55
|
- !ruby/object:Gem::Dependency
|
|
63
56
|
name: bundler
|
|
64
57
|
requirement: !ruby/object:Gem::Requirement
|
|
65
|
-
none: false
|
|
66
58
|
requirements:
|
|
67
59
|
- - ~>
|
|
68
60
|
- !ruby/object:Gem::Version
|
|
@@ -70,7 +62,6 @@ dependencies:
|
|
|
70
62
|
type: :development
|
|
71
63
|
prerelease: false
|
|
72
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
73
|
-
none: false
|
|
74
65
|
requirements:
|
|
75
66
|
- - ~>
|
|
76
67
|
- !ruby/object:Gem::Version
|
|
@@ -78,7 +69,6 @@ dependencies:
|
|
|
78
69
|
- !ruby/object:Gem::Dependency
|
|
79
70
|
name: rake
|
|
80
71
|
requirement: !ruby/object:Gem::Requirement
|
|
81
|
-
none: false
|
|
82
72
|
requirements:
|
|
83
73
|
- - ! '>='
|
|
84
74
|
- !ruby/object:Gem::Version
|
|
@@ -86,7 +76,6 @@ dependencies:
|
|
|
86
76
|
type: :development
|
|
87
77
|
prerelease: false
|
|
88
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
89
|
-
none: false
|
|
90
79
|
requirements:
|
|
91
80
|
- - ! '>='
|
|
92
81
|
- !ruby/object:Gem::Version
|
|
@@ -94,7 +83,6 @@ dependencies:
|
|
|
94
83
|
- !ruby/object:Gem::Dependency
|
|
95
84
|
name: rspec
|
|
96
85
|
requirement: !ruby/object:Gem::Requirement
|
|
97
|
-
none: false
|
|
98
86
|
requirements:
|
|
99
87
|
- - ! '>='
|
|
100
88
|
- !ruby/object:Gem::Version
|
|
@@ -102,7 +90,6 @@ dependencies:
|
|
|
102
90
|
type: :development
|
|
103
91
|
prerelease: false
|
|
104
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
105
|
-
none: false
|
|
106
93
|
requirements:
|
|
107
94
|
- - ! '>='
|
|
108
95
|
- !ruby/object:Gem::Version
|
|
@@ -110,7 +97,6 @@ dependencies:
|
|
|
110
97
|
- !ruby/object:Gem::Dependency
|
|
111
98
|
name: redcarpet
|
|
112
99
|
requirement: !ruby/object:Gem::Requirement
|
|
113
|
-
none: false
|
|
114
100
|
requirements:
|
|
115
101
|
- - ! '>='
|
|
116
102
|
- !ruby/object:Gem::Version
|
|
@@ -118,7 +104,6 @@ dependencies:
|
|
|
118
104
|
type: :development
|
|
119
105
|
prerelease: false
|
|
120
106
|
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
-
none: false
|
|
122
107
|
requirements:
|
|
123
108
|
- - ! '>='
|
|
124
109
|
- !ruby/object:Gem::Version
|
|
@@ -126,7 +111,6 @@ dependencies:
|
|
|
126
111
|
- !ruby/object:Gem::Dependency
|
|
127
112
|
name: yard
|
|
128
113
|
requirement: !ruby/object:Gem::Requirement
|
|
129
|
-
none: false
|
|
130
114
|
requirements:
|
|
131
115
|
- - ! '>='
|
|
132
116
|
- !ruby/object:Gem::Version
|
|
@@ -134,7 +118,6 @@ dependencies:
|
|
|
134
118
|
type: :development
|
|
135
119
|
prerelease: false
|
|
136
120
|
version_requirements: !ruby/object:Gem::Requirement
|
|
137
|
-
none: false
|
|
138
121
|
requirements:
|
|
139
122
|
- - ! '>='
|
|
140
123
|
- !ruby/object:Gem::Version
|
|
@@ -149,7 +132,14 @@ extensions:
|
|
|
149
132
|
extra_rdoc_files: []
|
|
150
133
|
files:
|
|
151
134
|
- bin/id3_tags
|
|
135
|
+
- lib/id3_tags/fields_accessors.rb
|
|
136
|
+
- lib/id3_tags/m4a/fields.rb
|
|
137
|
+
- lib/id3_tags/m4a/fields_getter.rb
|
|
138
|
+
- lib/id3_tags/m4a/fields_setter.rb
|
|
152
139
|
- lib/id3_tags/m4a.rb
|
|
140
|
+
- lib/id3_tags/mp3/fields.rb
|
|
141
|
+
- lib/id3_tags/mp3/fields_getter.rb
|
|
142
|
+
- lib/id3_tags/mp3/fields_setter.rb
|
|
153
143
|
- lib/id3_tags/mp3.rb
|
|
154
144
|
- lib/id3_tags/version.rb
|
|
155
145
|
- lib/id3_tags.rb
|
|
@@ -159,31 +149,27 @@ files:
|
|
|
159
149
|
homepage: https://github.com/topspin/id3_tags
|
|
160
150
|
licenses:
|
|
161
151
|
- MIT
|
|
152
|
+
metadata: {}
|
|
162
153
|
post_install_message:
|
|
163
154
|
rdoc_options: []
|
|
164
155
|
require_paths:
|
|
165
156
|
- lib
|
|
166
157
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
167
|
-
none: false
|
|
168
158
|
requirements:
|
|
169
159
|
- - ! '>='
|
|
170
160
|
- !ruby/object:Gem::Version
|
|
171
161
|
version: 1.9.0
|
|
172
162
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
173
|
-
none: false
|
|
174
163
|
requirements:
|
|
175
164
|
- - ! '>='
|
|
176
165
|
- !ruby/object:Gem::Version
|
|
177
166
|
version: '0'
|
|
178
|
-
segments:
|
|
179
|
-
- 0
|
|
180
|
-
hash: -2525239657657117389
|
|
181
167
|
requirements:
|
|
182
168
|
- taglib >= 1.7.2 (libtag1-dev in Debian/Ubuntu, taglib-devel in Fedora/RHEL)
|
|
183
169
|
rubyforge_project:
|
|
184
|
-
rubygems_version:
|
|
170
|
+
rubygems_version: 2.0.3
|
|
185
171
|
signing_key:
|
|
186
|
-
specification_version:
|
|
172
|
+
specification_version: 4
|
|
187
173
|
summary: ! 'Id3Tags provides two methods: * read_tags_from, which reads an MP3 file
|
|
188
174
|
and returns a Hash of metadata * write_tags_to, which writes a Hash of metadata
|
|
189
175
|
into an MP3 file'
|