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