1/* metaflac - Command-line FLAC metadata editor 2 * Copyright (C) 2001,2002,2003,2004,2005,2006,2007 Josh Coalson 3 * 4 * This program is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU General Public License 6 * as published by the Free Software Foundation; either version 2 7 * of the License, or (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, write to the Free Software 16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 17 */ 18 19#if HAVE_CONFIG_H 20# include <config.h> 21#endif 22 23#include "options.h" 24#include "utils.h" 25#include "FLAC/assert.h" 26#include "share/grabbag.h" /* for grabbag__file_get_filesize() */ 27#include "share/utf8.h" 28#include <errno.h> 29#include <stdlib.h> 30#include <string.h> 31#include "operations_shorthand.h" 32 33static FLAC__bool remove_vc_all(const char *filename, FLAC__StreamMetadata *block, FLAC__bool *needs_write); 34static FLAC__bool remove_vc_field(const char *filename, FLAC__StreamMetadata *block, const char *field_name, FLAC__bool *needs_write); 35static FLAC__bool remove_vc_firstfield(const char *filename, FLAC__StreamMetadata *block, const char *field_name, FLAC__bool *needs_write); 36static FLAC__bool set_vc_field(const char *filename, FLAC__StreamMetadata *block, const Argument_VcField *field, FLAC__bool *needs_write, FLAC__bool raw); 37static FLAC__bool import_vc_from(const char *filename, FLAC__StreamMetadata *block, const Argument_String *vc_filename, FLAC__bool *needs_write, FLAC__bool raw); 38static FLAC__bool export_vc_to(const char *filename, FLAC__StreamMetadata *block, const Argument_String *vc_filename, FLAC__bool raw); 39 40FLAC__bool do_shorthand_operation__vorbis_comment(const char *filename, FLAC__bool prefix_with_filename, FLAC__Metadata_Chain *chain, const Operation *operation, FLAC__bool *needs_write, FLAC__bool raw) 41{ 42 FLAC__bool ok = true, found_vc_block = false; 43 FLAC__StreamMetadata *block = 0; 44 FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new(); 45 46 if(0 == iterator) 47 die("out of memory allocating iterator"); 48 49 FLAC__metadata_iterator_init(iterator, chain); 50 51 do { 52 block = FLAC__metadata_iterator_get_block(iterator); 53 if(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) 54 found_vc_block = true; 55 } while(!found_vc_block && FLAC__metadata_iterator_next(iterator)); 56 57 if(!found_vc_block) { 58 /* create a new block if necessary */ 59 if(operation->type == OP__SET_VC_FIELD || operation->type == OP__IMPORT_VC_FROM) { 60 block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); 61 if(0 == block) 62 die("out of memory allocating VORBIS_COMMENT block"); 63 while(FLAC__metadata_iterator_next(iterator)) 64 ; 65 if(!FLAC__metadata_iterator_insert_block_after(iterator, block)) { 66 print_error_with_chain_status(chain, "%s: ERROR: adding new VORBIS_COMMENT block to metadata", filename); 67 return false; 68 } 69 /* iterator is left pointing to new block */ 70 FLAC__ASSERT(FLAC__metadata_iterator_get_block(iterator) == block); 71 } 72 else { 73 FLAC__metadata_iterator_delete(iterator); 74 return ok; 75 } 76 } 77 78 FLAC__ASSERT(0 != block); 79 FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); 80 81 switch(operation->type) { 82 case OP__SHOW_VC_VENDOR: 83 write_vc_field(prefix_with_filename? filename : 0, &block->data.vorbis_comment.vendor_string, raw, stdout); 84 break; 85 case OP__SHOW_VC_FIELD: 86 write_vc_fields(prefix_with_filename? filename : 0, operation->argument.vc_field_name.value, block->data.vorbis_comment.comments, block->data.vorbis_comment.num_comments, raw, stdout); 87 break; 88 case OP__REMOVE_VC_ALL: 89 ok = remove_vc_all(filename, block, needs_write); 90 break; 91 case OP__REMOVE_VC_FIELD: 92 ok = remove_vc_field(filename, block, operation->argument.vc_field_name.value, needs_write); 93 break; 94 case OP__REMOVE_VC_FIRSTFIELD: 95 ok = remove_vc_firstfield(filename, block, operation->argument.vc_field_name.value, needs_write); 96 break; 97 case OP__SET_VC_FIELD: 98 ok = set_vc_field(filename, block, &operation->argument.vc_field, needs_write, raw); 99 break; 100 case OP__IMPORT_VC_FROM: 101 ok = import_vc_from(filename, block, &operation->argument.filename, needs_write, raw); 102 break; 103 case OP__EXPORT_VC_TO: 104 ok = export_vc_to(filename, block, &operation->argument.filename, raw); 105 break; 106 default: 107 ok = false; 108 FLAC__ASSERT(0); 109 break; 110 }; 111 112 FLAC__metadata_iterator_delete(iterator); 113 return ok; 114} 115 116/* 117 * local routines 118 */ 119 120FLAC__bool remove_vc_all(const char *filename, FLAC__StreamMetadata *block, FLAC__bool *needs_write) 121{ 122 FLAC__ASSERT(0 != block); 123 FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); 124 FLAC__ASSERT(0 != needs_write); 125 126 if(0 != block->data.vorbis_comment.comments) { 127 FLAC__ASSERT(block->data.vorbis_comment.num_comments > 0); 128 if(!FLAC__metadata_object_vorbiscomment_resize_comments(block, 0)) { 129 fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename); 130 return false; 131 } 132 *needs_write = true; 133 } 134 else { 135 FLAC__ASSERT(block->data.vorbis_comment.num_comments == 0); 136 } 137 138 return true; 139} 140 141FLAC__bool remove_vc_field(const char *filename, FLAC__StreamMetadata *block, const char *field_name, FLAC__bool *needs_write) 142{ 143 int n; 144 145 FLAC__ASSERT(0 != needs_write); 146 147 n = FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, field_name); 148 149 if(n < 0) { 150 fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename); 151 return false; 152 } 153 else if(n > 0) 154 *needs_write = true; 155 156 return true; 157} 158 159FLAC__bool remove_vc_firstfield(const char *filename, FLAC__StreamMetadata *block, const char *field_name, FLAC__bool *needs_write) 160{ 161 int n; 162 163 FLAC__ASSERT(0 != needs_write); 164 165 n = FLAC__metadata_object_vorbiscomment_remove_entry_matching(block, field_name); 166 167 if(n < 0) { 168 fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename); 169 return false; 170 } 171 else if(n > 0) 172 *needs_write = true; 173 174 return true; 175} 176 177FLAC__bool set_vc_field(const char *filename, FLAC__StreamMetadata *block, const Argument_VcField *field, FLAC__bool *needs_write, FLAC__bool raw) 178{ 179 FLAC__StreamMetadata_VorbisComment_Entry entry; 180 char *converted; 181 182 FLAC__ASSERT(0 != block); 183 FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); 184 FLAC__ASSERT(0 != field); 185 FLAC__ASSERT(0 != needs_write); 186 187 if(field->field_value_from_file) { 188 /* read the file into 'data' */ 189 FILE *f = 0; 190 char *data = 0; 191 const off_t size = grabbag__file_get_filesize(field->field_value); 192 if(size < 0) { 193 fprintf(stderr, "%s: ERROR: can't open file '%s' for '%s' tag value\n", filename, field->field_value, field->field_name); 194 return false; 195 } 196 if(size >= 0x100000) { /* magic arbitrary limit, actual format limit is near 16MB */ 197 fprintf(stderr, "%s: ERROR: file '%s' for '%s' tag value is too large\n", filename, field->field_value, field->field_name); 198 return false; 199 } 200 if(0 == (data = malloc(size+1))) 201 die("out of memory allocating tag value"); 202 data[size] = '\0'; 203 if(0 == (f = fopen(field->field_value, "rb")) || fread(data, 1, size, f) != (size_t)size) { 204 fprintf(stderr, "%s: ERROR: while reading file '%s' for '%s' tag value: %s\n", filename, field->field_value, field->field_name, strerror(errno)); 205 free(data); 206 if(f) 207 fclose(f); 208 return false; 209 } 210 fclose(f); 211 if(strlen(data) != (size_t)size) { 212 free(data); 213 fprintf(stderr, "%s: ERROR: file '%s' for '%s' tag value has embedded NULs\n", filename, field->field_value, field->field_name); 214 return false; 215 } 216 217 /* move 'data' into 'converted', converting to UTF-8 if necessary */ 218 if(raw) { 219 converted = data; 220 } 221 else if(utf8_encode(data, &converted) >= 0) { 222 free(data); 223 } 224 else { 225 free(data); 226 fprintf(stderr, "%s: ERROR: converting file '%s' contents to UTF-8 for tag value\n", filename, field->field_value); 227 return false; 228 } 229 230 /* create and entry and append it */ 231 if(!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, field->field_name, converted)) { 232 free(converted); 233 fprintf(stderr, "%s: ERROR: file '%s' for '%s' tag value is not valid UTF-8\n", filename, field->field_value, field->field_name); 234 return false; 235 } 236 free(converted); 237 if(!FLAC__metadata_object_vorbiscomment_append_comment(block, entry, /*copy=*/false)) { 238 fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename); 239 return false; 240 } 241 242 *needs_write = true; 243 return true; 244 } 245 else { 246 FLAC__bool needs_free = false; 247 if(raw) { 248 entry.entry = (FLAC__byte *)field->field; 249 } 250 else if(utf8_encode(field->field, &converted) >= 0) { 251 entry.entry = (FLAC__byte *)converted; 252 needs_free = true; 253 } 254 else { 255 fprintf(stderr, "%s: ERROR: converting comment '%s' to UTF-8\n", filename, field->field); 256 return false; 257 } 258 entry.length = strlen((const char *)entry.entry); 259 if(!FLAC__format_vorbiscomment_entry_is_legal(entry.entry, entry.length)) { 260 if(needs_free) 261 free(converted); 262 /* 263 * our previous parsing has already established that the field 264 * name is OK, so it must be the field value 265 */ 266 fprintf(stderr, "%s: ERROR: tag value for '%s' is not valid UTF-8\n", filename, field->field_name); 267 return false; 268 } 269 270 if(!FLAC__metadata_object_vorbiscomment_append_comment(block, entry, /*copy=*/true)) { 271 if(needs_free) 272 free(converted); 273 fprintf(stderr, "%s: ERROR: memory allocation failure\n", filename); 274 return false; 275 } 276 277 *needs_write = true; 278 if(needs_free) 279 free(converted); 280 return true; 281 } 282} 283 284FLAC__bool import_vc_from(const char *filename, FLAC__StreamMetadata *block, const Argument_String *vc_filename, FLAC__bool *needs_write, FLAC__bool raw) 285{ 286 FILE *f; 287 char line[65536]; 288 FLAC__bool ret; 289 290 if(0 == vc_filename->value || strlen(vc_filename->value) == 0) { 291 fprintf(stderr, "%s: ERROR: empty import file name\n", filename); 292 return false; 293 } 294 if(0 == strcmp(vc_filename->value, "-")) 295 f = stdin; 296 else 297 f = fopen(vc_filename->value, "r"); 298 299 if(0 == f) { 300 fprintf(stderr, "%s: ERROR: can't open import file %s: %s\n", filename, vc_filename->value, strerror(errno)); 301 return false; 302 } 303 304 ret = true; 305 while(ret && !feof(f)) { 306 fgets(line, sizeof(line), f); 307 if(!feof(f)) { 308 char *p = strchr(line, '\n'); 309 if(0 == p) { 310 fprintf(stderr, "%s: ERROR: line too long, aborting\n", vc_filename->value); 311 ret = false; 312 } 313 else { 314 const char *violation; 315 Argument_VcField field; 316 *p = '\0'; 317 memset(&field, 0, sizeof(Argument_VcField)); 318 field.field_value_from_file = false; 319 if(!parse_vorbis_comment_field(line, &field.field, &field.field_name, &field.field_value, &field.field_value_length, &violation)) { 320 FLAC__ASSERT(0 != violation); 321 fprintf(stderr, "%s: ERROR: malformed vorbis comment field \"%s\",\n %s\n", vc_filename->value, line, violation); 322 ret = false; 323 } 324 else { 325 ret = set_vc_field(filename, block, &field, needs_write, raw); 326 } 327 if(0 != field.field) 328 free(field.field); 329 if(0 != field.field_name) 330 free(field.field_name); 331 if(0 != field.field_value) 332 free(field.field_value); 333 } 334 } 335 }; 336 337 if(f != stdin) 338 fclose(f); 339 return ret; 340} 341 342FLAC__bool export_vc_to(const char *filename, FLAC__StreamMetadata *block, const Argument_String *vc_filename, FLAC__bool raw) 343{ 344 FILE *f; 345 FLAC__bool ret; 346 347 if(0 == vc_filename->value || strlen(vc_filename->value) == 0) { 348 fprintf(stderr, "%s: ERROR: empty export file name\n", filename); 349 return false; 350 } 351 if(0 == strcmp(vc_filename->value, "-")) 352 f = stdout; 353 else 354 f = fopen(vc_filename->value, "w"); 355 356 if(0 == f) { 357 fprintf(stderr, "%s: ERROR: can't open export file %s: %s\n", filename, vc_filename->value, strerror(errno)); 358 return false; 359 } 360 361 ret = true; 362 363 write_vc_fields(0, 0, block->data.vorbis_comment.comments, block->data.vorbis_comment.num_comments, raw, f); 364 365 if(f != stdout) 366 fclose(f); 367 return ret; 368} 369