Hello Max, as promised i send the patch against the latest git sources (cloned on Mo 23 Feb 2009 10:57:36 CET). This will add support for embedded cue sheets in flac files. Please send me any comments for improvement, critisim or bugs.
I have commented some parts where i think more work has to be done. Whatsmore i'm not so happy with it overall, to me it seems more like a hack rather then something clean.. anyway for the moment it's the best i can do. Hope you like it anyway. ;) Any hints where i could get started to get the subsongs proper tagged? Max Kellermann wrote: > If you're developing only for the new libflac API > (FLAC_API_VERSION_CURRENT > 7), then you don't need any macros, but be > sure your code won't be compiled on older libflac APIs. There are > still people around using those ancient versions.. For the moment my patch isn't #defined for FLAC > 7 but i'll change that asap. One last question: Is there a way to access song->tag from the decoder plugin (flac_plugin.c)? Trying access decoder->stream_tag or decoder->decoder_tag doesn't work/wouldn't even compile. Thanks and regards, Jochen
diff --git a/src/decoder/_flac_common.c b/src/decoder/_flac_common.c index 63dc6e1..7dd0b8a 100644 --- a/src/decoder/_flac_common.c +++ b/src/decoder/_flac_common.c @@ -345,3 +345,97 @@ flac_common_write(struct flac_data *data, const FLAC__Frame * frame, return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; } + +char* +flac_cue_track( const char* pathname, + const uint8_t tnum) +{ + FLAC__StreamMetadata* cs = FLAC__metadata_object_new(FLAC__METADATA_TYPE_CUESHEET); + + FLAC__metadata_get_cuesheet(pathname, &cs); + + if (cs == NULL) + return NULL; + + if (cs->data.cue_sheet.num_tracks <= 1) + { + FLAC__metadata_object_delete(cs); + return NULL; + } + + if (tnum > 0 && tnum < cs->data.cue_sheet.num_tracks) + { + char* track = g_malloc(sizeof(char)); + if (tnum < 10) + sprintf(track, "track_00%ld.flac", (long)tnum); + + else if (tnum >= 10) + sprintf(track, "track_0%ld.flac", (long)tnum); + + else if (tnum >= 100) + sprintf(track, "track_%ld.flac", (long)tnum); + + FLAC__metadata_object_delete(cs); + + return track; + } + else + { + FLAC__metadata_object_delete(cs); + return NULL; + } +} + +const char* +flac_get_basename(const char* pathname) +{ + uint8_t tnum = 0; + char* ptr = NULL; + char* ptr_ = NULL; + char* ptrdot = NULL; + const char* filename; + + FLAC__StreamMetadata* cs; + + cs = FLAC__metadata_object_new(FLAC__METADATA_TYPE_CUESHEET); + + FLAC__metadata_get_cuesheet(pathname, &cs); + + filename = strdup(pathname); + + if (cs == NULL) + { + // last '/' in pathname: ++ptr should now start at 'track_xxx.flac' + ptr = strrchr(filename, '/'); + *ptr = '\0'; + + // underscore in 'track_xxx.flac'; ++ptr_ = 'xxx.flac' + ptr_ = strrchr(++ptr, '_'); + *ptr_ = '\0'; + + // ++ptrdot should start at 'flac' + ptrdot = strrchr(++ptr_, '.'); + *ptrdot = '\0'; + + tnum = strtol(ptr_, NULL, 10); + + if (strcmp(ptr, "track") == 0 && + strcmp(++ptrdot, "flac") == 0 && + tnum > 0 && tnum < 255) + { + g_debug("return filename: %s", filename); + return filename; + } + else + return NULL; + } + else + return pathname; +} + +void +flac_cue_tag_song(struct tag* tag, const uint8_t tnum) +{ + //tag = NULL; + //tnum = 0; +} diff --git a/src/decoder/_flac_common.h b/src/decoder/_flac_common.h index e876c35..cbbc496 100644 --- a/src/decoder/_flac_common.h +++ b/src/decoder/_flac_common.h @@ -175,4 +175,17 @@ FLAC__StreamDecoderWriteStatus flac_common_write(struct flac_data *data, const FLAC__Frame * frame, const FLAC__int32 *const buf[]); +char* +flac_cue_track( const char* pathname, + const uint8_t tnum); + +const char* +flac_get_basename(const char* pathname); + +void +flac_check_cue_track(struct tag* tag, const uint8_t tnum); + +void +flac_cue_tag_song(struct tag* tag, const uint8_t tnum); + #endif /* _FLAC_COMMON_H */ diff --git a/src/decoder/flac_plugin.c b/src/decoder/flac_plugin.c index ce056d2..6a44479 100644 --- a/src/decoder/flac_plugin.c +++ b/src/decoder/flac_plugin.c @@ -382,6 +382,233 @@ flac_decode(struct decoder * decoder, struct input_stream *input_stream) #ifndef HAVE_OGGFLAC +/* + * @brief Open a flac file for decoding + * @param fname filename on fs + */ +static void +flac_filedecode_internal(struct decoder* decoder, + const char* fname, + bool is_ogg) +{ + bool cuemode = false; + + uint8_t tnum = 0; + FLAC__uint64 t_start = 0; + FLAC__uint64 t_end = 0; + FLAC__uint64 track_time = 0; + FLAC__StreamMetadata* cs = NULL; + + flac_decoder *flac_dec; + struct flac_data data; + const char *err = NULL; + + const char* filename; + + /* we will do this whole voodoo only if we really + * have something like /foobar.flac/track_xxx.flac + * where xxx = 001, 002, and so on + */ + if ((filename = flac_get_basename(fname)) != NULL) + cuemode = true; + else + filename = fname; + + g_debug("flac_plugin filename: %s", filename); + + if (cuemode) + { + /* find last occurrence of '_' in fname + * which is hopefully something like track_xxx.flac + * another/better way would be to use tag struct + */ + char* ptr = strrchr(fname, '_'); + + // copy ascii tracknumber to int + char vtrack[3]; + strncpy(vtrack, ++ptr, 3); + vtrack[3] = '\0'; + tnum = strtol(vtrack, NULL, 10); + + cs = FLAC__metadata_object_new(FLAC__METADATA_TYPE_CUESHEET); + + FLAC__metadata_get_cuesheet(filename, &cs); + + if (cs != NULL) + { + if (cs->data.cue_sheet.tracks != NULL && (tnum <= cs->data.cue_sheet.num_tracks - 1)) + { + t_start = cs->data.cue_sheet.tracks[tnum - 1].offset; + t_end = cs->data.cue_sheet.tracks[tnum].offset - 1; + track_time = cs->data.cue_sheet.tracks[tnum].offset - 1 + - cs->data.cue_sheet.tracks[tnum - 1].offset; + } + + FLAC__metadata_object_delete(cs); + } + else + return; + } + + if (!(flac_dec = flac_new())) + return; + + flac_data_init(&data, decoder, NULL); + +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + if(!FLAC__stream_decoder_set_metadata_respond(flac_dec, FLAC__METADATA_TYPE_VORBIS_COMMENT)) + { + g_debug("Failed to set metadata respond\n"); + } +#endif + + + if (is_ogg) + { + if (FLAC__stream_decoder_init_ogg_file( flac_dec, + filename, + flac_write_cb, + flacMetadata, + flac_error_cb, + (void*) &data ) + != FLAC__STREAM_DECODER_INIT_STATUS_OK ) + { + err = "doing Ogg init()"; + goto fail; + } + } + else + { + if (FLAC__stream_decoder_init_file( flac_dec, + filename, + flac_write_cb, + flacMetadata, + flac_error_cb, + (void*) &data ) + != FLAC__STREAM_DECODER_INIT_STATUS_OK ) + { + err = "doing init()"; + goto fail; + } + } + + if (!flac_process_metadata(flac_dec)) + { + err = "problem reading metadata"; + goto fail; + } + + if (!audio_format_valid(&data.audio_format)) + { + g_warning("Invalid audio format: %u:%u:%u\n", + data.audio_format.sample_rate, + data.audio_format.bits, + data.audio_format.channels); + goto fail; + } + + // set track time (order is important: after stream init) + if (cuemode) + { + data.total_time = (float)(track_time / data.audio_format.sample_rate); + data.position = 0; + } + + decoder_initialized(decoder, &data.audio_format, + true, data.total_time); + + // seek to song start (order is important: after decoder init) + if (cuemode) + { + flac_seek_absolute(flac_dec, (FLAC__uint64)t_start); + } + + while (true) + { + if (!flac_process_single(flac_dec)) + break; + + // we only need to break at the end of track if we are in "cue mode" + if (cuemode) + { + if (data.time >= data.total_time) + { + flacPrintErroredState(flac_get_state(flac_dec)); + flac_finish(flac_dec); + } + + if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) + { + FLAC__uint64 seek_sample = t_start + + (decoder_seek_where(decoder) * data.audio_format.sample_rate); + + if (seek_sample >= t_start && seek_sample <= t_end && data.total_time > 30) + { + if (flac_seek_absolute(flac_dec, (FLAC__uint64)seek_sample)) + { + data.time = (float)(seek_sample - t_start) / + data.audio_format.sample_rate; + data.position = 0; + + decoder_command_finished(decoder); + } + else + decoder_seek_error(decoder); + } + } + else if (flac_get_state(flac_dec) == flac_decoder_eof) + break; + } + else + { + if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) + { + FLAC__uint64 seek_sample = decoder_seek_where(decoder) * + data.audio_format.sample_rate + 0.5; + if (flac_seek_absolute(flac_dec, seek_sample)) + { + data.time = ((float)seek_sample) / + data.audio_format.sample_rate; + data.position = 0; + decoder_command_finished(decoder); + } + else + decoder_seek_error(decoder); + + } + else if (flac_get_state(flac_dec) == flac_decoder_eof) + break; + } + } + + if (decoder_get_command(decoder) != DECODE_COMMAND_STOP) + { + flacPrintErroredState(flac_get_state(flac_dec)); + flac_finish(flac_dec); + } + +fail: + if (data.replay_gain_info) + replay_gain_info_free(data.replay_gain_info); + + if (flac_dec) + flac_delete(flac_dec); + + if (err) + g_warning("%s\n", err); +} + +/* + * @brief wrapper function for + * flac_filedecode_internal method + * for decoding without ogg + */ +static void +flac_filedecode(struct decoder *decoder, const char *fname) +{ + flac_filedecode_internal(decoder, fname, false); +} + static bool oggflac_init(G_GNUC_UNUSED const struct config_param *param) { @@ -438,6 +665,10 @@ oggflac_decode(struct decoder *decoder, struct input_stream *input_stream) if (ogg_stream_type_detect(input_stream) != FLAC) return; + /* should we check for an embedded cue sheet here in order + * to use flac_filedecode_internal? + */ + /* rewind the stream, because ogg_stream_type_detect() has moved it */ input_stream_seek(input_stream, 0, SEEK_SET); @@ -476,7 +707,8 @@ static const char *const flac_mime_types[] = { const struct decoder_plugin flac_decoder_plugin = { .name = "flac", .stream_decode = flac_decode, + .file_decode = flac_filedecode, .tag_dup = flac_tag_dup, .suffixes = flac_suffixes, - .mime_types = flac_mime_types + .mime_types = flac_mime_types, }; diff --git a/src/input_file.c b/src/input_file.c index b29a412..cd3f2e7 100644 --- a/src/input_file.c +++ b/src/input_file.c @@ -25,6 +25,9 @@ #include <string.h> #include <glib.h> +#include "ls.h" +#include "decoder/_flac_common.h" + #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "input_file" @@ -33,11 +36,20 @@ input_file_open(struct input_stream *is, const char *filename) { int fd, ret; struct stat st; + const char* pathname; if (filename[0] != '/') return false; - fd = open(filename, O_RDONLY); + if (strcmp(uri_get_suffix(filename), "flac") == 0) + { + if ((pathname = flac_get_basename(filename)) == NULL) + { + pathname = filename; + } + } + + fd = open(pathname, O_RDONLY); if (fd < 0) { is->error = errno; return false; @@ -53,7 +65,7 @@ input_file_open(struct input_stream *is, const char *filename) } if (!S_ISREG(st.st_mode)) { - g_debug("Not a regular file: %s\n", filename); + g_debug("Not a regular file: %s\n", pathname); is->error = EINVAL; close(fd); return false; diff --git a/src/update.c b/src/update.c index 17059ff..9599e9a 100644 --- a/src/update.c +++ b/src/update.c @@ -35,6 +35,9 @@ #include "stats.h" #include "main.h" #include "config.h" +#include "tag.h" + +#include "decoder/_flac_common.h" #ifdef ENABLE_SQLITE #include "sticker.h" @@ -368,6 +371,57 @@ update_regular_file(struct directory *directory, if (suffix == NULL) return; + if (strcmp(suffix, "flac") == 0) + { + const char* pathname = map_directory_child_fs(directory, name); + const char* filename; + + if ((filename = flac_get_basename(pathname)) != NULL) + { + const struct decoder_plugin *plugin; + const char* vtrack; + uint8_t tnum = 0; + + struct directory* flacdir = make_subdir(directory, name); + + while ((vtrack = flac_cue_track(pathname, ++tnum)) != NULL) + { + struct song *song = songvec_find(&flacdir->songs, vtrack); + + if (song == NULL) + { + song = song_file_new(vtrack, flacdir); + if (song == NULL) + return; + + plugin = decoder_plugin_from_suffix(suffix, false); + + song->tag = plugin->tag_dup(pathname); + + // subfunction + //flac_cue_tag_song(song->tag); + { + char tracknum[3]; + + snprintf( tracknum, + sizeof(tracknum), "%u", + tnum); + + tag_add_item( song->tag, + TAG_ITEM_TRACK, + tracknum); + } + + songvec_add(&flacdir->songs, song); + } + } + + modified = true; + + return; + } + } + if (decoder_plugin_from_suffix(suffix, false) != NULL) { struct song *song = songvec_find(&directory->songs, name);
signature.asc
Description: OpenPGP digital signature
------------------------------------------------------------------------------ Open Source Business Conference (OSBC), March 24-25, 2009, San Francisco, CA -OSBC tackles the biggest issue in open source: Open Sourcing the Enterprise -Strategies to boost innovation and cut costs with open source participation -Receive a $600 discount off the registration fee with the source code: SFAD http://p.sf.net/sfu/XcvMzF8H
_______________________________________________ Musicpd-dev-team mailing list Musicpd-dev-team@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/musicpd-dev-team