X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/763d5e6ad88ef3ba1cd1d7742d060e4f1e54c6b8..64589273d54ee8813703818d18a680d7feebe3a5:/plugins/tracklength.c diff --git a/plugins/tracklength.c b/plugins/tracklength.c index 100b218..42f9432 100644 --- a/plugins/tracklength.c +++ b/plugins/tracklength.c @@ -1,6 +1,6 @@ /* * This file is part of DisOrder. - * Portions copyright (C) 2004, 2005 Richard Kettlewell (see also below) + * Copyright (C) 2004, 2005, 2007 Richard Kettlewell * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,10 +32,20 @@ #include #include +/* libFLAC has had an API change and stupidly taken away the old API */ +#if HAVE_FLAC_FILE_DECODER_H +# include +#else +# include +#define FLAC__FileDecoder FLAC__StreamDecoder +#define FLAC__FileDecoderState FLAC__StreamDecoderState +#endif + #include #include "madshim.h" +#include "wav.h" static void *mmap_file(const char *path, size_t *lengthp) { int fd; @@ -95,141 +105,132 @@ error: } static long tl_wav(const char *path) { - size_t length; - void *base; - long duration = -1; - unsigned char *ptr; - unsigned n, m, data_bytes = 0, samples_per_second = 0; - unsigned n_channels = 0, bits_per_sample = 0, sample_point_size; - unsigned sample_frame_size, n_samples; + struct wavfile f[1]; + int err, sample_frame_size; + long duration; - /* Sources: - * - * http://www.technology.niagarac.on.ca/courses/comp530/WavFileFormat.html - * http://www.borg.com/~jglatt/tech/wave.htm - * http://www.borg.com/~jglatt/tech/aboutiff.htm - * - * These files consists of a header followed by chunks. - * Multibyte values are little-endian. - * - * 12 byte file header: - * offset size meaning - * 00 4 'RIFF' - * 04 4 length of rest of file - * 08 4 'WAVE' - * - * The length includes 'WAVE' but excludes the 1st 8 bytes. - * - * Chunk header: - * 00 4 chunk ID - * 04 4 length of rest of chunk - * - * The stated length may be odd, if so then there is an implicit padding byte - * appended to the chunk to make it up to an even length (someone wasn't - * think about 32/64-bit worlds). - * - * Also some files seem to have extra stuff at the end of chunks that nobody - * I know of documents. Go figure, but check the length field rather than - * deducing the length from the ID. - * - * Format chunk: - * 00 4 'fmt' - * 04 4 length of rest of chunk - * 08 2 compression (1 = none) - * 0a 2 number of channels - * 0c 4 samples/second - * 10 4 average bytes/second, = (samples/sec) * (bytes/sample) - * 14 2 bytes/sample - * 16 2 bits/sample point - * - * 'sample' means 'sample frame' above, i.e. a sample point for each channel. - * - * Data chunk: - * 00 4 'data' - * 04 4 length of rest of chunk - * 08 ... data - * - * There is only allowed to be one data chunk. Some people violate this; we - * shall encourage people to fix their broken WAV files by not supporting - * this violation and because it's easier. - * - * As to the encoding of the data: - * - * Firstly, samples up to 8 bits in size are unsigned, larger samples are - * signed. Madness. - * - * Secondly sample points are stored rounded up to a multiple of 8 bits in - * size. Marginally saner. - * - * Written as a single word (of 8, 16, 24, whatever bits) the padding to - * implement this happens at the right hand (least significant) end. - * e.g. assuming a 9 bit sample: - * - * | padded sample word | - * | 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 | - * | 8 7 6 5 4 3 2 1 0 - - - - - - - | - * - * But this is a little-endian file format so the least significant byte is - * the first, which means that the padding is "between" the bits if you - * imagine them in their usual order: - * - * | first byte | second byte | - * | 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | - * | 0 - - - - - - - | 8 7 6 5 4 3 2 1 | - * - * Sample points are grouped into sample frames, consisting of as many - * samples points as their are channels. It seems that there are standard - * orderings of different channels. - * - * Given all of the above all we need to do is pick up some numbers from the - * format chunk, and the length of the data chunk, and do some arithmetic. - */ - if(!(base = mmap_file(path, &length))) return -1; -#define get16(p) ((p)[0] + 256 * (p)[1]) -#define get32(p) ((p)[0] + 256 * ((p)[1] + 256 * ((p)[2] + 256 * (p)[3]))) - ptr = base; - if(length < 12) goto out; - if(strncmp((char *)ptr, "RIFF", 4)) goto out; /* wrong type */ - n = get32(ptr + 4); /* file length */ - if(n > length - 8) goto out; /* truncated */ - ptr += 8; /* skip file header */ - if(n < 4 || strncmp((char *)ptr, "WAVE", 4)) goto out; /* wrong type */ - ptr += 4; /* skip 'WAVE' */ - n -= 4; - while(n >= 8) { - m = get32(ptr + 4); /* chunk length */ - if(m > n - 8) goto out; /* truncated */ - if(!strncmp((char *)ptr, "fmt ", 4)) { - if(samples_per_second) goto out; /* duplicate format chunk! */ - n_channels = get16(ptr + 0x0a); - samples_per_second = get32(ptr + 0x0c); - bits_per_sample = get16(ptr + 0x16); - if(!samples_per_second) goto out; /* bogus! */ - } else if(!strncmp((char *)ptr, "data", 4)) { - if(data_bytes) goto out; /* multiple data chunks! */ - data_bytes = m; /* remember data size */ - } - m += 8; /* include chunk header */ - ptr += m; /* skip chunk */ - n -= m; + if((err = wav_init(f, path))) { + disorder_error(err, "error opening %s", path); + return -1; } - sample_point_size = (bits_per_sample + 7) / 8; - sample_frame_size = sample_point_size * n_channels; - if(!sample_frame_size) goto out; /* bogus or overflow */ - n_samples = data_bytes / sample_frame_size; - duration = (n_samples + samples_per_second - 1) / samples_per_second; -out: - munmap(base, length); + sample_frame_size = (f->bits + 7) / 8 * f->channels; + if(sample_frame_size) { + const long long n_samples = f->datasize / sample_frame_size; + duration = (n_samples + f->rate - 1) / f->rate; + } else + duration = -1; + wav_destroy(f); return duration; } +/* libFLAC's "simplified" interface is rather heavyweight... */ + +struct flac_state { + long duration; + const char *path; +}; + +static void flac_metadata(const FLAC__FileDecoder attribute((unused)) *decoder, + const FLAC__StreamMetadata *metadata, + void *client_data) { + struct flac_state *const state = client_data; + const FLAC__StreamMetadata_StreamInfo *const stream_info + = &metadata->data.stream_info; + + if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO) + /* FLAC uses 0 to mean unknown and conveniently so do we */ + state->duration = (stream_info->total_samples + + stream_info->sample_rate - 1) + / stream_info->sample_rate; +} + +static void flac_error(const FLAC__FileDecoder attribute((unused)) *decoder, + FLAC__StreamDecoderErrorStatus status, + void *client_data) { + const struct flac_state *const state = client_data; + + disorder_error(0, "error decoding %s: %s", state->path, + FLAC__StreamDecoderErrorStatusString[status]); +} + +static FLAC__StreamDecoderWriteStatus flac_write + (const FLAC__FileDecoder attribute((unused)) *decoder, + const FLAC__Frame attribute((unused)) *frame, + const FLAC__int32 attribute((unused)) *const buffer_[], + void attribute((unused)) *client_data) { + const struct flac_state *const state = client_data; + + if(state->duration >= 0) + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + else + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +static long tl_flac(const char *path) { + struct flac_state state[1]; + + state->duration = -1; /* error */ + state->path = path; +#if HAVE_FLAC_FILE_DECODER_H + { + FLAC__FileDecoder *fd = 0; + FLAC__FileDecoderState fs; + + if(!(fd = FLAC__file_decoder_new())) { + disorder_error(0, "FLAC__file_decoder_new failed"); + goto fail; + } + if(!(FLAC__file_decoder_set_filename(fd, path))) { + disorder_error(0, "FLAC__file_set_filename failed"); + goto fail; + } + FLAC__file_decoder_set_metadata_callback(fd, flac_metadata); + FLAC__file_decoder_set_error_callback(fd, flac_error); + FLAC__file_decoder_set_write_callback(fd, flac_write); + FLAC__file_decoder_set_client_data(fd, state); + if((fs = FLAC__file_decoder_init(fd))) { + disorder_error(0, "FLAC__file_decoder_init: %s", + FLAC__FileDecoderStateString[fs]); + goto fail; + } + FLAC__file_decoder_process_until_end_of_metadata(fd); +fail: + if(fd) + FLAC__file_decoder_delete(fd); + } +#else + { + FLAC__StreamDecoder *sd = 0; + FLAC__StreamDecoderInitStatus is; + + if(!(sd = FLAC__stream_decoder_new())) { + disorder_error(0, "FLAC__stream_decoder_new failed"); + goto fail; + } + if((is = FLAC__stream_decoder_init_file(sd, path, flac_write, flac_metadata, + flac_error, state))) { + disorder_error(0, "FLAC__stream_decoder_init_file %s: %s", + path, FLAC__StreamDecoderInitStatusString[is]); + goto fail; + } + FLAC__stream_decoder_process_until_end_of_metadata(sd); +fail: + if(sd) + FLAC__stream_decoder_delete(sd); + } +#endif + return state->duration; +} + static const struct { const char *ext; long (*fn)(const char *path); } file_formats[] = { + { ".FLAC", tl_flac }, { ".MP3", tl_mp3 }, { ".OGG", tl_ogg }, { ".WAV", tl_wav }, + { ".flac", tl_flac }, { ".mp3", tl_mp3 }, { ".ogg", tl_ogg }, { ".wav", tl_wav }