chiark / gitweb /
typo
[disorder] / plugins / tracklength.c
index 8d21ad8b535d0c5d1ff4d568d0a0709a21fa5606..42f9432918e1aaeffb04c36e12b1105bc0477505 100644 (file)
@@ -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
 
 #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;
@@ -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 }
@@ -262,4 +263,3 @@ comment-column:40
 fill-column:79
 End:
 */
-/* arch-tag:fb794bf679375f55bf26fbb7a96a39de */