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 <errno.h>
24#include <stdio.h> /* for snprintf() */
25#include <string.h>
26#include "options.h"
27#include "utils.h"
28#include "FLAC/assert.h"
29#include "share/grabbag.h"
30#include "operations_shorthand.h"
31
32static FLAC__bool import_cs_from(const char *filename, FLAC__StreamMetadata **cuesheet, const char *cs_filename, FLAC__bool *needs_write, FLAC__uint64 lead_out_offset, FLAC__bool is_cdda, Argument_AddSeekpoint *add_seekpoint_link);
33static FLAC__bool export_cs_to(const char *filename, const FLAC__StreamMetadata *cuesheet, const char *cs_filename);
34
35FLAC__bool do_shorthand_operation__cuesheet(const char *filename, FLAC__Metadata_Chain *chain, const Operation *operation, FLAC__bool *needs_write)
36{
37	FLAC__bool ok = true;
38	FLAC__StreamMetadata *cuesheet = 0;
39	FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new();
40	FLAC__uint64 lead_out_offset = 0;
41	FLAC__bool is_cdda = false;
42
43	if(0 == iterator)
44		die("out of memory allocating iterator");
45
46	FLAC__metadata_iterator_init(iterator, chain);
47
48	do {
49		FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(iterator);
50		if(block->type == FLAC__METADATA_TYPE_STREAMINFO) {
51			lead_out_offset = block->data.stream_info.total_samples;
52			if(lead_out_offset == 0) {
53				fprintf(stderr, "%s: ERROR: FLAC file must have total_samples set in STREAMINFO in order to import/export cuesheet\n", filename);
54				FLAC__metadata_iterator_delete(iterator);
55				return false;
56			}
57			is_cdda = (block->data.stream_info.channels == 1 || block->data.stream_info.channels == 2) && (block->data.stream_info.bits_per_sample == 16) && (block->data.stream_info.sample_rate == 44100);
58		}
59		else if(block->type == FLAC__METADATA_TYPE_CUESHEET)
60			cuesheet = block;
61	} while(FLAC__metadata_iterator_next(iterator));
62
63	if(lead_out_offset == 0) {
64		fprintf(stderr, "%s: ERROR: FLAC stream has no STREAMINFO block\n", filename);
65		FLAC__metadata_iterator_delete(iterator);
66		return false;
67	}
68
69	switch(operation->type) {
70		case OP__IMPORT_CUESHEET_FROM:
71			if(0 != cuesheet) {
72				fprintf(stderr, "%s: ERROR: FLAC file already has CUESHEET block\n", filename);
73				ok = false;
74			}
75			else {
76				ok = import_cs_from(filename, &cuesheet, operation->argument.import_cuesheet_from.filename, needs_write, lead_out_offset, is_cdda, operation->argument.import_cuesheet_from.add_seekpoint_link);
77				if(ok) {
78					/* append CUESHEET block */
79					while(FLAC__metadata_iterator_next(iterator))
80						;
81					if(!FLAC__metadata_iterator_insert_block_after(iterator, cuesheet)) {
82						print_error_with_chain_status(chain, "%s: ERROR: adding new CUESHEET block to metadata", filename);
83						FLAC__metadata_object_delete(cuesheet);
84						ok = false;
85					}
86				}
87			}
88			break;
89		case OP__EXPORT_CUESHEET_TO:
90			if(0 == cuesheet) {
91				fprintf(stderr, "%s: ERROR: FLAC file has no CUESHEET block\n", filename);
92				ok = false;
93			}
94			else
95				ok = export_cs_to(filename, cuesheet, operation->argument.filename.value);
96			break;
97		default:
98			ok = false;
99			FLAC__ASSERT(0);
100			break;
101	};
102
103	FLAC__metadata_iterator_delete(iterator);
104	return ok;
105}
106
107/*
108 * local routines
109 */
110
111FLAC__bool import_cs_from(const char *filename, FLAC__StreamMetadata **cuesheet, const char *cs_filename, FLAC__bool *needs_write, FLAC__uint64 lead_out_offset, FLAC__bool is_cdda, Argument_AddSeekpoint *add_seekpoint_link)
112{
113	FILE *f;
114	const char *error_message;
115	char **seekpoint_specification = add_seekpoint_link? &(add_seekpoint_link->specification) : 0;
116	unsigned last_line_read;
117
118	if(0 == cs_filename || strlen(cs_filename) == 0) {
119		fprintf(stderr, "%s: ERROR: empty import file name\n", filename);
120		return false;
121	}
122	if(0 == strcmp(cs_filename, "-"))
123		f = stdin;
124	else
125		f = fopen(cs_filename, "r");
126
127	if(0 == f) {
128		fprintf(stderr, "%s: ERROR: can't open import file %s: %s\n", filename, cs_filename, strerror(errno));
129		return false;
130	}
131
132	*cuesheet = grabbag__cuesheet_parse(f, &error_message, &last_line_read, is_cdda, lead_out_offset);
133
134	if(f != stdin)
135		fclose(f);
136
137	if(0 == *cuesheet) {
138		fprintf(stderr, "%s: ERROR: while parsing cuesheet \"%s\" on line %u: %s\n", filename, cs_filename, last_line_read, error_message);
139		return false;
140	}
141
142	if(!FLAC__format_cuesheet_is_legal(&(*cuesheet)->data.cue_sheet, /*check_cd_da_subset=*/false, &error_message)) {
143		fprintf(stderr, "%s: ERROR parsing cuesheet \"%s\": %s\n", filename, cs_filename, error_message);
144		return false;
145	}
146
147	/* if we're expecting CDDA, warn about non-compliance */
148	if(is_cdda && !FLAC__format_cuesheet_is_legal(&(*cuesheet)->data.cue_sheet, /*check_cd_da_subset=*/true, &error_message)) {
149		fprintf(stderr, "%s: WARNING cuesheet \"%s\" is not audio CD compliant: %s\n", filename, cs_filename, error_message);
150		(*cuesheet)->data.cue_sheet.is_cd = false;
151	}
152
153	/* add seekpoints for each index point if required */
154	if(0 != seekpoint_specification) {
155		char spec[128];
156		unsigned track, index;
157		const FLAC__StreamMetadata_CueSheet *cs = &(*cuesheet)->data.cue_sheet;
158		if(0 == *seekpoint_specification)
159			*seekpoint_specification = local_strdup("");
160		for(track = 0; track < cs->num_tracks; track++) {
161			const FLAC__StreamMetadata_CueSheet_Track *tr = cs->tracks+track;
162			for(index = 0; index < tr->num_indices; index++) {
163#ifdef _MSC_VER
164				sprintf(spec, "%I64u;", tr->offset + tr->indices[index].offset);
165#else
166				sprintf(spec, "%llu;", (unsigned long long)(tr->offset + tr->indices[index].offset));
167#endif
168				local_strcat(seekpoint_specification, spec);
169			}
170		}
171	}
172
173	*needs_write = true;
174	return true;
175}
176
177FLAC__bool export_cs_to(const char *filename, const FLAC__StreamMetadata *cuesheet, const char *cs_filename)
178{
179	FILE *f;
180	char *ref = 0;
181	size_t reflen;
182
183	if(0 == cs_filename || strlen(cs_filename) == 0) {
184		fprintf(stderr, "%s: ERROR: empty export file name\n", filename);
185		return false;
186	}
187	if(0 == strcmp(cs_filename, "-"))
188		f = stdout;
189	else
190		f = fopen(cs_filename, "w");
191
192	if(0 == f) {
193		fprintf(stderr, "%s: ERROR: can't open export file %s: %s\n", filename, cs_filename, strerror(errno));
194		return false;
195	}
196
197	reflen = strlen(filename) + 7 + 1;
198	if(0 == (ref = malloc(reflen))) {
199		fprintf(stderr, "%s: ERROR: allocating memory\n", filename);
200		return false;
201	}
202
203#if defined _MSC_VER || defined __MINGW32__
204	_snprintf(ref, reflen, "\"%s\" FLAC", filename);
205#else
206	snprintf(ref, reflen, "\"%s\" FLAC", filename);
207#endif
208
209	grabbag__cuesheet_emit(f, cuesheet, ref);
210
211	free(ref);
212
213	if(f != stdout)
214		fclose(f);
215
216	return true;
217}
218