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