/*
* 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
+ * 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
- * the Free Software Foundation; either version 2 of the License, or
+ * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
* You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/** @file plugins/tracklength.c
+ * @brief Plugin to compute track lengths
+ *
+ * Currently implements MP3, OGG, FLAC and WAV.
*/
#include <config.h>
#include <vorbis/vorbisfile.h>
#include <mad.h>
+/* libFLAC has had an API change and stupidly taken away the old API */
+#if HAVE_FLAC_FILE_DECODER_H
+# include <FLAC/file_decoder.h>
+#else
+# include <FLAC/stream_decoder.h>
+#define FLAC__FileDecoder FLAC__StreamDecoder
+#define FLAC__FileDecoderState FLAC__StreamDecoderState
+#endif
+
#include <disorder.h>
#include "madshim.h"
+#include "wav.h"
static void *mmap_file(const char *path, size_t *lengthp) {
int fd;
}
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 }
fill-column:79
End:
*/
-/* arch-tag:fb794bf679375f55bf26fbb7a96a39de */