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