taglib-simple 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.yardopts +8 -0
- data/INTERNALS.md +40 -0
- data/LICENSE.txt +20 -0
- data/README.md +167 -0
- data/bin/taglib.rb +146 -0
- data/ext/taglib_simple_fileref/FileRef.cpp +270 -0
- data/ext/taglib_simple_fileref/FileRef.hpp +164 -0
- data/ext/taglib_simple_fileref/IOStream.cpp +148 -0
- data/ext/taglib_simple_fileref/IOStream.hpp +48 -0
- data/ext/taglib_simple_fileref/conversions.h +38 -0
- data/ext/taglib_simple_fileref/convert_ruby_to_taglib.cpp +205 -0
- data/ext/taglib_simple_fileref/convert_taglib_to_ruby.cpp +133 -0
- data/ext/taglib_simple_fileref/extconf.rb +23 -0
- data/ext/taglib_simple_fileref/taglib_simple_fileref.cpp +57 -0
- data/ext/taglib_simple_fileref/taglib_wrap.h +5 -0
- data/lib/taglib_simple/audio_properties.rb +32 -0
- data/lib/taglib_simple/audio_tag.rb +67 -0
- data/lib/taglib_simple/error.rb +5 -0
- data/lib/taglib_simple/media_file.rb +598 -0
- data/lib/taglib_simple/types.rb +47 -0
- data/lib/taglib_simple/version.rb +8 -0
- data/lib/taglib_simple.rb +22 -0
- data/lib/yard_rice.rb +53 -0
- metadata +199 -0
@@ -0,0 +1,133 @@
|
|
1
|
+
#include "conversions.h"
|
2
|
+
#include <ruby/encoding.h>
|
3
|
+
|
4
|
+
using namespace Rice;
|
5
|
+
|
6
|
+
namespace TagLib {
|
7
|
+
namespace Simple {
|
8
|
+
|
9
|
+
Object tagLibStringToNonEmptyRubyUTF8String(TagLib::String string) {
|
10
|
+
if (string.length() == 0) {
|
11
|
+
return {Qnil};
|
12
|
+
}
|
13
|
+
return { tagLibStringToRubyUTF8String(string) };
|
14
|
+
}
|
15
|
+
|
16
|
+
Object uintToNonZeroRubyInteger(unsigned integer) {
|
17
|
+
if (integer == 0) {
|
18
|
+
return {Qnil};
|
19
|
+
}
|
20
|
+
return { UINT2NUM(integer) };
|
21
|
+
}
|
22
|
+
|
23
|
+
Rice::String tagLibStringToRubyUTF8String(const TagLib::String& str) {
|
24
|
+
Rice::String rb_str(str.to8Bit(true)); // true ensures UTF-8
|
25
|
+
rb_enc_associate(rb_str.value(), rb_utf8_encoding());
|
26
|
+
return rb_str;
|
27
|
+
}
|
28
|
+
|
29
|
+
Array tagLibStringListToRuby(const TagLib::StringList& list) {
|
30
|
+
Array result;
|
31
|
+
for (const auto& str : list) {
|
32
|
+
result.push(tagLibStringToRubyUTF8String(str));
|
33
|
+
}
|
34
|
+
return result;
|
35
|
+
}
|
36
|
+
|
37
|
+
Rice::String tagLibByteVectorToRuby(const TagLib::ByteVector& byteVector) {
|
38
|
+
// Convert ByteVector to Ruby String with binary encoding
|
39
|
+
Rice::String rb_str(std::string(byteVector.data(), byteVector.size()));
|
40
|
+
rb_enc_associate(rb_str.value(), rb_ascii8bit_encoding());
|
41
|
+
return rb_str;
|
42
|
+
}
|
43
|
+
|
44
|
+
Array tagLibByteVectorListToRuby(const TagLib::ByteVectorList& list) {
|
45
|
+
Array result;
|
46
|
+
for (const auto& byteVector : list) {
|
47
|
+
result.push(tagLibByteVectorToRuby((byteVector)));
|
48
|
+
}
|
49
|
+
return result;
|
50
|
+
}
|
51
|
+
|
52
|
+
Hash tagLibPropertyMapToRubyHash(const TagLib::PropertyMap& properties) {
|
53
|
+
Hash result;
|
54
|
+
// Iterate through the PropertyMap
|
55
|
+
for(auto & property : properties) {
|
56
|
+
|
57
|
+
Rice::String key = tagLibStringToRubyUTF8String(property.first);
|
58
|
+
|
59
|
+
// Convert the StringList to Ruby Array
|
60
|
+
Array values;
|
61
|
+
for(const auto& item : property.second) {
|
62
|
+
values.push(tagLibStringToRubyUTF8String(item));
|
63
|
+
}
|
64
|
+
// Add to result hash
|
65
|
+
values.freeze();
|
66
|
+
result[key] = values;
|
67
|
+
}
|
68
|
+
|
69
|
+
result.freeze();
|
70
|
+
return result;
|
71
|
+
}
|
72
|
+
#if (TAGLIB_MAJOR_VERSION >= 2)
|
73
|
+
Object taglibVariantToRuby(const TagLib::Variant &value);
|
74
|
+
|
75
|
+
Hash taglibVariantMapToRuby(const TagLib::Map<TagLib::String, TagLib::Variant> & map) {
|
76
|
+
Hash result;
|
77
|
+
for (const auto& pair : map) {
|
78
|
+
Rice::String key(pair.first.toCString());
|
79
|
+
const TagLib::Variant& value = pair.second;
|
80
|
+
result[key] = taglibVariantToRuby(value);
|
81
|
+
}
|
82
|
+
return result;
|
83
|
+
}
|
84
|
+
|
85
|
+
Array taglibVariantListToRuby(const TagLib::List<TagLib::Variant> & list) {
|
86
|
+
Array result;
|
87
|
+
for (const auto& item : list) {
|
88
|
+
result.push(taglibVariantToRuby(item));
|
89
|
+
}
|
90
|
+
return result;
|
91
|
+
}
|
92
|
+
|
93
|
+
Object taglibVariantToRuby(const TagLib::Variant &value) {
|
94
|
+
switch (value.type()) {
|
95
|
+
case TagLib::Variant::Bool:
|
96
|
+
return value.toBool() ? Qtrue : Qfalse;
|
97
|
+
case TagLib::Variant::Int:
|
98
|
+
return INT2NUM(value.toInt());
|
99
|
+
case TagLib::Variant::UInt:
|
100
|
+
return UINT2NUM(value.toUInt());
|
101
|
+
case TagLib::Variant::LongLong:
|
102
|
+
return LL2NUM(value.toLongLong());
|
103
|
+
case TagLib::Variant::ULongLong:
|
104
|
+
return ULL2NUM(value.toULongLong());
|
105
|
+
case TagLib::Variant::String:
|
106
|
+
return { tagLibStringToRubyUTF8String(value.toString()) };
|
107
|
+
case TagLib::Variant::StringList:
|
108
|
+
return { tagLibStringListToRuby(value.toStringList()) };
|
109
|
+
case TagLib::Variant::ByteVector:
|
110
|
+
return { tagLibByteVectorToRuby(value.toByteVector()) };
|
111
|
+
case TagLib::Variant::ByteVectorList:
|
112
|
+
return { tagLibByteVectorListToRuby(value.toByteVectorList()) };
|
113
|
+
case TagLib::Variant::VariantList:
|
114
|
+
return { taglibVariantListToRuby(value.toList()) };
|
115
|
+
case TagLib::Variant::VariantMap:
|
116
|
+
return { taglibVariantMapToRuby(value.toMap()) };
|
117
|
+
default:
|
118
|
+
return Qnil;
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
Array tagLibComplexPropertyToRuby(const TagLib::List<TagLib::VariantMap>& list) {
|
123
|
+
Array result;
|
124
|
+
|
125
|
+
for (const auto& variantMap : list) {
|
126
|
+
result.push(taglibVariantMapToRuby(variantMap));
|
127
|
+
}
|
128
|
+
|
129
|
+
return result;
|
130
|
+
}
|
131
|
+
#endif
|
132
|
+
}
|
133
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mkmf-rice'
|
4
|
+
|
5
|
+
# --with-taglib-dir - the install dir that a custom build of taglib has been deployed to.
|
6
|
+
dir = with_config('taglib-dir')
|
7
|
+
if dir && !dir.empty?
|
8
|
+
dir = Pathname.new(dir)
|
9
|
+
dir = dir.expand_path if dir.to_s.start_with?('~') # ~/.local
|
10
|
+
# treat relative paths as relative to the project root
|
11
|
+
dir = Pathname.new(__FILE__).dirname.parent.parent.expand_path / dir if dir.relative?
|
12
|
+
raise "TAGLIB_DIR does not exist: #{dir}" unless dir.directory?
|
13
|
+
|
14
|
+
dir_config('tag', dir.to_path)
|
15
|
+
end
|
16
|
+
|
17
|
+
have_library('tag') || abort('TagLib is required')
|
18
|
+
|
19
|
+
append_cppflags('-g,-DDEBUG') if enable_config('debug')
|
20
|
+
|
21
|
+
# Add to existing flags
|
22
|
+
append_ldflags('-Wl,--no-undefined')
|
23
|
+
create_makefile('taglib_simple_fileref')
|
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
#include "FileRef.hpp"
|
3
|
+
#if TAGLIB_MAJOR_VERSION > 1
|
4
|
+
#include <taglib/tversionnumber.h>
|
5
|
+
#endif
|
6
|
+
|
7
|
+
extern "C"
|
8
|
+
void Init_taglib_simple_fileref() {
|
9
|
+
Module rb_mTagLib = define_module("TagLib");
|
10
|
+
Module rb_mTagLibExt = define_module_under({rb_mTagLib},"Simple");
|
11
|
+
|
12
|
+
define_taglib_simple_fileref(rb_mTagLibExt);
|
13
|
+
|
14
|
+
uint major;
|
15
|
+
uint minor;
|
16
|
+
uint patch;
|
17
|
+
Rice::String version;
|
18
|
+
|
19
|
+
#if TAGLIB_MAJOR_VERSION == 1
|
20
|
+
// Taglib 1 does not have runtime version information
|
21
|
+
major = TAGLIB_MAJOR_VERSION;
|
22
|
+
minor = TAGLIB_MINOR_VERSION;
|
23
|
+
patch = TAGLIB_PATCH_VERSION;
|
24
|
+
|
25
|
+
version = Rice::String::format("%d.%d.%d", TAGLIB_MAJOR_VERSION, TAGLIB_MINOR_VERSION, TAGLIB_PATCH_VERSION);
|
26
|
+
#else
|
27
|
+
|
28
|
+
TagLib::VersionNumber runtime_version = TagLib::runtimeVersion();
|
29
|
+
|
30
|
+
major = runtime_version.majorVersion();
|
31
|
+
minor = runtime_version.minorVersion();
|
32
|
+
patch = runtime_version.patchVersion();
|
33
|
+
version = runtime_version.toString().toCString();
|
34
|
+
|
35
|
+
// Fatal error on major version mismatch
|
36
|
+
if (TAGLIB_MAJOR_VERSION != runtime_version.majorVersion()) {
|
37
|
+
rb_raise(rb_eLoadError,
|
38
|
+
"Incompatible TagLib version. Compiled with %d.%d.%d but loaded %s",
|
39
|
+
TAGLIB_MAJOR_VERSION, TAGLIB_MINOR_VERSION, TAGLIB_PATCH_VERSION, version.c_str());
|
40
|
+
|
41
|
+
}
|
42
|
+
|
43
|
+
// Warning if compiled against a minor version that is newer than the runtime library
|
44
|
+
if (TAGLIB_MINOR_VERSION > runtime_version.minorVersion()) {
|
45
|
+
rb_warn(
|
46
|
+
"TagLib runtime version %s is older than compile-time version %d.%d.%d",
|
47
|
+
version.c_str(), TAGLIB_MAJOR_VERSION, TAGLIB_MINOR_VERSION, TAGLIB_PATCH_VERSION
|
48
|
+
);
|
49
|
+
}
|
50
|
+
#endif
|
51
|
+
|
52
|
+
rb_mTagLib.const_set("MAJOR_VERSION", UINT2NUM(major));
|
53
|
+
rb_mTagLib.const_set("MINOR_VERSION", UINT2NUM(minor));
|
54
|
+
rb_mTagLib.const_set("PATCH_VERSION", UINT2NUM(patch));
|
55
|
+
rb_mTagLib.const_set("LIBRARY_VERSION", {version});
|
56
|
+
|
57
|
+
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TagLib
|
4
|
+
AudioProperties = Data.define(:audio_length, :bitrate, :sample_rate, :channels)
|
5
|
+
|
6
|
+
# Represents the audio properties of a media file
|
7
|
+
# @see https://taglib.org/api/classTagLib_1_1AudioProperties.html`
|
8
|
+
class AudioProperties < Data
|
9
|
+
# @!attribute [r] audio_length
|
10
|
+
# @return [Integer] The length of the audio in milliseconds
|
11
|
+
|
12
|
+
# @!attribute [r] bitrate
|
13
|
+
# @return [Integer] The bitrate of the audio in kb/s
|
14
|
+
|
15
|
+
# @!attribute [r] sample_rate
|
16
|
+
# @return [Integer] The sample rate in Hz
|
17
|
+
|
18
|
+
# @!attribute [r] channels
|
19
|
+
# @return [Integer] The number of audio channels
|
20
|
+
|
21
|
+
class << self
|
22
|
+
# @param [String|:to_path|IO] filename
|
23
|
+
# @param [Symbol<:average, :fast, :accurate>] audio_properties read style
|
24
|
+
# @return [AudioProperties]
|
25
|
+
def read(filename, audio_properties: true)
|
26
|
+
raise ArgumentError, 'audio_properties must be one of :average, :fast, :accurate' unless audio_properties
|
27
|
+
|
28
|
+
Taglib::MediaFile.open(filename, audio_properties:, &:audio_properties)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TagLib
|
4
|
+
AudioTag = Data.define :title, :artist, :album, :genre, :year, :track, :comment
|
5
|
+
|
6
|
+
# Represents a normalised subset of tags
|
7
|
+
# @see https://taglib.org/api/classTagLib_1_1Tag.html
|
8
|
+
class AudioTag < Data
|
9
|
+
# @!attribute [r] title
|
10
|
+
# @return [String] The title of the track
|
11
|
+
|
12
|
+
# @!attribute [r] artist
|
13
|
+
# @return [String] The artist name
|
14
|
+
|
15
|
+
# @!attribute [r] album
|
16
|
+
# @return [String] The album name
|
17
|
+
|
18
|
+
# @!attribute [r] genre
|
19
|
+
# @return [String] The genre of the track
|
20
|
+
|
21
|
+
# @!attribute [r] year
|
22
|
+
# @return [Integer] The release year
|
23
|
+
|
24
|
+
# @!attribute [r] track
|
25
|
+
# @return [Integer] The track number
|
26
|
+
|
27
|
+
# @!attribute [r] comment
|
28
|
+
# @return [String] Additional comments about the track
|
29
|
+
|
30
|
+
class << self
|
31
|
+
# @param [String|:to_path|IO] filename
|
32
|
+
# @return [AudioTag]
|
33
|
+
def read(filename)
|
34
|
+
MediaFile.open(filename, all: false, tag: true, &:tag)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @!visibility private
|
38
|
+
def check_value(member, value)
|
39
|
+
return value if value.nil?
|
40
|
+
|
41
|
+
return check_int_value(member, value) if %i[year track].include?(member)
|
42
|
+
|
43
|
+
check_string_value(member, value)
|
44
|
+
end
|
45
|
+
|
46
|
+
# @!visibility private
|
47
|
+
def check_int_value(member, value)
|
48
|
+
raise TypeError, "#{member} must be a +ve integer" unless value.is_a?(Integer) && value >= 0
|
49
|
+
|
50
|
+
value.zero? ? nil : value
|
51
|
+
end
|
52
|
+
|
53
|
+
# @!visibility private
|
54
|
+
def check_string_value(member, value)
|
55
|
+
raise TypeError, "#{member} must be a string" unless value.is_a?(String)
|
56
|
+
|
57
|
+
value.empty? ? nil : value
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [Hash<Symbol, <String,Integer>>] the tag values as a hash, excluding entries with nil values
|
62
|
+
def to_h
|
63
|
+
# do not expose nil entries
|
64
|
+
super.compact
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|