taglib-simple 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,5 @@
1
+
2
+ #pragma once
3
+
4
+ // this is just a placeholder in case we need to define things before including taglib
5
+
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TagLib
4
+ class Error < StandardError; end
5
+ end