FLAC__bool do_shorthand_operation__add_padding(const char *filename, FLAC__Metadata_Chain *chain, unsigned length, FLAC__bool *needs_write) { FLAC__StreamMetadata *padding = 0; FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new(); if(0 == iterator) die("out of memory allocating iterator"); FLAC__metadata_iterator_init(iterator, chain); while(FLAC__metadata_iterator_next(iterator)) ; padding = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING); if(0 == padding) die("out of memory allocating PADDING block"); padding->length = length; if(!FLAC__metadata_iterator_insert_block_after(iterator, padding)) { print_error_with_chain_status(chain, "%s: ERROR: adding new PADDING block to metadata", filename); FLAC__metadata_object_delete(padding); FLAC__metadata_iterator_delete(iterator); return false; } FLAC__metadata_iterator_delete(iterator); *needs_write = true; return true; }
static struct tag * oggflac_tag_dup(const char *file) { struct tag *ret = NULL; FLAC__Metadata_Iterator *it; FLAC__StreamMetadata *block; FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); if (!(FLAC__metadata_chain_read_ogg(chain, file))) goto out; it = FLAC__metadata_iterator_new(); FLAC__metadata_iterator_init(it, chain); ret = tag_new(); do { if (!(block = FLAC__metadata_iterator_get_block(it))) break; flac_tag_apply_metadata(ret, NULL, block); } while (FLAC__metadata_iterator_next(it)); FLAC__metadata_iterator_delete(it); if (!tag_is_defined(ret)) { tag_free(ret); ret = NULL; } out: FLAC__metadata_chain_delete(chain); return ret; }
FLAC__bool do_major_operation__list(const char *filename, FLAC__Metadata_Chain *chain, const CommandLineOptions *options) { FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new(); FLAC__StreamMetadata *block; FLAC__bool ok = true; unsigned block_number; if(0 == iterator) die("out of memory allocating iterator"); FLAC__metadata_iterator_init(iterator, chain); block_number = 0; do { block = FLAC__metadata_iterator_get_block(iterator); ok &= (0 != block); if(!ok) fprintf(stderr, "%s: ERROR: couldn't get block from chain\n", filename); else if(passes_filter(options, FLAC__metadata_iterator_get_block(iterator), block_number)) write_metadata(filename, block, block_number, !options->utf8_convert, options->application_data_format_is_hexdump); block_number++; } while(ok && FLAC__metadata_iterator_next(iterator)); FLAC__metadata_iterator_delete(iterator); return ok; }
static bool oggflac_scan_file(const char *file, const struct tag_handler *handler, void *handler_ctx) { FLAC__Metadata_Iterator *it; FLAC__StreamMetadata *block; FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); if (!(FLAC__metadata_chain_read_ogg(chain, file))) { FLAC__metadata_chain_delete(chain); return false; } it = FLAC__metadata_iterator_new(); FLAC__metadata_iterator_init(it, chain); do { if (!(block = FLAC__metadata_iterator_get_block(it))) break; flac_scan_metadata(NULL, block, handler, handler_ctx); } while (FLAC__metadata_iterator_next(it)); FLAC__metadata_iterator_delete(it); FLAC__metadata_chain_delete(chain); return true; }
FLAC__bool do_major_operation__remove(FLAC__Metadata_Chain *chain, const CommandLineOptions *options) { FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new(); FLAC__bool ok = true; unsigned block_number; if(0 == iterator) die("out of memory allocating iterator"); FLAC__metadata_iterator_init(iterator, chain); block_number = 0; while(ok && FLAC__metadata_iterator_next(iterator)) { block_number++; if(passes_filter(options, FLAC__metadata_iterator_get_block(iterator), block_number)) { ok &= FLAC__metadata_iterator_delete_block(iterator, options->use_padding); if(options->use_padding) ok &= FLAC__metadata_iterator_next(iterator); } } FLAC__metadata_iterator_delete(iterator); return ok; }
bool_t flac_update_song_tuple(const char *filename, VFSFile *fd, const Tuple *tuple) { AUDDBG("Update song tuple.\n"); FLAC__Metadata_Iterator *iter; FLAC__Metadata_Chain *chain; FLAC__StreamMetadata *vc_block; FLAC__Metadata_ChainStatus status; chain = FLAC__metadata_chain_new(); if (!FLAC__metadata_chain_read_with_callbacks(chain, fd, io_callbacks)) goto ERR; iter = FLAC__metadata_iterator_new(); FLAC__metadata_iterator_init(iter, chain); while (FLAC__metadata_iterator_next(iter)) if (FLAC__metadata_iterator_get_block_type(iter) == FLAC__METADATA_TYPE_VORBIS_COMMENT) { FLAC__metadata_iterator_delete_block(iter, true); break; } vc_block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); insert_str_tuple_to_vc(vc_block, tuple, FIELD_TITLE, "TITLE"); insert_str_tuple_to_vc(vc_block, tuple, FIELD_ARTIST, "ARTIST"); insert_str_tuple_to_vc(vc_block, tuple, FIELD_ALBUM, "ALBUM"); insert_str_tuple_to_vc(vc_block, tuple, FIELD_GENRE, "GENRE"); insert_str_tuple_to_vc(vc_block, tuple, FIELD_COMMENT, "COMMENT"); insert_str_tuple_to_vc(vc_block, tuple, FIELD_MBID, "musicbrainz_trackid"); insert_int_tuple_to_vc(vc_block, tuple, FIELD_YEAR, "DATE"); insert_int_tuple_to_vc(vc_block, tuple, FIELD_TRACK_NUMBER, "TRACKNUMBER"); FLAC__metadata_iterator_insert_block_after(iter, vc_block); FLAC__metadata_iterator_delete(iter); FLAC__metadata_chain_sort_padding(chain); if (!FLAC__metadata_chain_write_with_callbacks(chain, TRUE, fd, io_callbacks)) goto ERR; FLAC__metadata_chain_delete(chain); return TRUE; ERR: status = FLAC__metadata_chain_status(chain); FLAC__metadata_chain_delete(chain); FLACNG_ERROR("An error occured: %s\n", FLAC__Metadata_ChainStatusString[status]); return FALSE; }
static const char *store_to_file_pre_(const char *filename, FLAC__Metadata_Chain **chain, FLAC__StreamMetadata **block) { FLAC__Metadata_Iterator *iterator; const char *error; FLAC__bool found_vc_block = false; if(0 == (*chain = FLAC__metadata_chain_new())) return "memory allocation error"; if(!FLAC__metadata_chain_read(*chain, filename)) { error = FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(*chain)]; FLAC__metadata_chain_delete(*chain); return error; } if(0 == (iterator = FLAC__metadata_iterator_new())) { FLAC__metadata_chain_delete(*chain); return "memory allocation error"; } FLAC__metadata_iterator_init(iterator, *chain); do { *block = FLAC__metadata_iterator_get_block(iterator); if((*block)->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) found_vc_block = true; } while(!found_vc_block && FLAC__metadata_iterator_next(iterator)); if(!found_vc_block) { /* create a new block */ *block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); if(0 == *block) { FLAC__metadata_chain_delete(*chain); FLAC__metadata_iterator_delete(iterator); return "memory allocation error"; } while(FLAC__metadata_iterator_next(iterator)) ; if(!FLAC__metadata_iterator_insert_block_after(iterator, *block)) { error = FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(*chain)]; FLAC__metadata_chain_delete(*chain); FLAC__metadata_iterator_delete(iterator); return error; } /* iterator is left pointing to new block */ FLAC__ASSERT(FLAC__metadata_iterator_get_block(iterator) == *block); } FLAC__metadata_iterator_delete(iterator); FLAC__ASSERT(0 != *block); FLAC__ASSERT((*block)->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); return 0; }
bool_t flac_get_image(const char *filename, VFSFile *fd, void **data, int64_t *length) { AUDDBG("Probe for song image.\n"); FLAC__Metadata_Iterator *iter; FLAC__Metadata_Chain *chain; FLAC__StreamMetadata *metadata = NULL; FLAC__Metadata_ChainStatus status; bool_t has_image = FALSE; chain = FLAC__metadata_chain_new(); if (!FLAC__metadata_chain_read_with_callbacks(chain, fd, io_callbacks)) goto ERR; iter = FLAC__metadata_iterator_new(); FLAC__metadata_iterator_init(iter, chain); while (FLAC__metadata_iterator_next(iter)) if (FLAC__metadata_iterator_get_block_type(iter) == FLAC__METADATA_TYPE_PICTURE) break; if (FLAC__metadata_iterator_get_block_type(iter) == FLAC__METADATA_TYPE_PICTURE) { metadata = FLAC__metadata_iterator_get_block(iter); if (metadata->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER) { AUDDBG("FLAC__STREAM_METADATA_PICTURE_TYPE_FRONT_COVER found."); * data = malloc (metadata->data.picture.data_length); * length = metadata->data.picture.data_length; memcpy (* data, metadata->data.picture.data, * length); has_image = TRUE; } } FLAC__metadata_iterator_delete(iter); FLAC__metadata_chain_delete(chain); return has_image; ERR: status = FLAC__metadata_chain_status(chain); FLAC__metadata_chain_delete(chain); FLACNG_ERROR("An error occured: %s\n", FLAC__Metadata_ChainStatusString[status]); return FALSE; }
FLAC__bool do_major_operation__remove_all(FLAC__Metadata_Chain *chain, const CommandLineOptions *options) { FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new(); FLAC__bool ok = true; if(0 == iterator) die("out of memory allocating iterator"); FLAC__metadata_iterator_init(iterator, chain); while(ok && FLAC__metadata_iterator_next(iterator)) { ok &= FLAC__metadata_iterator_delete_block(iterator, options->use_padding); if(options->use_padding) ok &= FLAC__metadata_iterator_next(iterator); } FLAC__metadata_iterator_delete(iterator); return ok; }
Tuple *flac_probe_for_tuple(const char *filename, VFSFile *fd) { AUDDBG("Probe for tuple.\n"); Tuple *tuple = NULL; FLAC__Metadata_Iterator *iter; FLAC__Metadata_Chain *chain; FLAC__StreamMetadata *metadata = NULL; FLAC__Metadata_ChainStatus status; FLAC__StreamMetadata_VorbisComment_Entry *entry; char *key; char *value; tuple = tuple_new_from_filename(filename); tuple_set_str(tuple, FIELD_CODEC, NULL, "Free Lossless Audio Codec (FLAC)"); tuple_set_str(tuple, FIELD_QUALITY, NULL, _("lossless")); chain = FLAC__metadata_chain_new(); if (!FLAC__metadata_chain_read_with_callbacks(chain, fd, io_callbacks)) goto ERR; iter = FLAC__metadata_iterator_new(); FLAC__metadata_iterator_init(iter, chain); do { switch (FLAC__metadata_iterator_get_block_type(iter)) { case FLAC__METADATA_TYPE_VORBIS_COMMENT: if (FLAC__metadata_iterator_get_block_type(iter) == FLAC__METADATA_TYPE_VORBIS_COMMENT) { metadata = FLAC__metadata_iterator_get_block(iter); AUDDBG("Vorbis comment contains %d fields\n", metadata->data.vorbis_comment.num_comments); AUDDBG("Vendor string: %s\n", metadata->data.vorbis_comment.vendor_string.entry); entry = metadata->data.vorbis_comment.comments; for (int i = 0; i < metadata->data.vorbis_comment.num_comments; i++, entry++) { if (FLAC__metadata_object_vorbiscomment_entry_to_name_value_pair(*entry, &key, &value) == false) AUDDBG("Could not parse comment\n"); else { parse_comment(tuple, key, value); free(key); free(value); } } } break; case FLAC__METADATA_TYPE_STREAMINFO: metadata = FLAC__metadata_iterator_get_block(iter); /* Calculate the stream length (milliseconds) */ if (metadata->data.stream_info.sample_rate == 0) { FLACNG_ERROR("Invalid sample rate for stream!\n"); tuple_set_int(tuple, FIELD_LENGTH, NULL, -1); } else { tuple_set_int(tuple, FIELD_LENGTH, NULL, (metadata->data.stream_info.total_samples / metadata->data.stream_info.sample_rate) * 1000); AUDDBG("Stream length: %d seconds\n", tuple_get_int(tuple, FIELD_LENGTH, NULL)); } int64_t size = vfs_fsize(fd); if (size == -1 || metadata->data.stream_info.total_samples == 0) tuple_set_int(tuple, FIELD_BITRATE, NULL, 0); else { int bitrate = 8 * size * (int64_t) metadata->data.stream_info.sample_rate / metadata->data.stream_info.total_samples; tuple_set_int(tuple, FIELD_BITRATE, NULL, (bitrate + 500) / 1000); } break; default: ; } } while (FLAC__metadata_iterator_next(iter)); FLAC__metadata_iterator_delete(iter); FLAC__metadata_chain_delete(chain); return tuple; ERR: status = FLAC__metadata_chain_status(chain); FLAC__metadata_chain_delete(chain); FLACNG_ERROR("An error occured: %s\n", FLAC__Metadata_ChainStatusString[status]); return tuple; }
int32_t MV_PlayFLAC ( char *ptr, uint32_t ptrlength, int32_t loopstart, int32_t loopend, int32_t pitchoffset, int32_t vol, int32_t left, int32_t right, int32_t priority, uint32_t callbackval ) { VoiceNode *voice; flac_data * fd = 0; FLAC__Metadata_Chain* metadata_chain; UNREFERENCED_PARAMETER(loopend); if ( !MV_Installed ) { MV_SetErrorCode( MV_NotInstalled ); return MV_Error; } fd = (flac_data *) malloc( sizeof(flac_data) ); if (!fd) { MV_SetErrorCode( MV_InvalidFLACFile ); return MV_Error; } memset(fd, 0, sizeof(flac_data)); fd->ptr = ptr; fd->pos = 0; fd->blocksize = 0; fd->length = ptrlength; fd->block = NULL; fd->stream = FLAC__stream_decoder_new(); fd->sample_pos = 0; FLAC__stream_decoder_set_metadata_ignore_all(fd->stream); if (FLAC__stream_decoder_init_stream(fd->stream, read_flac_stream, seek_flac_stream, tell_flac_stream, length_flac_stream, eof_flac_stream, write_flac_stream, /*metadata_flac_stream*/ NULL, error_flac_stream, (void*) fd) != FLAC__STREAM_DECODER_INIT_STATUS_OK) { MV_Printf("MV_PlayFLAC: %s\n", FLAC__stream_decoder_get_resolved_state_string(fd->stream)); MV_SetErrorCode( MV_InvalidFLACFile ); return MV_Error; } // Request a voice from the voice pool voice = MV_AllocVoice( priority ); if ( voice == NULL ) { FLAC__stream_decoder_finish(fd->stream); FLAC__stream_decoder_delete(fd->stream); free(fd); MV_SetErrorCode( MV_NoVoices ); return MV_Error; } fd->owner = voice; voice->wavetype = FLAC; voice->extra = (void*)fd; voice->GetSound = MV_GetNextFLACBlock; voice->NextBlock = fd->block; voice->DemandFeed = NULL; voice->LoopCount = 0; voice->BlockLength = 0; voice->PitchScale = PITCH_GetScale( pitchoffset ); voice->next = NULL; voice->prev = NULL; voice->priority = priority; voice->callbackval = callbackval; voice->Playing = TRUE; voice->Paused = FALSE; voice->LoopStart = 0; voice->LoopEnd = 0; voice->LoopSize = (loopstart >= 0 ? 1 : 0); // parse metadata // loop parsing designed with multiple repetitions in mind // In retrospect, it may be possible to MV_GetVorbisCommentLoops(voice, (vorbis_comment *) tags->data.vorbis_comment) // but libvorbisfile may be confused by the signedness of char* vs FLAC__byte* and this code does not depend on HAVE_VORBIS. metadata_chain = FLAC__metadata_chain_new(); if (metadata_chain != NULL) { if (FLAC__metadata_chain_read_with_callbacks(metadata_chain, fd, flac_callbacks)) { FLAC__Metadata_Iterator* metadata_iterator = FLAC__metadata_iterator_new(); if (metadata_iterator != NULL) { char *vc_loopstart = NULL; char *vc_loopend = NULL; char *vc_looplength = NULL; FLAC__metadata_iterator_init(metadata_iterator, metadata_chain); do { FLAC__StreamMetadata *tags = FLAC__metadata_iterator_get_block(metadata_iterator); if (tags->type == FLAC__METADATA_TYPE_STREAMINFO) { const FLAC__StreamMetadata_StreamInfo *info = &tags->data.stream_info; if (info->channels != 1 && info->channels != 2) { FLAC__metadata_object_delete(tags); FLAC__metadata_iterator_delete(metadata_iterator); // FLAC__metadata_chain_delete(metadata_chain); FLAC__stream_decoder_finish(fd->stream); FLAC__stream_decoder_delete(fd->stream); free(fd); MV_SetErrorCode( MV_InvalidFLACFile ); return MV_Error; } voice->channels = info->channels; voice->bits = info->bits_per_sample; voice->SamplingRate = info->sample_rate; } // load loop tags from metadata if (tags->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) { FLAC__uint32 comment; uint8_t loopTagCount; for (comment = 0; comment < tags->data.vorbis_comment.num_comments; ++comment) { const char *entry = (const char *) tags->data.vorbis_comment.comments[comment].entry; if (entry != NULL && entry[0] != '\0') { const char *value = strchr(entry,'='); const size_t field = value-entry; value += 1; for (loopTagCount = 0; loopTagCount < loopStartTagCount && vc_loopstart == NULL; ++loopTagCount) if (strncasecmp(entry, loopStartTags[loopTagCount], field) == 0) vc_loopstart = strdup(value); for (loopTagCount = 0; loopTagCount < loopEndTagCount && vc_loopend == NULL; ++loopTagCount) if (strncasecmp(entry, loopEndTags[loopTagCount], field) == 0) vc_loopend = strdup(value); for (loopTagCount = 0; loopTagCount < loopLengthTagCount && vc_looplength == NULL; ++loopTagCount) if (strncasecmp(entry, loopLengthTags[loopTagCount], field) == 0) vc_looplength = strdup(value); } } } FLAC__metadata_object_delete(tags); // If it wasn't for this I would assign pointers instead of strdup(). } while (FLAC__metadata_iterator_next(metadata_iterator)); if (vc_loopstart != NULL) { { const FLAC__int64 flac_loopstart = atol(vc_loopstart); if (flac_loopstart >= 0) // a loop starting at 0 is valid { voice->LoopStart = (const char *) (intptr_t) flac_loopstart; voice->LoopSize = 1; } } free(vc_loopstart); } if (vc_loopend != NULL) { if (voice->LoopSize > 0) { const FLAC__int64 flac_loopend = atol(vc_loopend); if (flac_loopend > 0) // a loop ending at 0 is invalid voice->LoopEnd = (const char *) (intptr_t) flac_loopend; } free(vc_loopend); } if (vc_looplength != NULL) { if (voice->LoopSize > 0 && voice->LoopEnd == 0) { const FLAC__int64 flac_looplength = atol(vc_looplength); if (flac_looplength > 0) // a loop of length 0 is invalid voice->LoopEnd = (const char *) ((intptr_t) flac_looplength + (intptr_t) voice->LoopStart); } free(vc_looplength); } FLAC__metadata_iterator_delete(metadata_iterator); } else MV_Printf("Error allocating FLAC__Metadata_Iterator!\n"); } else MV_Printf("%s\n", FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(metadata_chain)]); // FLAC__metadata_chain_delete(metadata_chain); // when run with GDB, this throws SIGTRAP about freed heap memory being modified } else MV_Printf("Error allocating FLAC__Metadata_Chain!\n"); // CODEDUP multivoc.c MV_SetVoicePitch voice->RateScale = ( voice->SamplingRate * voice->PitchScale ) / MV_MixRate; voice->FixedPointBufferSize = ( voice->RateScale * MV_MIXBUFFERSIZE ) - voice->RateScale; MV_SetVoiceMixMode( voice ); MV_SetVoiceVolume( voice, vol, left, right ); MV_PlayVoice( voice ); return voice->handle; }
FLAC__bool do_shorthand_operation__picture(const char *filename, FLAC__Metadata_Chain *chain, const Operation *operation, FLAC__bool *needs_write) { FLAC__bool ok = true, has_type1 = false, has_type2 = false; FLAC__StreamMetadata *picture = 0; FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new(); if(0 == iterator) die("out of memory allocating iterator"); FLAC__metadata_iterator_init(iterator, chain); switch(operation->type) { case OP__IMPORT_PICTURE_FROM: ok = import_pic_from(filename, &picture, operation->argument.specification.value, needs_write); if(ok) { /* append PICTURE block */ while(FLAC__metadata_iterator_next(iterator)) ; if(!FLAC__metadata_iterator_insert_block_after(iterator, picture)) { print_error_with_chain_status(chain, "%s: ERROR: adding new PICTURE block to metadata", filename); FLAC__metadata_object_delete(picture); ok = false; } } if(ok) { /* check global PICTURE constraints (max 1 block each of type=1 and type=2) */ while(FLAC__metadata_iterator_prev(iterator)) ; do { FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(iterator); if(block->type == FLAC__METADATA_TYPE_PICTURE) { if(block->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON_STANDARD) { if(has_type1) { print_error_with_chain_status(chain, "%s: ERROR: FLAC stream can only have one 32x32 standard icon (type=1) PICTURE block", filename); ok = false; } has_type1 = true; } else if(block->data.picture.type == FLAC__STREAM_METADATA_PICTURE_TYPE_FILE_ICON) { if(has_type2) { print_error_with_chain_status(chain, "%s: ERROR: FLAC stream can only have one icon (type=2) PICTURE block", filename); ok = false; } has_type2 = true; } } } while(FLAC__metadata_iterator_next(iterator)); } break; case OP__EXPORT_PICTURE_TO: { const Argument_BlockNumber *a = operation->argument.export_picture_to.block_number_link; int block_number = (a && a->num_entries > 0)? (int)a->entries[0] : -1; unsigned i = 0; do { FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(iterator); if(block->type == FLAC__METADATA_TYPE_PICTURE && (block_number < 0 || i == (unsigned)block_number)) picture = block; i++; } while(FLAC__metadata_iterator_next(iterator) && 0 == picture); if(0 == picture) { if(block_number < 0) fprintf(stderr, "%s: ERROR: FLAC file has no PICTURE block\n", filename); else fprintf(stderr, "%s: ERROR: FLAC file has no PICTURE block at block #%d\n", filename, block_number); ok = false; } else ok = export_pic_to(filename, picture, operation->argument.filename.value); } break; default: ok = false; FLAC__ASSERT(0); break; }; FLAC__metadata_iterator_delete(iterator); return ok; }
FLAC__bool do_shorthand_operation__cuesheet(const char *filename, FLAC__Metadata_Chain *chain, const Operation *operation, FLAC__bool *needs_write) { FLAC__bool ok = true; FLAC__StreamMetadata *cuesheet = 0; FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new(); FLAC__uint64 lead_out_offset = 0; FLAC__bool is_cdda = false; unsigned sample_rate = 0; if(0 == iterator) die("out of memory allocating iterator"); FLAC__metadata_iterator_init(iterator, chain); do { FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(iterator); if(block->type == FLAC__METADATA_TYPE_STREAMINFO) { lead_out_offset = block->data.stream_info.total_samples; if(lead_out_offset == 0) { flac_fprintf(stderr, "%s: ERROR: FLAC file must have total_samples set in STREAMINFO in order to import/export cuesheet\n", filename); FLAC__metadata_iterator_delete(iterator); return false; } sample_rate = block->data.stream_info.sample_rate; is_cdda = (block->data.stream_info.channels == 1 || block->data.stream_info.channels == 2) && (block->data.stream_info.bits_per_sample == 16) && (sample_rate == 44100); } else if(block->type == FLAC__METADATA_TYPE_CUESHEET) cuesheet = block; } while(FLAC__metadata_iterator_next(iterator)); if(lead_out_offset == 0) { flac_fprintf(stderr, "%s: ERROR: FLAC stream has no STREAMINFO block\n", filename); FLAC__metadata_iterator_delete(iterator); return false; } switch(operation->type) { case OP__IMPORT_CUESHEET_FROM: if(0 != cuesheet) { flac_fprintf(stderr, "%s: ERROR: FLAC file already has CUESHEET block\n", filename); ok = false; } else { ok = import_cs_from(filename, &cuesheet, operation->argument.import_cuesheet_from.filename, needs_write, lead_out_offset, sample_rate, is_cdda, operation->argument.import_cuesheet_from.add_seekpoint_link); if(ok) { /* append CUESHEET block */ while(FLAC__metadata_iterator_next(iterator)) ; if(!FLAC__metadata_iterator_insert_block_after(iterator, cuesheet)) { print_error_with_chain_status(chain, "%s: ERROR: adding new CUESHEET block to metadata", filename); FLAC__metadata_object_delete(cuesheet); ok = false; } } } break; case OP__EXPORT_CUESHEET_TO: if(0 == cuesheet) { flac_fprintf(stderr, "%s: ERROR: FLAC file has no CUESHEET block\n", filename); ok = false; } else ok = export_cs_to(filename, cuesheet, operation->argument.filename.value); break; default: ok = false; FLAC__ASSERT(0); break; }; FLAC__metadata_iterator_delete(iterator); return ok; }
bool FLACMetadata::WriteMetadata(CFErrorRef *error) { UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX)) return false; FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); // ENOMEM sux if(NULL == chain) return false; if(!FLAC__metadata_chain_read(chain, reinterpret_cast<const char *>(buf))) { // Attempt to provide a descriptive error message if(NULL != error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 32, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); switch(FLAC__metadata_chain_status(chain)) { case FLAC__METADATA_CHAIN_STATUS_NOT_A_FLAC_FILE: { CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not a FLAC file"), "")); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedRecoverySuggestionKey, CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), "")); CFRelease(errorString), errorString = NULL; CFRelease(displayName), displayName = NULL; break; } case FLAC__METADATA_CHAIN_STATUS_BAD_METADATA: { CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not a FLAC file"), "")); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedRecoverySuggestionKey, CFCopyLocalizedString(CFSTR("The file contains bad metadata."), "")); CFRelease(errorString), errorString = NULL; CFRelease(displayName), displayName = NULL; break; } default: { CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not a FLAC file"), "")); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedRecoverySuggestionKey, CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), "")); CFRelease(errorString), errorString = NULL; CFRelease(displayName), displayName = NULL; break; } } *error = CFErrorCreate(kCFAllocatorDefault, AudioMetadataErrorDomain, AudioMetadataFileFormatNotRecognizedError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } FLAC__metadata_chain_delete(chain), chain = NULL; return false; } FLAC__metadata_chain_sort_padding(chain); FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new(); if(NULL == iterator) { FLAC__metadata_chain_delete(chain), chain = NULL; return false; } FLAC__metadata_iterator_init(iterator, chain); // Seek to the vorbis comment block if it exists while(FLAC__METADATA_TYPE_VORBIS_COMMENT != FLAC__metadata_iterator_get_block_type(iterator)) { if(!FLAC__metadata_iterator_next(iterator)) break; // Already at end } FLAC__StreamMetadata *block = NULL; // If there isn't a vorbis comment block add one if(FLAC__METADATA_TYPE_VORBIS_COMMENT != FLAC__metadata_iterator_get_block_type(iterator)) { // The padding block will be the last block if it exists; add the comment block before it if(FLAC__METADATA_TYPE_PADDING == FLAC__metadata_iterator_get_block_type(iterator)) FLAC__metadata_iterator_prev(iterator); block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); if(NULL == block) { FLAC__metadata_chain_delete(chain), chain = NULL; FLAC__metadata_iterator_delete(iterator), iterator = NULL; return false; } // Add our metadata if(!FLAC__metadata_iterator_insert_block_after(iterator, block)) { if(NULL != error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 32, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Unable to write metadata"), "")); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedRecoverySuggestionKey, CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), "")); CFRelease(errorString), errorString = NULL; CFRelease(displayName), displayName = NULL; *error = CFErrorCreate(kCFAllocatorDefault, AudioMetadataErrorDomain, AudioMetadataInputOutputError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } FLAC__metadata_chain_delete(chain), chain = NULL; FLAC__metadata_iterator_delete(iterator), iterator = NULL; return false; } } else block = FLAC__metadata_iterator_get_block(iterator); // Standard tags SetVorbisComment(block, "ALBUM", GetAlbumTitle()); SetVorbisComment(block, "ARTIST", GetArtist()); SetVorbisComment(block, "ALBUMARTIST", GetAlbumArtist()); SetVorbisComment(block, "COMPOSER", GetComposer()); SetVorbisComment(block, "GENRE", GetGenre()); SetVorbisComment(block, "DATE", GetReleaseDate()); SetVorbisComment(block, "DESCRIPTION", GetComment()); SetVorbisComment(block, "TITLE", GetTitle()); SetVorbisCommentNumber(block, "TRACKNUMBER", GetTrackNumber()); SetVorbisCommentNumber(block, "TRACKTOTAL", GetTrackTotal()); SetVorbisCommentBoolean(block, "COMPILATION", GetCompilation()); SetVorbisCommentNumber(block, "DISCNUMBER", GetDiscNumber()); SetVorbisCommentNumber(block, "DISCTOTAL", GetDiscTotal()); SetVorbisComment(block, "ISRC", GetISRC()); SetVorbisComment(block, "MCN", GetMCN()); // Additional metadata CFDictionaryRef additionalMetadata = GetAdditionalMetadata(); if(NULL != additionalMetadata) { CFIndex count = CFDictionaryGetCount(additionalMetadata); const void * keys [count]; const void * values [count]; CFDictionaryGetKeysAndValues(additionalMetadata, reinterpret_cast<const void **>(keys), reinterpret_cast<const void **>(values)); for(CFIndex i = 0; i < count; ++i) { CFIndex keySize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(reinterpret_cast<CFStringRef>(keys[i])), kCFStringEncodingASCII); char key [keySize + 1]; if(!CFStringGetCString(reinterpret_cast<CFStringRef>(keys[i]), key, keySize + 1, kCFStringEncodingASCII)) { log4cxx::LoggerPtr logger = log4cxx::Logger::getLogger("org.sbooth.AudioEngine.AudioMetadata.FLAC"); LOG4CXX_WARN(logger, "CFStringGetCString() failed"); continue; } SetVorbisComment(block, key, reinterpret_cast<CFStringRef>(values[i])); } } // ReplayGain info SetVorbisCommentDouble(block, "REPLAYGAIN_REFERENCE_LOUDNESS", GetReplayGainReferenceLoudness(), CFSTR("%2.1f dB")); SetVorbisCommentDouble(block, "REPLAYGAIN_TRACK_GAIN", GetReplayGainReferenceLoudness(), CFSTR("%+2.2f dB")); SetVorbisCommentDouble(block, "REPLAYGAIN_TRACK_PEAK", GetReplayGainTrackGain(), CFSTR("%1.8f")); SetVorbisCommentDouble(block, "REPLAYGAIN_ALBUM_GAIN", GetReplayGainAlbumGain(), CFSTR("%+2.2f dB")); SetVorbisCommentDouble(block, "REPLAYGAIN_ALBUM_PEAK", GetReplayGainAlbumPeak(), CFSTR("%1.8f")); // Write the new metadata to the file if(!FLAC__metadata_chain_write(chain, true, false)) { if(NULL != error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 32, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Unable to write metadata"), "")); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedRecoverySuggestionKey, CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), "")); CFRelease(errorString), errorString = NULL; CFRelease(displayName), displayName = NULL; *error = CFErrorCreate(kCFAllocatorDefault, AudioMetadataErrorDomain, AudioMetadataInputOutputError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } FLAC__metadata_chain_delete(chain), chain = NULL; FLAC__metadata_iterator_delete(iterator), iterator = NULL; return false; } FLAC__metadata_chain_delete(chain), chain = NULL; FLAC__metadata_iterator_delete(iterator), iterator = NULL; MergeChangedMetadataIntoMetadata(); return true; }
bool FLACMetadata::ReadMetadata(CFErrorRef *error) { // Start from scratch CFDictionaryRemoveAllValues(mMetadata); CFDictionaryRemoveAllValues(mChangedMetadata); UInt8 buf [PATH_MAX]; if(!CFURLGetFileSystemRepresentation(mURL, false, buf, PATH_MAX)) return false; FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); // ENOMEM sux if(NULL == chain) return false; if(!FLAC__metadata_chain_read(chain, reinterpret_cast<const char *>(buf))) { // Attempt to provide a descriptive error message if(NULL != error) { CFMutableDictionaryRef errorDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 32, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); switch(FLAC__metadata_chain_status(chain)) { case FLAC__METADATA_CHAIN_STATUS_NOT_A_FLAC_FILE: { CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not a FLAC file"), "")); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedRecoverySuggestionKey, CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), "")); CFRelease(errorString), errorString = NULL; CFRelease(displayName), displayName = NULL; break; } case FLAC__METADATA_CHAIN_STATUS_BAD_METADATA: { CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not a FLAC file"), "")); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedRecoverySuggestionKey, CFCopyLocalizedString(CFSTR("The file contains bad metadata."), "")); CFRelease(errorString), errorString = NULL; CFRelease(displayName), displayName = NULL; break; } default: { CFStringRef displayName = CreateDisplayNameForURL(mURL); CFStringRef errorString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFCopyLocalizedString(CFSTR("The file “%@” is not a valid FLAC file."), ""), displayName); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedDescriptionKey, errorString); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedFailureReasonKey, CFCopyLocalizedString(CFSTR("Not a FLAC file"), "")); CFDictionarySetValue(errorDictionary, kCFErrorLocalizedRecoverySuggestionKey, CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), "")); CFRelease(errorString), errorString = NULL; CFRelease(displayName), displayName = NULL; break; } } *error = CFErrorCreate(kCFAllocatorDefault, AudioMetadataErrorDomain, AudioMetadataFileFormatNotRecognizedError, errorDictionary); CFRelease(errorDictionary), errorDictionary = NULL; } FLAC__metadata_chain_delete(chain), chain = NULL; return false; } FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new(); if(NULL == iterator) { FLAC__metadata_chain_delete(chain), chain = NULL; return false; } CFDictionarySetValue(mMetadata, kPropertiesFormatNameKey, CFSTR("FLAC")); FLAC__metadata_iterator_init(iterator, chain); FLAC__StreamMetadata *block = NULL; CFMutableDictionaryRef additionalMetadata = CFDictionaryCreateMutable(kCFAllocatorDefault, 32, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); do { block = FLAC__metadata_iterator_get_block(iterator); if(NULL == block) break; switch(block->type) { case FLAC__METADATA_TYPE_VORBIS_COMMENT: for(unsigned i = 0; i < block->data.vorbis_comment.num_comments; ++i) { char *fieldName = NULL; char *fieldValue = NULL; // Let FLAC parse the comment for us if(!FLAC__metadata_object_vorbiscomment_entry_to_name_value_pair(block->data.vorbis_comment.comments[i], &fieldName, &fieldValue)) { // Ignore malformed comments continue; } CFStringRef key = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(fieldName), strlen(fieldName), kCFStringEncodingASCII, false, kCFAllocatorMalloc); CFStringRef value = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(fieldValue), strlen(fieldValue), kCFStringEncodingUTF8, false, kCFAllocatorMalloc); if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUM"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataAlbumTitleKey, value); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ARTIST"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataArtistKey, value); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ALBUMARTIST"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataAlbumArtistKey, value); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("COMPOSER"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataComposerKey, value); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("GENRE"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataGenreKey, value); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DATE"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataReleaseDateKey, value); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DESCRIPTION"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataCommentKey, value); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TITLE"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataTitleKey, value); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TRACKNUMBER"), kCFCompareCaseInsensitive)) { int num = CFStringGetIntValue(value); CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &num); CFDictionarySetValue(mMetadata, kMetadataTrackNumberKey, number); CFRelease(number), number = NULL; } else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("TRACKTOTAL"), kCFCompareCaseInsensitive)) { int num = CFStringGetIntValue(value); CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &num); CFDictionarySetValue(mMetadata, kMetadataTrackTotalKey, number); CFRelease(number), number = NULL; } else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("COMPILATION"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataCompilationKey, CFStringGetIntValue(value) ? kCFBooleanTrue : kCFBooleanFalse); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DISCNUMBER"), kCFCompareCaseInsensitive)) { int num = CFStringGetIntValue(value); CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &num); CFDictionarySetValue(mMetadata, kMetadataDiscNumberKey, number); CFRelease(number), number = NULL; } else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("DISCTOTAL"), kCFCompareCaseInsensitive)) { int num = CFStringGetIntValue(value); CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &num); CFDictionarySetValue(mMetadata, kMetadataDiscTotalKey, number); CFRelease(number), number = NULL; } else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("ISRC"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataISRCKey, value); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("MCN"), kCFCompareCaseInsensitive)) CFDictionarySetValue(mMetadata, kMetadataMCNKey, value); else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_REFERENCE_LOUDNESS"), kCFCompareCaseInsensitive)) { double num = CFStringGetDoubleValue(value); CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &num); CFDictionarySetValue(mMetadata, kReplayGainReferenceLoudnessKey, number); CFRelease(number), number = NULL; } else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_TRACK_GAIN"), kCFCompareCaseInsensitive)) { double num = CFStringGetDoubleValue(value); CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &num); CFDictionarySetValue(mMetadata, kReplayGainTrackGainKey, number); CFRelease(number), number = NULL; } else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_TRACK_PEAK"), kCFCompareCaseInsensitive)) { double num = CFStringGetDoubleValue(value); CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &num); CFDictionarySetValue(mMetadata, kReplayGainTrackPeakKey, number); CFRelease(number), number = NULL; } else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_ALBUM_GAIN"), kCFCompareCaseInsensitive)) { double num = CFStringGetDoubleValue(value); CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &num); CFDictionarySetValue(mMetadata, kReplayGainAlbumGainKey, number); CFRelease(number), number = NULL; } else if(kCFCompareEqualTo == CFStringCompare(key, CFSTR("REPLAYGAIN_ALBUM_PEAK"), kCFCompareCaseInsensitive)) { double num = CFStringGetDoubleValue(value); CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &num); CFDictionarySetValue(mMetadata, kReplayGainAlbumPeakKey, number); CFRelease(number), number = NULL; } // Put all unknown tags into the additional metadata else CFDictionarySetValue(additionalMetadata, key, value); CFRelease(key), key = NULL; CFRelease(value), value = NULL; fieldName = NULL; fieldValue = NULL; } break; case FLAC__METADATA_TYPE_PICTURE: { CFDataRef data = CFDataCreate(kCFAllocatorDefault, block->data.picture.data, block->data.picture.data_length); CFDictionarySetValue(mMetadata, kAlbumArtFrontCoverKey, data); CFRelease(data), data = NULL; } break; case FLAC__METADATA_TYPE_STREAMINFO: { CFNumberRef sampleRate = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &block->data.stream_info.sample_rate); CFDictionarySetValue(mMetadata, kPropertiesSampleRateKey, sampleRate); CFRelease(sampleRate), sampleRate = NULL; CFNumberRef channelsPerFrame = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &block->data.stream_info.channels); CFDictionarySetValue(mMetadata, kPropertiesChannelsPerFrameKey, channelsPerFrame); CFRelease(channelsPerFrame), channelsPerFrame = NULL; CFNumberRef bitsPerChannel = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &block->data.stream_info.bits_per_sample); CFDictionarySetValue(mMetadata, kPropertiesBitsPerChannelKey, bitsPerChannel); CFRelease(bitsPerChannel), bitsPerChannel = NULL; CFNumberRef totalFrames = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &block->data.stream_info.total_samples); CFDictionarySetValue(mMetadata, kPropertiesTotalFramesKey, totalFrames); CFRelease(totalFrames), totalFrames = NULL; double length = static_cast<double>(block->data.stream_info.total_samples / block->data.stream_info.sample_rate); CFNumberRef duration = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &length); CFDictionarySetValue(mMetadata, kPropertiesDurationKey, duration); CFRelease(duration), duration = NULL; double losslessBitrate = static_cast<double>(block->data.stream_info.sample_rate * block->data.stream_info.channels * block->data.stream_info.bits_per_sample) / 1000; CFNumberRef bitrate = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &losslessBitrate); CFDictionarySetValue(mMetadata, kPropertiesBitrateKey, bitrate); CFRelease(bitrate), bitrate = NULL; } break; case FLAC__METADATA_TYPE_PADDING: break; case FLAC__METADATA_TYPE_APPLICATION: break; case FLAC__METADATA_TYPE_SEEKTABLE: break; case FLAC__METADATA_TYPE_CUESHEET: break; case FLAC__METADATA_TYPE_UNDEFINED: break; default: break; } } while(FLAC__metadata_iterator_next(iterator)); if(CFDictionaryGetCount(additionalMetadata)) SetAdditionalMetadata(additionalMetadata); CFRelease(additionalMetadata), additionalMetadata = NULL; FLAC__metadata_iterator_delete(iterator), iterator = NULL; FLAC__metadata_chain_delete(chain), chain = NULL; return true; }
/* * Write Flac tag, using the level 2 flac interface */ gboolean Flac_Tag_Write_File_Tag (ET_File *ETFile) { File_Tag *FileTag; gchar *filename_utf8, *filename; gchar *basename_utf8; FLAC__Metadata_Chain *chain; FLAC__Metadata_Iterator *iter; FLAC__StreamMetadata_VorbisComment_Entry vce_field_vendor_string; // To save vendor string gboolean vce_field_vendor_string_found = FALSE; if (!ETFile || !ETFile->FileTag) return FALSE; FileTag = (File_Tag *)ETFile->FileTag->data; filename = ((File_Name *)ETFile->FileNameCur->data)->value; filename_utf8 = ((File_Name *)ETFile->FileNameCur->data)->value_utf8; flac_error_msg = NULL; /* libFLAC is able to detect (and skip) ID3v2 tags by itself */ // Create a new chain instance to get all blocks in one time chain = FLAC__metadata_chain_new(); if (chain == NULL || !FLAC__metadata_chain_read(chain,filename)) { if (chain == NULL) { // Error with "FLAC__metadata_chain_new" flac_error_msg = FLAC__Metadata_ChainStatusString[FLAC__METADATA_CHAIN_STATUS_MEMORY_ALLOCATION_ERROR]; } else { // Error with "FLAC__metadata_chain_read" FLAC__Metadata_ChainStatus status = FLAC__metadata_chain_status(chain); flac_error_msg = FLAC__Metadata_ChainStatusString[status]; FLAC__metadata_chain_delete(chain); } Log_Print(LOG_ERROR,_("ERROR while opening file: '%s' as FLAC (%s)."),filename_utf8,flac_error_msg); return FALSE; } // Create a new iterator instance for the chain iter = FLAC__metadata_iterator_new(); if (iter == NULL) { flac_error_msg = FLAC__Metadata_ChainStatusString[FLAC__METADATA_CHAIN_STATUS_MEMORY_ALLOCATION_ERROR]; Log_Print(LOG_ERROR,_("ERROR while opening file: '%s' as FLAC (%s)."),filename_utf8,flac_error_msg); return FALSE; } // Initialize the iterator to point to the first metadata block in the given chain. FLAC__metadata_iterator_init(iter,chain); while (FLAC__metadata_iterator_next(iter)) { //g_print("Write: %d %s -> block type: %d\n",j++,g_path_get_basename(filename),FLAC__metadata_iterator_get_block_type(iter)); // Action to do according the type switch ( FLAC__metadata_iterator_get_block_type(iter) ) { // // Delete the VORBIS_COMMENT block and convert to padding. But before, save the original vendor string. // case FLAC__METADATA_TYPE_VORBIS_COMMENT: { // Get block data FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(iter); FLAC__StreamMetadata_VorbisComment *vc = &block->data.vorbis_comment; if (vc->vendor_string.entry != NULL) { // Get initial vendor string, to don't alterate it by FLAC__VENDOR_STRING when saving file vce_field_vendor_string.entry = (FLAC__byte *)g_strdup((gchar *)vc->vendor_string.entry); vce_field_vendor_string.length = strlen((gchar *)vce_field_vendor_string.entry); vce_field_vendor_string_found = TRUE; } // Free block data FLAC__metadata_iterator_delete_block(iter,true); break; } // // Delete all the PICTURE blocks, and convert to padding // #ifndef LEGACY_FLAC // For FLAC >= 1.1.3 case FLAC__METADATA_TYPE_PICTURE: { FLAC__metadata_iterator_delete_block(iter,true); break; } #endif default: break; } } // // Create and insert a new VORBISCOMMENT block // { FLAC__StreamMetadata *vc_block; // For vorbis comments FLAC__StreamMetadata_VorbisComment_Entry field; gchar *string; GList *list; // Allocate a block for Vorbis comments vc_block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); // Set the original vendor string, else will be use the version of library if (vce_field_vendor_string_found) { // must set 'copy' param to false, because the API will reuse the pointer of an empty // string (yet still return 'true', indicating it was copied); the string is free'd during // metadata_chain_delete routine FLAC__metadata_object_vorbiscomment_set_vendor_string(vc_block, vce_field_vendor_string, /*copy=*/false); } /********* * Title * *********/ if ( FileTag->title ) { Flac_Write_Delimetered_Tag(vc_block,"TITLE=",FileTag->title); } /********** * Artist * **********/ if ( FileTag->artist ) { Flac_Write_Delimetered_Tag(vc_block,"ARTIST=",FileTag->artist); } /********* * Album * *********/ if ( FileTag->album ) { Flac_Write_Delimetered_Tag(vc_block,"ALBUM=",FileTag->album); } /*************** * Disc Number * ***************/ if ( FileTag->disc_number ) { string = g_strconcat("DISCNUMBER=",FileTag->disc_number,NULL); field.entry = (FLAC__byte *)string; field.length = strlen(string); FLAC__metadata_object_vorbiscomment_insert_comment(vc_block,vc_block->data.vorbis_comment.num_comments,field,true); g_free(string); } /******** * Year * ********/ if ( FileTag->year ) { string = g_strconcat("DATE=",FileTag->year,NULL); field.entry = (FLAC__byte *)string; field.length = strlen(string); FLAC__metadata_object_vorbiscomment_insert_comment(vc_block,vc_block->data.vorbis_comment.num_comments,field,true); g_free(string); } /************************* * Track and Total Track * *************************/ if ( FileTag->track ) { string = g_strconcat("TRACKNUMBER=",FileTag->track,NULL); field.entry = (FLAC__byte *)string; field.length = strlen(string); FLAC__metadata_object_vorbiscomment_insert_comment(vc_block,vc_block->data.vorbis_comment.num_comments,field,true); g_free(string); } if ( FileTag->track_total /*&& strlen(FileTag->track_total)>0*/ ) { string = g_strconcat("TRACKTOTAL=",FileTag->track_total,NULL); field.entry = (FLAC__byte *)string; field.length = strlen(string); FLAC__metadata_object_vorbiscomment_insert_comment(vc_block,vc_block->data.vorbis_comment.num_comments,field,true); g_free(string); } /********* * Genre * *********/ if ( FileTag->genre ) { Flac_Write_Delimetered_Tag(vc_block,"GENRE=",FileTag->genre); } /*********** * Comment * ***********/ // We write the comment using the "both" format if ( FileTag->comment ) { Flac_Write_Delimetered_Tag(vc_block,"DESCRIPTION=",FileTag->comment); Flac_Write_Delimetered_Tag(vc_block,"COMMENT=",FileTag->comment); } /************ * Composer * ************/ if ( FileTag->composer ) { Flac_Write_Delimetered_Tag(vc_block,"COMPOSER=",FileTag->composer); } /******************* * Original artist * *******************/ if ( FileTag->orig_artist ) { Flac_Write_Delimetered_Tag(vc_block,"PERFORMER=",FileTag->orig_artist); } /************* * Copyright * *************/ if ( FileTag->copyright ) { string = g_strconcat("COPYRIGHT=",FileTag->copyright,NULL); field.entry = (FLAC__byte *)string; field.length = strlen(string); FLAC__metadata_object_vorbiscomment_insert_comment(vc_block,vc_block->data.vorbis_comment.num_comments,field,true); g_free(string); } /******* * URL * *******/ if ( FileTag->url ) { string = g_strconcat("LICENSE=",FileTag->url,NULL); field.entry = (FLAC__byte *)string; field.length = strlen(string); FLAC__metadata_object_vorbiscomment_insert_comment(vc_block,vc_block->data.vorbis_comment.num_comments,field,true); g_free(string); } /************** * Encoded by * **************/ if ( FileTag->encoded_by ) { string = g_strconcat("ENCODED-BY=",FileTag->encoded_by,NULL); field.entry = (FLAC__byte *)string; field.length = strlen(string); FLAC__metadata_object_vorbiscomment_insert_comment(vc_block,vc_block->data.vorbis_comment.num_comments,field,true); g_free(string); } /************************** * Set unsupported fields * **************************/ list = FileTag->other; while (list) { if (list->data) { string = (gchar*)list->data; field.entry = (FLAC__byte *)string; field.length = strlen(string); FLAC__metadata_object_vorbiscomment_insert_comment(vc_block,vc_block->data.vorbis_comment.num_comments,field,true); } list = list->next; } // Add the block to the the chain (so we don't need to free the block) FLAC__metadata_iterator_insert_block_after(iter, vc_block); } // // Create and insert PICTURE blocks // /*********** * Picture * ***********/ // For FLAC >= 1.1.3 #ifndef LEGACY_FLAC { Picture *pic = FileTag->picture; while (pic) { if (pic->data) { const gchar *violation; FLAC__StreamMetadata *picture_block; // For picture data Picture_Format format; // Allocate block for picture data picture_block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE); // Type picture_block->data.picture.type = pic->type; // Mime type format = Picture_Format_From_Data(pic); FLAC__metadata_object_picture_set_mime_type(picture_block, (gchar *)Picture_Mime_Type_String(format), TRUE); // Description if (pic->description) { FLAC__metadata_object_picture_set_description(picture_block, (FLAC__byte *)pic->description, TRUE); } // Resolution picture_block->data.picture.width = pic->width; picture_block->data.picture.height = pic->height; picture_block->data.picture.depth = 0; // Picture data FLAC__metadata_object_picture_set_data(picture_block, (FLAC__byte *)pic->data, (FLAC__uint32) pic->size, TRUE); if (!FLAC__metadata_object_picture_is_legal(picture_block, &violation)) { Log_Print(LOG_ERROR,_("Picture block isn't valid: '%s'"),violation); FLAC__metadata_object_delete(picture_block); } else { // Add the block to the the chain (so we don't need to free the block) FLAC__metadata_iterator_insert_block_after(iter, picture_block); } } pic = pic->next; } } #endif // Free iter FLAC__metadata_iterator_delete(iter); // // Prepare for writing tag // // Move all PADDING blocks to the end on the metadata, and merge them into a single block. FLAC__metadata_chain_sort_padding(chain); // Write tag if ( !FLAC__metadata_chain_write(chain, /*padding*/TRUE, PRESERVE_MODIFICATION_TIME) ) { // Error with "FLAC__metadata_chain_write" FLAC__Metadata_ChainStatus status = FLAC__metadata_chain_status(chain); flac_error_msg = FLAC__Metadata_ChainStatusString[status]; FLAC__metadata_chain_delete(chain); Log_Print(LOG_ERROR,_("ERROR: Failed to write comments to file '%s' (%s)."),filename_utf8,flac_error_msg); return FALSE; } else { basename_utf8 = g_path_get_basename(filename_utf8); Log_Print(LOG_OK,_("Written tag of '%s'"),basename_utf8); g_free(basename_utf8); } FLAC__metadata_chain_delete(chain); #ifdef ENABLE_MP3 /* * Write also the ID3 tags (ID3v1 and/or ID3v2) if wanted (as needed by some players) */ if (WRITE_ID3_TAGS_IN_FLAC_FILE) { Id3tag_Write_File_Tag(ETFile); } else { // Delete the ID3 tags (create a dummy ETFile for the Id3tag_... function) ET_File *ETFile_tmp = ET_File_Item_New(); File_Name *FileName_tmp = ET_File_Name_Item_New(); File_Tag *FileTag_tmp = ET_File_Tag_Item_New(); // Same file... FileName_tmp->value = g_strdup(filename); FileName_tmp->value_utf8 = g_strdup(filename_utf8); // Not necessary to fill 'value_ck' ETFile_tmp->FileNameList = g_list_append(NULL,FileName_tmp); ETFile_tmp->FileNameCur = ETFile_tmp->FileNameList; // With empty tag... ETFile_tmp->FileTagList = g_list_append(NULL,FileTag_tmp); ETFile_tmp->FileTag = ETFile_tmp->FileTagList; Id3tag_Write_File_Tag(ETFile_tmp); ET_Free_File_List_Item(ETFile_tmp); } #endif return TRUE; }
FLAC__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) { FLAC__bool ok = true, found_vc_block = false; FLAC__StreamMetadata *block = 0; FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new(); if(0 == iterator) die("out of memory allocating iterator"); FLAC__metadata_iterator_init(iterator, chain); do { block = FLAC__metadata_iterator_get_block(iterator); if(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) found_vc_block = true; } while(!found_vc_block && FLAC__metadata_iterator_next(iterator)); if(!found_vc_block) { /* create a new block if necessary */ if(operation->type == OP__SET_VC_FIELD || operation->type == OP__IMPORT_VC_FROM) { block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); if(0 == block) die("out of memory allocating VORBIS_COMMENT block"); while(FLAC__metadata_iterator_next(iterator)) ; if(!FLAC__metadata_iterator_insert_block_after(iterator, block)) { print_error_with_chain_status(chain, "%s: ERROR: adding new VORBIS_COMMENT block to metadata", filename); return false; } /* iterator is left pointing to new block */ FLAC__ASSERT(FLAC__metadata_iterator_get_block(iterator) == block); } else { FLAC__metadata_iterator_delete(iterator); return ok; } } FLAC__ASSERT(0 != block); FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); switch(operation->type) { case OP__SHOW_VC_VENDOR: write_vc_field(prefix_with_filename? filename : 0, &block->data.vorbis_comment.vendor_string, raw, stdout); break; case OP__SHOW_VC_FIELD: 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); break; case OP__REMOVE_VC_ALL: ok = remove_vc_all(filename, block, needs_write); break; case OP__REMOVE_VC_FIELD: ok = remove_vc_field(filename, block, operation->argument.vc_field_name.value, needs_write); break; case OP__REMOVE_VC_FIRSTFIELD: ok = remove_vc_firstfield(filename, block, operation->argument.vc_field_name.value, needs_write); break; case OP__SET_VC_FIELD: ok = set_vc_field(filename, block, &operation->argument.vc_field, needs_write, raw); break; case OP__IMPORT_VC_FROM: ok = import_vc_from(filename, block, &operation->argument.filename, needs_write, raw); break; case OP__EXPORT_VC_TO: ok = export_vc_to(filename, block, &operation->argument.filename, raw); break; default: ok = false; FLAC__ASSERT(0); break; }; FLAC__metadata_iterator_delete(iterator); return ok; }
FLAC__bool do_shorthand_operation__streaminfo(const char *filename, FLAC__bool prefix_with_filename, FLAC__Metadata_Chain *chain, const Operation *operation, FLAC__bool *needs_write) { unsigned i; FLAC__bool ok = true; FLAC__StreamMetadata *block; FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new(); if(0 == iterator) die("out of memory allocating iterator"); FLAC__metadata_iterator_init(iterator, chain); block = FLAC__metadata_iterator_get_block(iterator); FLAC__ASSERT(0 != block); FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_STREAMINFO); if(prefix_with_filename) flac_printf("%s:", filename); switch(operation->type) { case OP__SHOW_MD5SUM: for(i = 0; i < 16; i++) printf("%02x", block->data.stream_info.md5sum[i]); printf("\n"); break; case OP__SHOW_MIN_BLOCKSIZE: printf("%u\n", block->data.stream_info.min_blocksize); break; case OP__SHOW_MAX_BLOCKSIZE: printf("%u\n", block->data.stream_info.max_blocksize); break; case OP__SHOW_MIN_FRAMESIZE: printf("%u\n", block->data.stream_info.min_framesize); break; case OP__SHOW_MAX_FRAMESIZE: printf("%u\n", block->data.stream_info.max_framesize); break; case OP__SHOW_SAMPLE_RATE: printf("%u\n", block->data.stream_info.sample_rate); break; case OP__SHOW_CHANNELS: printf("%u\n", block->data.stream_info.channels); break; case OP__SHOW_BPS: printf("%u\n", block->data.stream_info.bits_per_sample); break; case OP__SHOW_TOTAL_SAMPLES: printf("%" PRIu64 "\n", block->data.stream_info.total_samples); break; case OP__SET_MD5SUM: memcpy(block->data.stream_info.md5sum, operation->argument.streaminfo_md5.value, 16); *needs_write = true; break; case OP__SET_MIN_BLOCKSIZE: block->data.stream_info.min_blocksize = operation->argument.streaminfo_uint32.value; *needs_write = true; break; case OP__SET_MAX_BLOCKSIZE: block->data.stream_info.max_blocksize = operation->argument.streaminfo_uint32.value; *needs_write = true; break; case OP__SET_MIN_FRAMESIZE: block->data.stream_info.min_framesize = operation->argument.streaminfo_uint32.value; *needs_write = true; break; case OP__SET_MAX_FRAMESIZE: block->data.stream_info.max_framesize = operation->argument.streaminfo_uint32.value; *needs_write = true; break; case OP__SET_SAMPLE_RATE: block->data.stream_info.sample_rate = operation->argument.streaminfo_uint32.value; *needs_write = true; break; case OP__SET_CHANNELS: block->data.stream_info.channels = operation->argument.streaminfo_uint32.value; *needs_write = true; break; case OP__SET_BPS: block->data.stream_info.bits_per_sample = operation->argument.streaminfo_uint32.value; *needs_write = true; break; case OP__SET_TOTAL_SAMPLES: block->data.stream_info.total_samples = operation->argument.streaminfo_uint64.value; *needs_write = true; break; default: ok = false; FLAC__ASSERT(0); break; }; FLAC__metadata_iterator_delete(iterator); return ok; }
/* * Write Flac tag, using the level 2 flac interface */ gboolean flac_tag_write_file_tag (const ET_File *ETFile, GError **error) { const File_Tag *FileTag; GFile *file; GFileIOStream *iostream; EtFlacWriteState state; FLAC__IOCallbacks callbacks = { et_flac_read_func, et_flac_write_func, et_flac_seek_func, et_flac_tell_func, et_flac_eof_func, et_flac_write_close_func }; const gchar *filename; const gchar *filename_utf8; const gchar *flac_error_msg; FLAC__Metadata_Chain *chain; FLAC__Metadata_Iterator *iter; FLAC__StreamMetadata_VorbisComment_Entry vce_field_vendor_string; // To save vendor string gboolean vce_field_vendor_string_found = FALSE; g_return_val_if_fail (ETFile != NULL && ETFile->FileTag != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); FileTag = (File_Tag *)ETFile->FileTag->data; filename = ((File_Name *)ETFile->FileNameCur->data)->value; filename_utf8 = ((File_Name *)ETFile->FileNameCur->data)->value_utf8; /* libFLAC is able to detect (and skip) ID3v2 tags by itself */ /* Create a new chain instance to get all blocks in one time. */ chain = FLAC__metadata_chain_new (); if (chain == NULL) { flac_error_msg = FLAC__Metadata_ChainStatusString[FLAC__METADATA_CHAIN_STATUS_MEMORY_ALLOCATION_ERROR]; g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Error while opening file ‘%s’ as FLAC: %s"), filename_utf8, flac_error_msg); return FALSE; } file = g_file_new_for_path (filename); state.file = file; state.error = NULL; /* TODO: Fallback to an in-memory copy of the file for non-local files, * where creation of the GFileIOStream may fail. */ iostream = g_file_open_readwrite (file, NULL, &state.error); if (iostream == NULL) { FLAC__metadata_chain_delete (chain); g_propagate_error (error, state.error); g_object_unref (file); return FALSE; } state.istream = G_FILE_INPUT_STREAM (g_io_stream_get_input_stream (G_IO_STREAM (iostream))); state.ostream = G_FILE_OUTPUT_STREAM (g_io_stream_get_output_stream (G_IO_STREAM (iostream))); state.seekable = G_SEEKABLE (iostream); state.iostream = iostream; if (!FLAC__metadata_chain_read_with_callbacks (chain, &state, callbacks)) { const FLAC__Metadata_ChainStatus status = FLAC__metadata_chain_status (chain); flac_error_msg = FLAC__Metadata_ChainStatusString[status]; FLAC__metadata_chain_delete (chain); g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Error while opening file ‘%s’ as FLAC: %s"), filename_utf8, flac_error_msg); et_flac_write_close_func (&state); return FALSE; } /* Create a new iterator instance for the chain. */ iter = FLAC__metadata_iterator_new (); if (iter == NULL) { flac_error_msg = FLAC__Metadata_ChainStatusString[FLAC__METADATA_CHAIN_STATUS_MEMORY_ALLOCATION_ERROR]; g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Error while opening file ‘%s’ as FLAC: %s"), filename_utf8, flac_error_msg); FLAC__metadata_chain_delete (chain); et_flac_write_close_func (&state); return FALSE; } FLAC__metadata_iterator_init (iter, chain); while (FLAC__metadata_iterator_next (iter)) { const FLAC__MetadataType block_type = FLAC__metadata_iterator_get_block_type (iter); /* TODO: Modify the blocks directly, rather than deleting and * recreating. */ if (block_type == FLAC__METADATA_TYPE_VORBIS_COMMENT) { // Delete the VORBIS_COMMENT block and convert to padding. But before, save the original vendor string. /* Get block data. */ FLAC__StreamMetadata *block = FLAC__metadata_iterator_get_block(iter); FLAC__StreamMetadata_VorbisComment *vc = &block->data.vorbis_comment; if (vc->vendor_string.entry != NULL) { // Get initial vendor string, to don't alterate it by FLAC__VENDOR_STRING when saving file vce_field_vendor_string.entry = (FLAC__byte *)g_strdup ((gchar *)vc->vendor_string.entry); vce_field_vendor_string.length = strlen ((gchar *)vce_field_vendor_string.entry); vce_field_vendor_string_found = TRUE; } /* Free block data. */ FLAC__metadata_iterator_delete_block (iter, true); } else if (block_type == FLAC__METADATA_TYPE_PICTURE) { /* Delete all the PICTURE blocks, and convert to padding. */ FLAC__metadata_iterator_delete_block (iter, true); } } // // Create and insert a new VORBISCOMMENT block // { FLAC__StreamMetadata *vc_block; // For vorbis comments GList *l; // Allocate a block for Vorbis comments vc_block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); // Set the original vendor string, else will be use the version of library if (vce_field_vendor_string_found) { // must set 'copy' param to false, because the API will reuse the pointer of an empty // string (yet still return 'true', indicating it was copied); the string is free'd during // metadata_chain_delete routine FLAC__metadata_object_vorbiscomment_set_vendor_string(vc_block, vce_field_vendor_string, /*copy=*/false); } /********* * Title * *********/ vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_TITLE, FileTag->title, g_settings_get_boolean (MainSettings, "ogg-split-title")); /********** * Artist * **********/ vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_ARTIST, FileTag->artist, g_settings_get_boolean (MainSettings, "ogg-split-artist")); /**************** * Album Artist * ****************/ vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_ALBUM_ARTIST, FileTag->album_artist, g_settings_get_boolean (MainSettings, "ogg-split-artist")); /********* * Album * *********/ vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_ALBUM, FileTag->album, g_settings_get_boolean (MainSettings, "ogg-split-album")); /****************************** * Disc Number and Disc Total * ******************************/ vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_DISC_NUMBER, FileTag->disc_number, FALSE); vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_DISC_TOTAL, FileTag->disc_total, FALSE); /******** * Year * ********/ vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_DATE, FileTag->year, FALSE); /************************* * Track and Total Track * *************************/ vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_TRACK_NUMBER, FileTag->track, FALSE); vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_TRACK_TOTAL, FileTag->track_total, FALSE); /********* * Genre * *********/ vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_GENRE, FileTag->genre, g_settings_get_boolean (MainSettings, "ogg-split-genre")); /*********** * Comment * ***********/ vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_DESCRIPTION, FileTag->comment, g_settings_get_boolean (MainSettings, "ogg-split-comment")); /************ * Composer * ************/ vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_COMPOSER, FileTag->composer, g_settings_get_boolean (MainSettings, "ogg-split-composer")); /******************* * Original artist * *******************/ vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_PERFORMER, FileTag->orig_artist, g_settings_get_boolean (MainSettings, "ogg-split-original-artist")); /************* * Copyright * *************/ vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_COPYRIGHT, FileTag->copyright, FALSE); /******* * URL * *******/ vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_CONTACT, FileTag->url, FALSE); /************** * Encoded by * **************/ vc_block_append_tag (vc_block, ET_VORBIS_COMMENT_FIELD_ENCODED_BY, FileTag->encoded_by, FALSE); /************************** * Set unsupported fields * **************************/ for (l = FileTag->other; l != NULL; l = g_list_next (l)) { vc_block_append_other_tag (vc_block, (gchar *)l->data); } // Add the block to the the chain (so we don't need to free the block) FLAC__metadata_iterator_insert_block_after(iter, vc_block); } // // Create and insert PICTURE blocks // /*********** * Picture * ***********/ { EtPicture *pic = FileTag->picture; while (pic) { /* TODO: Can this ever be NULL? */ if (pic->bytes) { const gchar *violation; FLAC__StreamMetadata *picture_block; // For picture data Picture_Format format; gconstpointer data; gsize data_size; // Allocate block for picture data picture_block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PICTURE); // Type picture_block->data.picture.type = pic->type; // Mime type format = Picture_Format_From_Data(pic); /* Safe to pass a const string, according to the FLAC API * reference. */ FLAC__metadata_object_picture_set_mime_type(picture_block, (gchar *)Picture_Mime_Type_String(format), TRUE); // Description if (pic->description) { FLAC__metadata_object_picture_set_description(picture_block, (FLAC__byte *)pic->description, TRUE); } // Resolution picture_block->data.picture.width = pic->width; picture_block->data.picture.height = pic->height; picture_block->data.picture.depth = 0; /* Picture data. */ data = g_bytes_get_data (pic->bytes, &data_size); /* Safe to pass const data, if the last argument (copy) is * TRUE, according the the FLAC API reference. */ FLAC__metadata_object_picture_set_data (picture_block, (FLAC__byte *)data, (FLAC__uint32)data_size, true); if (!FLAC__metadata_object_picture_is_legal (picture_block, &violation)) { g_critical ("Created an invalid picture block: ‘%s’", violation); FLAC__metadata_object_delete (picture_block); } else { // Add the block to the the chain (so we don't need to free the block) FLAC__metadata_iterator_insert_block_after(iter, picture_block); } } pic = pic->next; } } FLAC__metadata_iterator_delete (iter); // // Prepare for writing tag // FLAC__metadata_chain_sort_padding (chain); /* Write tag. */ if (FLAC__metadata_chain_check_if_tempfile_needed (chain, true)) { EtFlacWriteState temp_state; GFile *temp_file; GFileIOStream *temp_iostream; GError *temp_error = NULL; temp_file = g_file_new_tmp ("easytag-XXXXXX", &temp_iostream, &temp_error); if (temp_file == NULL) { FLAC__metadata_chain_delete (chain); g_propagate_error (error, temp_error); et_flac_write_close_func (&state); return FALSE; } temp_state.file = temp_file; temp_state.error = NULL; temp_state.istream = G_FILE_INPUT_STREAM (g_io_stream_get_input_stream (G_IO_STREAM (temp_iostream))); temp_state.ostream = G_FILE_OUTPUT_STREAM (g_io_stream_get_output_stream (G_IO_STREAM (temp_iostream))); temp_state.seekable = G_SEEKABLE (temp_iostream); temp_state.iostream = temp_iostream; if (!FLAC__metadata_chain_write_with_callbacks_and_tempfile (chain, true, &state, callbacks, &temp_state, callbacks)) { const FLAC__Metadata_ChainStatus status = FLAC__metadata_chain_status (chain); flac_error_msg = FLAC__Metadata_ChainStatusString[status]; FLAC__metadata_chain_delete (chain); et_flac_write_close_func (&temp_state); et_flac_write_close_func (&state); g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Failed to write comments to file ‘%s’: %s"), filename_utf8, flac_error_msg); return FALSE; } if (!g_file_move (temp_file, file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &state.error)) { FLAC__metadata_chain_delete (chain); et_flac_write_close_func (&temp_state); g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Failed to write comments to file ‘%s’: %s"), filename_utf8, state.error->message); et_flac_write_close_func (&state); return FALSE; } et_flac_write_close_func (&temp_state); } else { if (!FLAC__metadata_chain_write_with_callbacks (chain, true, &state, callbacks)) { const FLAC__Metadata_ChainStatus status = FLAC__metadata_chain_status (chain); flac_error_msg = FLAC__Metadata_ChainStatusString[status]; FLAC__metadata_chain_delete (chain); et_flac_write_close_func (&state); g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Failed to write comments to file ‘%s’: %s"), filename_utf8, flac_error_msg); return FALSE; } } FLAC__metadata_chain_delete (chain); et_flac_write_close_func (&state); #ifdef ENABLE_MP3 { // Delete the ID3 tags (create a dummy ETFile for the Id3tag_... function) ET_File *ETFile_tmp = ET_File_Item_New(); File_Name *FileName_tmp = et_file_name_new (); File_Tag *FileTag_tmp = et_file_tag_new (); // Same file... FileName_tmp->value = g_strdup(filename); FileName_tmp->value_utf8 = g_strdup(filename_utf8); // Not necessary to fill 'value_ck' ETFile_tmp->FileNameList = g_list_append(NULL,FileName_tmp); ETFile_tmp->FileNameCur = ETFile_tmp->FileNameList; // With empty tag... ETFile_tmp->FileTagList = g_list_append(NULL,FileTag_tmp); ETFile_tmp->FileTag = ETFile_tmp->FileTagList; id3tag_write_file_tag (ETFile_tmp, NULL); ET_Free_File_List_Item(ETFile_tmp); } #endif return TRUE; }
/* * Read tag data from a FLAC file using the level 2 flac interface, * Note: * - if field is found but contains no info (strlen(str)==0), we don't read it */ gboolean flac_tag_read_file_tag (GFile *file, File_Tag *FileTag, GError **error) { FLAC__Metadata_Chain *chain; EtFlacReadState state; FLAC__IOCallbacks callbacks = { et_flac_read_func, NULL, /* Do not set a write callback. */ et_flac_seek_func, et_flac_tell_func, et_flac_eof_func, et_flac_read_close_func }; FLAC__Metadata_Iterator *iter; EtPicture *prev_pic = NULL; g_return_val_if_fail (file != NULL && FileTag != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); chain = FLAC__metadata_chain_new (); if (chain == NULL) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOMEM, "%s", g_strerror (ENOMEM)); return FALSE; } state.error = NULL; state.istream = g_file_read (file, NULL, &state.error); state.seekable = G_SEEKABLE (state.istream); if (!FLAC__metadata_chain_read_with_callbacks (chain, &state, callbacks)) { /* TODO: Provide a dedicated error enum corresponding to status. */ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "%s", _("Error opening FLAC file")); et_flac_read_close_func (&state); return FALSE; } iter = FLAC__metadata_iterator_new (); if (iter == NULL) { et_flac_read_close_func (&state); g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOMEM, "%s", g_strerror (ENOMEM)); return FALSE; } FLAC__metadata_iterator_init (iter, chain); while (FLAC__metadata_iterator_next (iter)) { FLAC__StreamMetadata *block; block = FLAC__metadata_iterator_get_block (iter); if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) { const FLAC__StreamMetadata_VorbisComment *vc; GHashTable *tags; GSList *strings; GHashTableIter tags_iter; gchar *key; /* Get comments from block. */ vc = &block->data.vorbis_comment; tags = populate_tag_hash_table (vc); /* Title */ if ((strings = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_TITLE))) { g_slist_foreach (strings, values_list_foreach, &FileTag->title); g_slist_free (strings); g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_TITLE); } /* Artist */ if ((strings = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_ARTIST))) { g_slist_foreach (strings, values_list_foreach, &FileTag->artist); g_slist_free (strings); g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_ARTIST); } /* Album artist. */ if ((strings = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_ALBUM_ARTIST))) { g_slist_foreach (strings, values_list_foreach, &FileTag->album_artist); g_slist_free (strings); g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_ALBUM_ARTIST); } /* Album. */ if ((strings = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_ALBUM))) { g_slist_foreach (strings, values_list_foreach, &FileTag->album); g_slist_free (strings); g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_ALBUM); } /* Disc number and total discs. */ if ((strings = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_DISC_TOTAL))) { /* Only take values from the first total discs field. */ if (!et_str_empty (strings->data)) { FileTag->disc_total = et_disc_number_to_string (atoi (strings->data)); } g_slist_free_full (strings, g_free); g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_DISC_TOTAL); } if ((strings = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_DISC_NUMBER))) { /* Only take values from the first disc number field. */ if (!et_str_empty (strings->data)) { gchar *separator; separator = strchr (strings->data, '/'); if (separator && !FileTag->disc_total) { FileTag->disc_total = et_disc_number_to_string (atoi (separator + 1)); *separator = '\0'; } FileTag->disc_number = et_disc_number_to_string (atoi (strings->data)); } g_slist_free_full (strings, g_free); g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_DISC_NUMBER); } /* Track number and total tracks. */ if ((strings = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_TRACK_TOTAL))) { /* Only take values from the first total tracks field. */ if (!et_str_empty (strings->data)) { FileTag->track_total = et_track_number_to_string (atoi (strings->data)); } g_slist_free_full (strings, g_free); g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_TRACK_TOTAL); } if ((strings = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_TRACK_NUMBER))) { /* Only take values from the first track number field. */ if (!et_str_empty (strings->data)) { gchar *separator; separator = strchr (strings->data, '/'); if (separator && !FileTag->track_total) { FileTag->track_total = et_track_number_to_string (atoi (separator + 1)); *separator = '\0'; } FileTag->track = et_track_number_to_string (atoi (strings->data)); } g_slist_free_full (strings, g_free); g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_TRACK_NUMBER); } /* Year. */ if ((strings = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_DATE))) { g_slist_foreach (strings, values_list_foreach, &FileTag->year); g_slist_free (strings); g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_DATE); } /* Genre. */ if ((strings = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_GENRE))) { g_slist_foreach (strings, values_list_foreach, &FileTag->genre); g_slist_free (strings); g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_GENRE); } /* Comment. */ { GSList *descs; GSList *comments; descs = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_DESCRIPTION); comments = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_COMMENT); /* Prefer DESCRIPTION, as it is part of the spec. */ if (descs && !comments) { g_slist_foreach (descs, values_list_foreach, &FileTag->comment); } else if (descs && comments) { /* Mark the file as modified, so that comments are written * to the DESCRIPTION field on saving. */ FileTag->saved = FALSE; g_slist_foreach (descs, values_list_foreach, &FileTag->comment); g_slist_foreach (comments, values_list_foreach, &FileTag->comment); } else if (comments) { FileTag->saved = FALSE; g_slist_foreach (comments, values_list_foreach, &FileTag->comment); } g_slist_free (descs); g_slist_free (comments); g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_DESCRIPTION); g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_COMMENT); } /* Composer. */ if ((strings = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_COMPOSER))) { g_slist_foreach (strings, values_list_foreach, &FileTag->composer); g_slist_free (strings); g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_COMPOSER); } /* Original artist. */ if ((strings = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_PERFORMER))) { g_slist_foreach (strings, values_list_foreach, &FileTag->orig_artist); g_slist_free (strings); g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_PERFORMER); } /* Copyright. */ if ((strings = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_COPYRIGHT))) { g_slist_foreach (strings, values_list_foreach, &FileTag->copyright); g_slist_free (strings); g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_COPYRIGHT); } /* URL. */ if ((strings = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_CONTACT))) { g_slist_foreach (strings, values_list_foreach, &FileTag->url); g_slist_free (strings); g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_CONTACT); } /* Encoded by. */ if ((strings = g_hash_table_lookup (tags, ET_VORBIS_COMMENT_FIELD_ENCODED_BY))) { g_slist_foreach (strings, values_list_foreach, &FileTag->encoded_by); g_slist_free (strings); g_hash_table_remove (tags, ET_VORBIS_COMMENT_FIELD_ENCODED_BY); } /* Save unsupported fields. */ g_hash_table_iter_init (&tags_iter, tags); while (g_hash_table_iter_next (&tags_iter, (gpointer *)&key, (gpointer *)&strings)) { GSList *l; for (l = strings; l != NULL; l = g_slist_next (l)) { FileTag->other = g_list_prepend (FileTag->other, g_strconcat (key, "=", l->data, NULL)); } g_slist_free_full (strings, g_free); g_hash_table_iter_remove (&tags_iter); } if (FileTag->other) { FileTag->other = g_list_reverse (FileTag->other); } /* The hash table should now only contain keys. */ g_hash_table_unref (tags); } else if (block->type == FLAC__METADATA_TYPE_PICTURE) { /* Picture. */ const FLAC__StreamMetadata_Picture *p; GBytes *bytes; EtPicture *pic; /* Get picture data from block. */ p = &block->data.picture; bytes = g_bytes_new (p->data, p->data_length); pic = et_picture_new (p->type, (const gchar *)p->description, 0, 0, bytes); g_bytes_unref (bytes); if (!prev_pic) { FileTag->picture = pic; } else { prev_pic->next = pic; } prev_pic = pic; } } FLAC__metadata_iterator_delete (iter); FLAC__metadata_chain_delete (chain); et_flac_read_close_func (&state); #ifdef ENABLE_MP3 /* If no FLAC vorbis tag found : we try to get the ID3 tag if it exists * (but it will be deleted when rewriting the tag) */ if ( FileTag->title == NULL && FileTag->artist == NULL && FileTag->album_artist == NULL && FileTag->album == NULL && FileTag->disc_number == NULL && FileTag->disc_total == NULL && FileTag->year == NULL && FileTag->track == NULL && FileTag->track_total == NULL && FileTag->genre == NULL && FileTag->comment == NULL && FileTag->composer == NULL && FileTag->orig_artist == NULL && FileTag->copyright == NULL && FileTag->url == NULL && FileTag->encoded_by == NULL && FileTag->picture == NULL) { id3tag_read_file_tag (file, FileTag, NULL); // If an ID3 tag has been found (and no FLAC tag), we mark the file as // unsaved to rewrite a flac tag. if ( FileTag->title != NULL || FileTag->artist != NULL || FileTag->album_artist != NULL || FileTag->album != NULL || FileTag->disc_number != NULL || FileTag->disc_total != NULL || FileTag->year != NULL || FileTag->track != NULL || FileTag->track_total != NULL || FileTag->genre != NULL || FileTag->comment != NULL || FileTag->composer != NULL || FileTag->orig_artist != NULL || FileTag->copyright != NULL || FileTag->url != NULL || FileTag->encoded_by != NULL || FileTag->picture != NULL) { FileTag->saved = FALSE; } } #endif return TRUE; }
FLAC__bool do_shorthand_operation__add_seekpoints(const char *filename, FLAC__Metadata_Chain *chain, const char *specification, FLAC__bool *needs_write) { FLAC__bool ok = true, found_seektable_block = false; FLAC__StreamMetadata *block = 0; FLAC__Metadata_Iterator *iterator = FLAC__metadata_iterator_new(); FLAC__uint64 total_samples = 0; unsigned sample_rate = 0; if(0 == iterator) die("out of memory allocating iterator"); FLAC__metadata_iterator_init(iterator, chain); do { block = FLAC__metadata_iterator_get_block(iterator); if(block->type == FLAC__METADATA_TYPE_STREAMINFO) { sample_rate = block->data.stream_info.sample_rate; total_samples = block->data.stream_info.total_samples; } else if(block->type == FLAC__METADATA_TYPE_SEEKTABLE) found_seektable_block = true; } while(!found_seektable_block && FLAC__metadata_iterator_next(iterator)); if(total_samples == 0) { fprintf(stderr, "%s: ERROR: cannot add seekpoints because STREAMINFO block does not specify total_samples\n", filename); return false; } if(!found_seektable_block) { /* create a new block */ block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_SEEKTABLE); if(0 == block) die("out of memory allocating SEEKTABLE block"); while(FLAC__metadata_iterator_prev(iterator)) ; if(!FLAC__metadata_iterator_insert_block_after(iterator, block)) { print_error_with_chain_status(chain, "%s: ERROR: adding new SEEKTABLE block to metadata", filename); FLAC__metadata_object_delete(block); return false; } /* iterator is left pointing to new block */ FLAC__ASSERT(FLAC__metadata_iterator_get_block(iterator) == block); } FLAC__metadata_iterator_delete(iterator); FLAC__ASSERT(0 != block); FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_SEEKTABLE); if(!grabbag__seektable_convert_specification_to_template(specification, /*only_explicit_placeholders=*/false, total_samples, sample_rate, block, /*spec_has_real_points=*/0)) { fprintf(stderr, "%s: ERROR (internal) preparing seektable with seekpoints\n", filename); return false; } ok = populate_seekpoint_values(filename, block, needs_write); if(ok) (void) FLAC__format_seektable_sort(&block->data.seek_table); return ok; }