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