taglib-simple 0.1.1
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/.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
|