Skip to content

Commit c817cc0

Browse files
committed
tagwriter: Support setting of complex properties
A complex property can be set with -C <complex-property-key> <key1=val1,key2=val2,...> The second parameter can be set to "" to delete complex properties with the given key. The set complex property values, a simple shorthand syntax can be used. Multiple maps are separated by ';', values within a map are assigned with key=value and separated by a ','. Types are automatically detected, double quotes can be used to force a string. A ByteVector can be constructed from the contents of a file with the path is given after "file://". There is no escape, but hex codes are supported, e.g. "\x2C" to include a ',' and \x3B to include a ';'. Examples: Set a GEOB frame in an ID3v2 tag: examples/tagwriter -C GENERALOBJECT \ 'data=file://file.bin,description=My description,fileName=file.bin,mimeType=application/octet-stream' \ file.mp3 Set an APIC frame in an ID3v2 tag (same as -p file.jpg 'My description'): examples/tagwriter -C PICTURE \ 'data=file://file.jpg,description=My description,pictureType=Front Cover,mimeType=image/jpeg' \ file.mp3 Set an attached file in a Matroska file: examples/tagwriter -C file.bin \ 'fileName=file.bin,data=file://file.bin,mimeType=application/octet-stream' \ file.mka Set simple tag with target type in a Matroska file: examples/tagwriter -C PART_NUMBER \ name=PART_NUMBER,targetTypeValue=20,value=2 file.mka Set simple tag with binary value in a Matroska file: examples/tagwriter -C BINARY \ name=BINARY,data=file://file.bin,targetTypeValue=60 file.mka
1 parent 7a5a101 commit c817cc0

1 file changed

Lines changed: 110 additions & 0 deletions

File tree

examples/tagwriter.cpp

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ void usage()
7171
std::cout << " -R <tagname> <tagvalue>" << std::endl;
7272
std::cout << " -I <tagname> <tagvalue>" << std::endl;
7373
std::cout << " -D <tagname>" << std::endl;
74+
std::cout << " -C <complex-property-key> <key1=val1,key2=val2,...>" << std::endl;
7475
std::cout << " -p <picturefile> <description> (\"\" \"\" to remove)" << std::endl;
7576
std::cout << std::endl;
7677

@@ -95,6 +96,102 @@ void checkForRejectedProperties(const TagLib::PropertyMap &tags)
9596
}
9697
}
9798

99+
/*!
100+
* Create a list of variant maps from a string.
101+
* The shorthand syntax in the string is kept simple, but should be sufficient
102+
* for testing. Multiple maps are separated by ';', values within a map are
103+
* assigned with key=value and separated by a ','. Types are detected, use
104+
* double quotes to force a string. A ByteVector can be constructed from the
105+
* contents of a file, the path is given after "file://". There is no escape
106+
* character, use hex codes for ',' (\x2C) or ';' (\x3B).
107+
*/
108+
TagLib::List<TagLib::VariantMap> parseComplexPropertyValues(const TagLib::String &str)
109+
{
110+
if(str.isEmpty() || str == "\"\"" || str == "''") {
111+
return {};
112+
}
113+
TagLib::List<TagLib::VariantMap> values;
114+
const auto valueStrs = str.split(";");
115+
for(const auto &valueStr : valueStrs) {
116+
TagLib::VariantMap value;
117+
const auto keyValStrs = valueStr.split(",");
118+
for(const auto &keyValStr : keyValStrs) {
119+
if(int equalPos = keyValStr.find('='); equalPos != -1) {
120+
TagLib::String key = keyValStr.substr(0, equalPos);
121+
TagLib::String valStr = keyValStr.substr(equalPos + 1);
122+
bool hasDot = false;
123+
bool hasNonNumeric = false;
124+
bool hasSign = false;
125+
for(auto it = valStr.cbegin(); it != valStr.cend(); ++it) {
126+
if(it == valStr.cbegin() && (*it == '-' || *it == '+')) {
127+
hasSign = true;
128+
}
129+
else if(*it == '.') {
130+
hasDot = true;
131+
}
132+
else if(*it < '0' || *it > '9') {
133+
hasNonNumeric = true;
134+
}
135+
}
136+
TagLib::Variant val;
137+
if(valStr == "null") {
138+
// keep empty variant
139+
}
140+
else if(valStr == "true" || valStr == "false") {
141+
val = TagLib::Variant(valStr == "true");
142+
}
143+
else if(!hasNonNumeric && hasDot) {
144+
val = TagLib::Variant(std::stod(valStr.to8Bit()));
145+
}
146+
else if(!hasNonNumeric && hasSign) {
147+
val = valStr.toLongLong(nullptr);
148+
}
149+
else if(!hasNonNumeric) {
150+
val = valStr.toULongLong(nullptr);
151+
}
152+
else if(valStr.startsWith("file://")) {
153+
auto filePath = valStr.substr(7 );
154+
if(isFile(filePath.toCString())) {
155+
std::ifstream fs;
156+
fs.open(filePath.toCString(), std::ios::in | std::ios::binary);
157+
std::stringstream buffer;
158+
buffer << fs.rdbuf();
159+
fs.close();
160+
TagLib::String buf(buffer.str());
161+
val = TagLib::Variant(buf.data(TagLib::String::Latin1));
162+
}
163+
else {
164+
std::cout << filePath.toCString() << " not found." << std::endl;
165+
val = TagLib::Variant(TagLib::ByteVector());
166+
}
167+
}
168+
else {
169+
int len = valStr.size();
170+
if(len >= 2 && valStr[0] == '"' && valStr[len - 1] == '"') {
171+
valStr = valStr.substr(1, len - 2);
172+
}
173+
int hexPos = 0;
174+
while((hexPos = valStr.find("\\x", hexPos)) != -1) {
175+
char ch;
176+
bool ok;
177+
if(static_cast<int>(valStr.length()) < hexPos + 4 ||
178+
(ch = static_cast<char>(
179+
valStr.substr(hexPos + 2, 2).toLongLong(&ok, 16)), !ok)) {
180+
break;
181+
}
182+
valStr = valStr.substr(0, hexPos) + ch + valStr.substr(hexPos + 4);
183+
++hexPos;
184+
}
185+
val = TagLib::Variant(valStr);
186+
}
187+
value.insert(key, val);
188+
}
189+
}
190+
values.append(value);
191+
}
192+
return values;
193+
}
194+
98195
int main(int argc, char *argv[])
99196
{
100197
TagLib::List<TagLib::FileRef> fileList;
@@ -170,6 +267,19 @@ int main(int argc, char *argv[])
170267
checkForRejectedProperties(f.setProperties(map));
171268
break;
172269
}
270+
case 'C': {
271+
if(i + 2 < argc) {
272+
numArgsConsumed = 3;
273+
if(!value.isEmpty()) {
274+
TagLib::List<TagLib::VariantMap> values = parseComplexPropertyValues(argv[i + 2]);
275+
f.setComplexProperties(value, values);
276+
}
277+
}
278+
else {
279+
usage();
280+
}
281+
break;
282+
}
173283
case 'p': {
174284
if(i + 2 < argc) {
175285
numArgsConsumed = 3;

0 commit comments

Comments
 (0)