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,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
+ }