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.
@@ -0,0 +1,164 @@
1
+ #pragma once
2
+
3
+ #include "taglib_wrap.h"
4
+ #include <taglib/fileref.h>
5
+ #include <rice/rice.hpp>
6
+
7
+ // Specialised constructor template to avoid the Director constructor that matches Object as first argument.
8
+ // The use of TagLib::FileRef here is arbitrary
9
+ template<typename T, typename... Arg_Ts>
10
+ class Rice::Constructor<T, TagLib::FileRef, Arg_Ts...> {
11
+ public:
12
+ static void construct(VALUE self, Arg_Ts... args) {
13
+ T *data = new T(args...);
14
+ detail::replace<T>(self, Data_Type<T>::ruby_data_type(), data, true);
15
+ }
16
+ };
17
+
18
+ using namespace Rice;
19
+
20
+ // @!yard module TagLib
21
+ namespace TagLib {
22
+ // @!yard module Simple
23
+ namespace Simple {
24
+ /** @!yard
25
+ * # C++ extension wrapping underlying TagLib::FileRef so we can interact with it using Ruby objects
26
+ * class FileRef
27
+ */
28
+ class FileRef final {
29
+ std::unique_ptr<TagLib::FileRef> fileRef;
30
+ std::unique_ptr<IOStream> stream;
31
+
32
+ public:
33
+ /** @!yard
34
+ # Create a FileRef from a file or stream
35
+ # @param [String|:to_path|IO] file_or_stream
36
+ # A file name or an io like object responding to :read, :seek and :tell
37
+ # @param [Symbol<:average,:fast, :accurate>|nil] read_audio_properties
38
+ # :fast, :accurate, :average indicator for reading audio properties. nil/false to skip
39
+ def initialize(file_or_stream, read_audio_properties = nil); end
40
+ */
41
+ explicit FileRef(Object fileOrStream, Object readAudioProperties = Qnil);
42
+
43
+ ~FileRef() = default;
44
+
45
+ // Prevent copying
46
+ FileRef(const FileRef &) = delete;
47
+
48
+ FileRef &operator=(const FileRef &) = delete;
49
+
50
+ /** @!yard
51
+ # release file descriptors opened and held by TagLib
52
+ # @note does not close the input IO object (since we didn't open it)
53
+ # @return [void]
54
+ def close(); end
55
+ */
56
+ void close();
57
+
58
+ /** @!yard
59
+ # Underlying stream is open for reading
60
+ # @return [Boolean]
61
+ def valid?; end
62
+ */
63
+ bool isValid() const;
64
+
65
+ /** @!yard
66
+ # Is the underlying stream readonly (ie cannot update tags etc...)
67
+ # @return [Boolean]
68
+ def read_only?; end
69
+ */
70
+ bool isReadOnly() const;
71
+
72
+ /** @!yard
73
+ # @!group Reading Properties
74
+
75
+ # @return [AudioProperties] properties of the audio stream
76
+ # @return [nil] audio properties were not requested at {#initialize}
77
+ def audio_properties; end
78
+ */
79
+ Object audioProperties() const;
80
+
81
+ /** @!yard
82
+ # @return [AudioTag] normalised subset of well known tags
83
+ def tag; end
84
+ */
85
+ Object tag() const;
86
+
87
+ /** @!yard
88
+ # @return [Hash<String, Array<String>>] arbitrary String properties
89
+ def properties; end
90
+ */
91
+ Hash properties() const;
92
+
93
+ /** @!yard
94
+ # Retrieve a complex property
95
+ # @param [String] key the complex property to retrieve
96
+ # @return [Array<Hash<String>>] a list of complex property values for this key
97
+ # empty if the property does not exist
98
+ # @since TagLib 2.x
99
+ def complex_property(key); end
100
+ */
101
+ Array complexProperty(Rice::String key) const;
102
+
103
+ /** @!yard
104
+ # @return [Array<String>] list of complex properties available in this stream
105
+ # @since TagLib 2.x
106
+ def complex_property_keys; end
107
+ */
108
+ Array complexPropertyKeys() const;
109
+
110
+ /** @!yard
111
+ # @!endgroup
112
+ # @!group Writing Properties
113
+
114
+ # @param [Hash<String,Array<String>>] props input properties to merge
115
+ # @param [Boolean] replace_all true will clear all existing properties, otherwise the input Hash
116
+ # is merged with existing properties
117
+ # @return [void]
118
+ def merge_properties(props, replace_all = false); end
119
+ */
120
+ void mergeProperties(Hash props, bool replace_all = false) const;
121
+
122
+ /** @!yard
123
+ # @param [Hash<Symbol, String|Integer>|AudioTag] props input tag properties to merge.
124
+ # keys must be a subset of {AudioTag} members
125
+ # @return [void]
126
+ def merge_tag_properties(props); end
127
+ */
128
+ void mergeTagProperties(Object props) const;
129
+
130
+ /** @!yard
131
+ # @!method merge_complex_properties(props, replace_all)
132
+ # @param [Hash<String,Array<Hash<String,Object>>>] props Map of complex property name to new list of
133
+ # complex property values
134
+ # @param [Boolean] replace_all true will clear all existing complex properties before merging
135
+ # @since TagLib 2.x
136
+ # @return [void]
137
+
138
+ # @!endgroup
139
+ */
140
+ void mergeComplexProperties(Hash in, bool replace_all = false) const;
141
+
142
+ Rice::String toString() const;
143
+
144
+ Rice::String inspect() const;
145
+
146
+ /** @!yard
147
+ # Save updates back to the underlying file or stream
148
+ # @return [void]
149
+ def save; end
150
+ */
151
+ void save() const;
152
+
153
+ private:
154
+ void raiseInvalid() const;
155
+ };
156
+
157
+ //@!yard end # FileRef
158
+ }
159
+
160
+ //@!yard end # Simple
161
+ }
162
+
163
+ //@!yard end # TagLib
164
+ void define_taglib_simple_fileref(const Module &rb_mTagLibRuby);
@@ -0,0 +1,148 @@
1
+
2
+ #include "IOStream.hpp"
3
+ #include <rice/rice.hpp>
4
+ #include <utility>
5
+ #include <taglib/tiostream.h>
6
+ #include <taglib/tbytevector.h>
7
+ #include <ruby/io.h>
8
+
9
+ using namespace Rice;
10
+ namespace TagLib {
11
+ namespace Simple {
12
+
13
+ IOStream::IOStream(Object ruby_io) : io(std::move(ruby_io)) {
14
+ // Only things like File that have a writable? method are considered writable
15
+ // There are techniques to use write-nonblock etc to do this, but callers using custom streams
16
+ // will have to work that out themselves
17
+ openReadOnly = ruby_io.respond_to("writable?") && ruby_io.call("writable?").test();
18
+ };
19
+
20
+ IOStream::~IOStream() = default;
21
+
22
+ bool IOStream::isIO(const Object& io) {
23
+ return io.respond_to("tell") && io.respond_to("seek") && io.respond_to("read");
24
+ }
25
+
26
+ TagLib::FileName IOStream::name() const {
27
+ return io.to_s().c_str();
28
+ }
29
+
30
+ TagLib::ByteVector IOStream::readBlock(unsigned long length) {
31
+ // Call read method on Ruby IO object
32
+ Object result = io.call("read", (long)length);
33
+
34
+ if (result.is_nil()) {
35
+ return {};
36
+ }
37
+
38
+ Rice::String str = Rice::String(result);
39
+ return {str.c_str(), static_cast<unsigned int>(str.length())};
40
+ }
41
+
42
+
43
+ void IOStream::writeBlock(const TagLib::ByteVector &data) {
44
+ // Convert ByteVector to Ruby string and write
45
+ std::string str(data.data(), data.size());
46
+ io.call("write", Rice::String(str));
47
+ }
48
+
49
+ void IOStream::insert(const TagLib::ByteVector &data, v1_unsigned_offset_type start, size_type replace) {
50
+ seek(start, Beginning);
51
+ // If replacing content, first read the content after the replace section
52
+ Rice::String remainder;
53
+ if (replace > 0) {
54
+ seek(static_cast<offset_type>(replace), Current);
55
+ remainder = io.call("read");
56
+ }
57
+
58
+ // Seek back and write new data
59
+ seek(start, Beginning);
60
+ writeBlock(data);
61
+
62
+ // Write remaining content if any
63
+ if (!remainder.is_nil()) {
64
+ // Write the Ruby string directly back to the IO
65
+ (void)io.call("write", remainder);
66
+ }
67
+
68
+ size_type new_length = start + data.size();
69
+ if (!remainder.is_nil()) {
70
+ new_length += remainder.length();
71
+ }
72
+
73
+ truncate(static_cast<offset_type>(new_length));
74
+ }
75
+
76
+ void IOStream::removeBlock(const v1_unsigned_offset_type start, const size_type length) {
77
+ // Read the content after the section to remove
78
+ seek(static_cast<offset_type>(start + length), Beginning);
79
+ Rice::String remainder = io.call("read");
80
+
81
+ // Seek back to start position
82
+ seek(start, Beginning);
83
+
84
+ // Write the remaining content if any
85
+ if (!remainder.is_nil()) {
86
+ (void) io.call("write", remainder);
87
+ }
88
+
89
+ truncate(static_cast<long>(start + remainder.length()));
90
+ }
91
+
92
+ bool IOStream::readOnly() const {
93
+ return openReadOnly;
94
+ }
95
+ bool IOStream::isOpen() const {
96
+ // Check if the file is open using Ruby's closed? method
97
+ return !io.call("closed?").test();
98
+ }
99
+
100
+ void IOStream::seek(offset_type offset, Position p) {
101
+ int whence;
102
+ switch(p) {
103
+ case Beginning:
104
+ whence = SEEK_SET;
105
+ break;
106
+ case Current:
107
+ whence = SEEK_CUR;
108
+ break;
109
+ case End:
110
+ whence = SEEK_END;
111
+ break;
112
+ default:
113
+ whence = SEEK_SET;
114
+ }
115
+
116
+ (void) io.call("seek", offset, whence);
117
+ }
118
+
119
+ offset_type IOStream::tell() const {
120
+ return NUM2LONG(io.call("tell"));
121
+ }
122
+
123
+ offset_type IOStream::length() {
124
+ // Store current position
125
+ const offset_type current = tell();
126
+
127
+ // Seek to end to get length
128
+ seek(0, End);
129
+ const offset_type file_length = tell();
130
+
131
+ // Restore original position
132
+ seek(current, Beginning);
133
+
134
+ return file_length;
135
+ }
136
+
137
+ void IOStream::clear() {
138
+ //nothing to do on ruby IO
139
+ }
140
+
141
+ // Truncate if necessary
142
+ // TODO: Truncate is defined on file, but not on IO
143
+ // but other kinds of streams are not rewritable like this anyway.
144
+ void IOStream::truncate(offset_type length) {
145
+ (void) io.call("truncate", length);
146
+ }
147
+ }
148
+ }
@@ -0,0 +1,48 @@
1
+ #pragma once
2
+
3
+ #include "taglib_wrap.h"
4
+ #include <taglib/tiostream.h>
5
+ #include <rice/rice.hpp>
6
+
7
+ using namespace Rice;
8
+
9
+ namespace TagLib {
10
+ namespace Simple {
11
+
12
+ #if (TAGLIB_MAJOR_VERSION >= 2)
13
+ // V2 looks to have aligned with posix
14
+ using offset_type = offset_t;
15
+ using size_type = size_t;
16
+ // V1 had some unsigned offsets
17
+ using v1_unsigned_offset_type = offset_t;
18
+ #else
19
+ using offset_type = long;
20
+ using size_type = unsigned long;
21
+ using v1_unsigned_offset_type = unsigned long;
22
+ #endif
23
+
24
+
25
+ // An TagLib::IOStream from Ruby IO
26
+ class IOStream final : public TagLib::IOStream {
27
+ Object io;
28
+ public:
29
+ static bool isIO(const Object& io);
30
+ bool openReadOnly;
31
+ explicit IOStream(Object ruby_io);
32
+
33
+ ~IOStream() override;
34
+ FileName name() const override;
35
+ ByteVector readBlock(unsigned long length) override;
36
+ void writeBlock(const ByteVector &data) override;
37
+ void insert(const ByteVector &data, v1_unsigned_offset_type start, size_type replace) override;
38
+ void removeBlock(v1_unsigned_offset_type start, size_type length) override;
39
+ void seek(offset_type offset, Position p) override;
40
+ void clear() override;
41
+ offset_type tell() const override;
42
+ offset_type length() override;
43
+ void truncate(offset_type length) override;
44
+ bool isOpen() const override;
45
+ bool readOnly() const override;
46
+ };
47
+ } // Ruby
48
+ } // TagLib
@@ -0,0 +1,38 @@
1
+ // Type conversations from ruby to taglib
2
+ #pragma once
3
+
4
+ #include "taglib_wrap.h"
5
+ #include <rice/rice.hpp>
6
+ #include <ruby/encoding.h>
7
+ #include <taglib/audioproperties.h>
8
+ #include <taglib/tstringlist.h>
9
+ #if (TAGLIB_MAJOR_VERSION >=2)
10
+ #include <taglib/tvariant.h>
11
+ #endif
12
+
13
+ #include <taglib/tpropertymap.h>
14
+
15
+ using namespace Rice;
16
+
17
+ namespace TagLib {
18
+ namespace Simple {
19
+ // taglib to ruby
20
+ Rice::Object tagLibStringToNonEmptyRubyUTF8String(TagLib::String string);
21
+ Rice::Object uintToNonZeroRubyInteger(unsigned integer);
22
+ Rice::Hash tagLibPropertyMapToRubyHash(const TagLib::PropertyMap& properties);
23
+ Array tagLibStringListToRuby(const TagLib::StringList& list);
24
+ Rice::String tagLibStringToRubyUTF8String(const TagLib::String& str);
25
+
26
+ // ruby to taglib
27
+ TagLib::String rubyStringOrNilToTagLibString(Object value);
28
+ unsigned int rubyIntegerOrNilToUInt(Object value);
29
+ TagLib::String rubyStringToTagLibString(const Rice::String& str);
30
+ TagLib::AudioProperties::ReadStyle rubyObjectToTagLibAudioPropertiesReadStyle(const Rice::Object& readStyle);
31
+ TagLib::StringList rubyObjectToTagLibStringList(const Rice::Object& obj);
32
+
33
+ #if (TAGLIB_MAJOR_VERSION >= 2)
34
+ Rice::Array tagLibComplexPropertyToRuby(const TagLib::List<TagLib::VariantMap>& list);
35
+ TagLib::List<TagLib::VariantMap> rubyObjectToTagLibComplexProperty(const Rice::Object& obj);
36
+ #endif
37
+ }
38
+ }
@@ -0,0 +1,205 @@
1
+ #include "conversions.h"
2
+
3
+ using namespace Rice;
4
+
5
+ namespace TagLib {
6
+ namespace Simple {
7
+
8
+ TagLib::AudioProperties::ReadStyle rubyObjectToTagLibAudioPropertiesReadStyle(const Object& readStyle) {
9
+ if (!readStyle.test()) {
10
+ return TagLib::AudioProperties::Average;
11
+ }
12
+ const Symbol readStyleSym = {readStyle};
13
+ if (readStyleSym.str() == "fast") {
14
+ return TagLib::AudioProperties::Fast;
15
+ }
16
+ if (readStyleSym.str() == "accurate") {
17
+ return TagLib::AudioProperties::Accurate;
18
+ }
19
+ if (readStyleSym.str() == "average") {
20
+ return TagLib::AudioProperties::Average;
21
+ }
22
+ throw Rice::Exception(rb_eArgError, "Invalid read style: %s", readStyleSym.str());
23
+ }
24
+
25
+ String rubyStringOrNilToTagLibString(Object value) {
26
+ if (value.is_nil()) {
27
+ return { "", String::UTF8 };
28
+ }
29
+ return rubyStringToTagLibString(value);
30
+ }
31
+
32
+ unsigned int rubyIntegerOrNilToUInt(Object value) {
33
+
34
+ if (value.is_nil()) {
35
+ return 0;
36
+ }
37
+ return NUM2UINT(value);
38
+ }
39
+
40
+ static const rb_encoding* const UTF16LE_ENCODING = rb_enc_find("UTF-16LE");
41
+ static const rb_encoding* const UTF16BE_ENCODING = rb_enc_find("UTF-16BE");
42
+ static const rb_encoding* const UTF16_ENCODING = rb_enc_find("UTF-16");
43
+ static const rb_encoding* const LATIN1_ENCODING = rb_enc_find("ISO-8859-1");
44
+
45
+
46
+ TagLib::String rubyStringToTagLibString(const Rice::String& str) {
47
+ rb_encoding* enc = rb_enc_get(str);
48
+ if (enc == rb_utf8_encoding()) {
49
+ return { str.c_str(), TagLib::String::UTF8 };
50
+ } else if (enc == rb_ascii8bit_encoding() || enc == rb_usascii_encoding()) {
51
+ return { str.c_str(), TagLib::String::UTF8 };
52
+ } else if (enc == LATIN1_ENCODING) {
53
+ return { str.c_str(), TagLib::String::Latin1 };
54
+ } else if (enc == UTF16LE_ENCODING) {
55
+ return { str.c_str(), TagLib::String::UTF16LE };
56
+ } else if (enc == UTF16BE_ENCODING) {
57
+ return { str.c_str(), TagLib::String::UTF16BE };
58
+ } else {
59
+ // For any other encoding, convert to UTF-8 first
60
+ Rice::String utf8_str = rb_str_export_to_enc(str, rb_utf8_encoding());
61
+ return { utf8_str.c_str(), TagLib::String::UTF8 };
62
+ }
63
+ }
64
+
65
+ TagLib::StringList rubyObjectToTagLibStringList(const Object& obj) {
66
+ TagLib::StringList string_list;
67
+
68
+ // Handle both String and Array values
69
+ if (TYPE(obj) == T_ARRAY) {
70
+ Array val_array(obj);
71
+ if (val_array.size() > 0) {
72
+ for (auto it = val_array.begin(); it != val_array.end(); ++it) {
73
+ Rice::String item(it->value());
74
+ string_list.append(TagLib::String(item.c_str()));
75
+ }
76
+ }
77
+ } else {
78
+ string_list.append(Rice::String(obj).c_str());
79
+ }
80
+ return string_list;
81
+ }
82
+
83
+ bool isBinaryEncoding(const Rice::String& str) {
84
+ return rb_enc_get(str) == rb_ascii8bit_encoding();
85
+ }
86
+
87
+ TagLib::StringList rubyArrayToTagLibStringList(Array arr) {
88
+ TagLib::StringList stringList;
89
+ for (const auto& item : arr) {
90
+ Rice::String str = {item.value() };
91
+ stringList.append(TagLib::String(str.c_str(), TagLib::String::UTF8));
92
+ }
93
+ return stringList;
94
+ }
95
+
96
+ TagLib::ByteVectorList rubyArrayToTagLibByteVectorList(Array arr) {
97
+ TagLib::ByteVectorList list;
98
+ for (const auto& item : arr) {
99
+ Rice::String str = {item.value() };
100
+ list.append(TagLib::ByteVector(str.c_str(), str.length()));
101
+ }
102
+ return list;
103
+ }
104
+
105
+ #if (TAGLIB_MAJOR_VERSION >= 2)
106
+ TagLib::Variant rubyObjectToTagLibVariant(Object &obj);
107
+
108
+ // This can be a StringList, a ByteVectorList or a List<Variant>
109
+ TagLib::Variant rubyArrayToTagLibVariant(Array arr) {
110
+ if (arr.size() == 0) {
111
+ return {};
112
+ }
113
+ Object first = arr[0];
114
+
115
+ // Check only first element to determine list type
116
+ if (first.is_a(rb_cString)) {
117
+ Rice::String first_str = Rice::String(first);
118
+ if (isBinaryEncoding(first_str)) {
119
+ return { rubyArrayToTagLibByteVectorList(arr) };
120
+ }
121
+ return { rubyArrayToTagLibStringList(arr) };
122
+ }
123
+
124
+ TagLib::List<TagLib::Variant> variantList;
125
+ for (const auto& item : arr) {
126
+ Rice::Object obj = { item.value() };
127
+ variantList.append(rubyObjectToTagLibVariant(obj));
128
+ }
129
+ return { variantList };
130
+ }
131
+
132
+
133
+ TagLib::Variant rubyStringToTaglibVariant(const Rice::String& str) {
134
+ if (isBinaryEncoding(str)) {
135
+ // Binary string - convert to ByteVector
136
+ return { TagLib::ByteVector(str.c_str(), str.length()) };
137
+ } else {
138
+ // Assume UTF-8 for all other encodings
139
+ return { TagLib::String(str.c_str(), TagLib::String::UTF8) };
140
+ }
141
+ }
142
+
143
+ TagLib::VariantMap rubyHashToTagLibVariantMap(const Hash& hash) {
144
+ TagLib::VariantMap result;
145
+
146
+ for (auto it = hash.begin(); it != hash.end(); ++it) {
147
+ TagLib::String key(Rice::String(it->first).c_str());
148
+ Rice::Object obj(it->second);
149
+ result.insert(key, rubyObjectToTagLibVariant(obj));
150
+ }
151
+
152
+ return result;
153
+ }
154
+
155
+ TagLib::Variant rubyObjectToTagLibVariant(Object &obj) {
156
+ if (obj.is_nil()) {
157
+ return {};
158
+ }
159
+
160
+ if (obj.is_a(rb_cInteger)) {
161
+ return {NUM2LL(obj.value())};
162
+ }
163
+
164
+ if (obj.is_a(rb_cString)) {
165
+ return rubyStringToTaglibVariant(Rice::String(obj));
166
+ }
167
+
168
+ if (obj.is_a(rb_cArray)) {
169
+ return rubyArrayToTagLibVariant(Array(obj));
170
+ }
171
+
172
+ if (obj.is_a(rb_cHash)) {
173
+ return { rubyHashToTagLibVariantMap(Hash(obj)) };
174
+ }
175
+
176
+ if (obj.value() == Qtrue) {
177
+ return {true};
178
+ }
179
+
180
+ if (obj.value() == Qfalse) {
181
+ return {false};
182
+ }
183
+
184
+ return {};
185
+ }
186
+
187
+ // This is used to set complex properties where the input must be Array<Hash>
188
+ TagLib::List<TagLib::VariantMap> rubyObjectToTagLibComplexProperty(const Object& obj) {
189
+
190
+ if (!obj.test()) {
191
+ return {};
192
+ }
193
+
194
+ Array list(obj);
195
+ TagLib::List<TagLib::VariantMap> result;
196
+ for (auto it = list.begin(); it != list.end(); ++it) {
197
+ result.append(rubyHashToTagLibVariantMap(Hash(it->value())));
198
+ }
199
+
200
+ return result;
201
+ }
202
+ #endif
203
+
204
+ }
205
+ }