commit - 893f7eed87919d0f7f4e3865207d4fe9556e7ea9
commit + f3cf8697f3f00c468de6b3f03912c97c799b6fa0
blob - caf21c548f295f0990d283b0a6ae722a5557384c
blob + c1084d0128ba152850f016cf6b617b0658e44550
--- .gitignore
+++ .gitignore
config.h.old
config.log
config.log.old
+songmeta/songmeta
web/amused-web
**/*.d
**/*.o
blob - 7c788e63407b076082a0a781bdd5ad43f3b4aebc
blob + 74cfd874337d88ae6683d4f7b40cf1d92d18c792
--- Makefile
+++ Makefile
-.PHONY: all web clean distclean install install-web
+.PHONY: all songmeta web clean distclean install install-songmeta install-web
VERSION = 0.15
PROG = amused
${CC} -o $@ ${OBJS} ${LDFLAGS} ${LDADD} ${LDADD_LIB_IMSG} \
${LDADD_DECODERS} ${LDADD_LIB_SOCKET} ${LDADD_BACKEND}
+songmeta:
+ ${MAKE} -C songmeta
+
web:
${MAKE} -C web
clean:
rm -f ${OBJS} ${OBJS:.o=.d} ${PROG}
+ -${MAKE} -C songmeta clean
-${MAKE} -C web clean
distclean: clean
${INSTALL_PROGRAM} ${PROG} ${DESTDIR}${BINDIR}
${INSTALL_MAN} amused.1 ${DESTDIR}${MANDIR}/man1/${PROG}.1
+install-songmeta:
+ ${MAKE} -C songmeta install
+
install-web:
${MAKE} -C web install
cd .dist/${DISTNAME} && chmod 755 configure
cd .dist/${DISTNAME} && cp -R ../../contrib . && \
chmod 755 contrib/amused-monitor
+ ${MAKE} -C songmeta DESTDIR=${PWD}/.dist/${DISTNAME}/songmeta dist
${MAKE} -C web DESTDIR=${PWD}/.dist/${DISTNAME}/web dist
cd .dist && tar zcf ../$@ ${DISTNAME}
rm -rf .dist/
blob - /dev/null
blob + 6d8f19b6aef88be092c5a42e6bc5674d2fe1c5ef (mode 644)
--- /dev/null
+++ songmeta/Makefile
+.PHONY: all clean
+
+PROG = songmeta
+SRCS = songmeta.c flac.c id3v1.c id3v2.c ogg.c opus.c text.c \
+ vorbis.c ../compats.c
+
+OBJS = ${SRCS:.c=.o}
+
+DISTFILES = Makefile songmeta.c flac.c id3v1.c id3v2.c ogg.c opus.c \
+ text.c vorbis.c
+
+all: ${PROG}
+
+../Makefile.configure ../config.h: ../configure ../tests.c
+ @echo "$@ is out of date; please run ../configure"
+ @exit 1
+
+include ../Makefile.configure
+
+# --- targets ---
+
+${PROG}: ${OBJS}
+ ${CC} -o $@ ${OBJS} ${LDFLAGS} ${LDADD} ${LDADD_LIB_IMSG} \
+ ${LDADD_LIB_SOCKET}
+
+clean:
+ rm -f ${OBJS} ${OBJS:.o=.d} ${PROG}
+
+distclean: clean
+
+install:
+ mkdir -p ${DESTDIR}${BINDIR}
+ mkdir -p ${DESTDIR}${MANDIR}/man1
+ ${INSTALL_PROGRAM} ${PROG} ${DESTDIR}${BINDIR}
+# ${INSTALL_MAN} songmeta.1 ${DESTDIR}${MANDIR}/man1/${PROG}.1
+
+install-local:
+ mkdir -p ${HOME}/bin
+ ${INSTALL_PROGRAM} ${PROG} ${HOME}/bin
+
+uninstall:
+ rm ${DESTDIR}${BINDIR}/${PROG}
+ rm ${DESTDIR}${MANDIR}/man1/${PROG}.1
+
+.c.o:
+ ${CC} -I../ ${CFLAGS} -c $< -o $@
+
+# --- maintainer targets ---
+
+dist:
+ mkdir -p ${DESTDIR}/
+ ${INSTALL} -m 0644 ${DISTFILES} ${DESTDIR}/
+
+# --- dependency management ---
+
+# these .d files are produced during the first build if the compiler
+# supports it.
+
+-include flac.d
+-include id3v1.d
+-include id3v2.d
+-include ogg.d
+-include opus.d
+-include songmeta.d
+-include text.d
+-include vorbis.d
+-include ../compats.d
blob - /dev/null
blob + bcb339f7a668484c91922c40ac260bfdc0e0f4f8 (mode 644)
--- /dev/null
+++ songmeta/flac.c
+/*
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * FLAC metadata handling.
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "songmeta.h"
+
+int
+flac_dump(FILE *fp, const char *name, const char *filter)
+{
+ uint8_t btype;
+ uint8_t magic[4], len[4];
+ size_t r, i, n, blen;
+ int last = 0;
+
+ if ((r = fread(magic, 1, sizeof(magic), fp)) != sizeof(magic))
+ return (-1);
+
+ if (memcmp(magic, "fLaC", 4) != 0) {
+ warnx("not a flac file %s", name);
+ return (-1);
+ }
+
+ while (!last) {
+ if (fread(&btype, 1, 1, fp) != 1)
+ return (-1);
+ last = btype & 0x80;
+
+ if (fread(len, 1, 3, fp) != 3)
+ return (-1);
+
+ blen = (len[0] << 16)|(len[1] << 8)|len[2];
+
+ if ((btype & 0x07) != 0x04) {
+ //printf("skipping %zu bytes of block type %d\n",
+ // blen, (btype & 0x07));
+ /* not a vorbis comment, skip... */
+ if (fseeko(fp, blen, SEEK_CUR) == -1)
+ return (-1);
+ continue;
+ }
+
+ if (fread(len, 1, 4, fp) != 4)
+ return (-1);
+
+ /* The vorbis comment has little-endian integers */
+
+ blen = (len[3] << 24)|(len[2] << 16)|(len[1] << 8)|len[0];
+ /* skip the vendor string comment */
+ if (fseeko(fp, blen, SEEK_CUR) == -1)
+ return (-1);
+
+ if (fread(len, 1, 4, fp) != 4)
+ return (-1);
+ n = (len[3] << 24)|(len[2] << 16)|(len[1] << 8)|len[0];
+
+ for (i = 0; i < n; ++i) {
+ char *m, *v;
+
+ if (fread(len, 1, 4, fp) != 4)
+ return (-1);
+ blen = (len[3] << 24)|(len[2] << 16)|(len[1] << 8)|len[0];
+
+ if ((m = malloc(blen + 1)) == NULL)
+ err(1, "malloc");
+
+ if (fread(m, 1, blen, fp) != blen) {
+ free(m);
+ return (-1);
+ }
+ m[blen] = '\0';
+
+ if ((v = strchr(m, '=')) == NULL) {
+ warnx("missing field name!");
+ free(m);
+ return (-1);
+ }
+
+ *v++ = '\0';
+ printf("%s = %s\n", m, v);
+
+ free(m);
+ }
+ }
+
+ return (0);
+}
blob - /dev/null
blob + b35ff6a7117553977876b7b1cca6b7462da0f619 (mode 644)
--- /dev/null
+++ songmeta/id3v1.c
+/*
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * ID3v1 and 1.1 handling.
+ */
+
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "songmeta.h"
+
+#define ID3v1_SIZE 128
+
+int
+id3v1_dump(int fd, const char *name, const char *filter)
+{
+ struct stat sb;
+ char *s, *e, id3[ID3v1_SIZE];
+ char buf[5]; /* wide enough for YYYY + NUL */
+ ssize_t r;
+ off_t off;
+
+ if (fstat(fd, &sb) == -1) {
+ warn("fstat %s", name);
+ return (-1);
+ }
+
+ if (sb.st_size < ID3v1_SIZE) {
+ warnx("no id3 section found in %s", name);
+ return (-1);
+ }
+ off = sb.st_size - ID3v1_SIZE;
+ r = pread(fd, id3, ID3v1_SIZE, off);
+ if (r == -1 || r != ID3v1_SIZE) {
+ warn("failed to read id3 section in %s", name);
+ return (-1);
+ }
+
+ s = id3;
+ if (strncmp(s, "TAG", 3) != 0)
+ goto bad;
+ s += 3;
+
+ if (memchr(s, '\0', 30) == NULL)
+ goto bad;
+ if (*s)
+ printfield("title", filter, "Title", 1, s);
+ else if (filter != NULL && matchfield("title", filter))
+ return (-1);
+ s += 30;
+
+ if (memchr(s, '\0', 30) == NULL)
+ goto bad;
+ if (*s)
+ printfield("artist", filter, "Artist", 1, s);
+ else if (filter != NULL && matchfield("artist", filter))
+ return (-1);
+ s += 30;
+
+ if (memchr(s, '\0', 30) == NULL)
+ goto bad;
+ if (*s)
+ printfield("album", filter, "Album", 1, s);
+ else if (filter != NULL && matchfield("album", filter))
+ return (-1);
+ s += 30;
+
+ if (!isdigit((unsigned char)s[0]) ||
+ !isdigit((unsigned char)s[1]) ||
+ !isdigit((unsigned char)s[2]) ||
+ !isdigit((unsigned char)s[3]))
+ goto bad;
+ memcpy(buf, s, 4);
+ buf[4] = '\0';
+ printfield("year", filter, "Year", 0, buf);
+ s += 4;
+
+ if ((e = memchr(s, '\0', 30)) == NULL)
+ goto bad;
+ s += strspn(s, " \t");
+ if (*s)
+ printfield("comment", filter, "Comment", 1, s);
+ else if (filter != NULL && matchfield("comment", filter))
+ return (-1);
+
+ /* ID3v1.1: track number is inside the comment space */
+
+ if (s[28] == '\0' && s[29] != '\0') {
+ snprintf(buf, sizeof(buf), "%d", (unsigned int)s[29]);
+ printfield("track", filter, "Track #", 0, s);
+ } else if (filter != NULL && matchfield("track", filter))
+ return (-1);
+
+ return (0);
+
+ bad:
+ warnx("bad id3 section in %s", name);
+ return (-1);
+}
blob - /dev/null
blob + 5599fa1354c7131188d02d97e403a917d2cb1e4f (mode 644)
--- /dev/null
+++ songmeta/id3v2.c
+/*
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * ID3v2(.4.0) handling.
+ */
+
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <endian.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "songmeta.h"
+
+#ifndef nitems
+#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
+#define ID3v2_HDR_SIZE 10
+#define ID3v2_FRAME_SIZE 10
+
+#define F_UNSYNC 0x80
+#define F_EXTHDR 0x40
+#define F_EXPIND 0x20
+#define F_FOOTER 0x10
+
+static const struct fnmap {
+ const char *id;
+ const char *name;
+ const char *pretty;
+} map[] = {
+ /* AENC Audio encryption */
+ /* APIC Attached picture */
+ /* ASPI Audio seek point index */
+
+ { "COMM", "comment", "Comment" },
+ /* COMR Commercial frame */
+
+ /* ENCR Encryption method registration */
+ /* EQU2 Equalisation (2) */
+ /* ETCO Event time codes */
+
+ /* GEOB General encapsulated object */
+ /* GRID Group identification registration */
+
+ /* LINK Linked information */
+
+ /* MCDI Music CD identifier */
+ /* MLLT MPEG location lookup table */
+
+ /* OWNE Ownership frame */
+
+ /* PRIV Private frame */
+ /* PCNT Play counter */
+ /* POPM Popularimeter */
+ /* POSS Position synchronisation frame */
+
+ /* RBUF Recommended buffer size */
+ /* RVA2 Relative volume adjustment (2) */
+ /* RVRB Reverb */
+
+ /* SEEK Seek frame */
+ /* SIGN Signature frame */
+ /* SYLT Synchronised lyric text */
+ /* SYTC Synchronised tempo codes */
+
+ { "TALB", "album", "Album" },
+ { "TBPM", "bpm", "beats per minute" },
+ { "TCOM", "composer", "Composer" },
+ { "TCON", "content-type", "Content type" },
+ { "TCOP", "copyright-message", "Copyright message" },
+ { "TDEN", "encoding-time", "Encoding time" },
+ { "TDLY", "playlist-delay", "Playlist delay" },
+ { "TDOR", "original-release-time","Original release time" },
+ { "TDRC", "recording-time", "Recording time" },
+ { "TDRL", "release-time", "Release time" },
+ { "TDTG", "tagging-time", "Tagging time" },
+ { "TENC", "encoded-by", "Encoded by" },
+ { "TEXT", "lyricist", "Lyricist/Text writer" },
+ { "TFLT", "file-type", "File type" },
+ { "TIPL", "involved-people", "Involved people list" },
+ { "TIT1", "content-group-description", "Content group description" },
+ { "TIT2", "title", "Title" },
+ { "TIT3", "subtitle", "Subtitle" },
+ { "TKEY", "initial-key", "Initial key" },
+ { "TLAN", "language", "Language" },
+ { "TLEN", "length", "Length" },
+ { "TMCL", "musician", "Musician credits list" },
+ { "TMED", "media-type", "Media type" },
+ { "TMOO", "mood", "Mood" },
+ { "TOAL", "original-title", "Original album/movie/show title" },
+ { "TOFN", "original-filename", "Original filename" },
+ { "TOLY", "original-lyricist", "Original lyricist(s)/text writer(s)" },
+ { "TOPE", "original-artist", "Original artist(s)/performer(s)" },
+ { "TOWN", "licensee", "File owner/licensee" },
+ { "TPE1", "lead-performer", "Lead performer(s)/Soloist(s)" },
+ { "TPE2", "band", "Band/orchestra/accompaniment" },
+ { "TPE3", "conductor", "Conductor/performer refinement" },
+ { "TPE4", "interpreted-by", "Interpreted, remixed, or otherwise modified by" },
+ { "TPOS", "part", "Part of a set" },
+ { "TPRO", "notice", "Produced notice" },
+ { "TPUB", "publisher", "Publisher" },
+ { "TRCK", "track", "Track number/Position in set" },
+ { "TRSN", "radio-name", "Internet radio station name" },
+ { "TRSO", "radio-owner", "Internet radio station owner" },
+ { "TSOA", "album-order", "Album sort order" },
+ { "TSOP", "performer-order", "Performer sort order" },
+ { "TSOT", "title-order", "Title sort order" },
+ { "TSRC", "isrc", "ISRC (international standard recording code)" },
+ { "TSSE", "encoder", "Software/Hardware and settings used for encoding" },
+ { "TSST", "subtitle", "Set subtitle" },
+ /* TXXX user defined text information frame */
+
+ /* UFID Unique file identifier */
+ /* USER Terms of use */
+ /* USLT Unsynchronised lyric/text transcription */
+
+ /* WCOM Commercial information */
+ /* WCOP Copyright/legal information */
+ /* WOAF Official audio file webpage */
+ /* WOAR Official artist/performer webpage */
+ /* WOAS Official audio source webpage */
+ /* WORS Official internet radio station homepage */
+ /* WPAY Payment */
+ /* WPUB Publishers official webpage */
+ /* WXXX User defined URL link frame */
+};
+
+static int
+mapcmp(const void *k, const void *e)
+{
+ const struct fnmap *f = e;
+
+ return (memcmp(k, f->id, 4));
+}
+
+static uint32_t
+fromss32(uint32_t x)
+{
+ uint8_t y[4];
+
+ memcpy(y, &x, sizeof(x));
+ return (y[0] << 21) | (y[1] << 14) | (y[2] << 7) | y[3];
+}
+
+int
+id3v2_dump(int fd, const char *name, const char *filter)
+{
+ struct fnmap *f;
+ char hdr[ID3v2_HDR_SIZE];
+ char *s;
+ ssize_t r;
+ uint8_t flags;
+ uint32_t size, fsize;
+
+ if ((r = read(fd, hdr, sizeof(hdr))) == -1 ||
+ r != ID3v2_HDR_SIZE) {
+ warn("read failed: %s", name);
+ return (-1);
+ }
+
+ s = hdr;
+ if (strncmp(s, "ID3", 3))
+ goto bad;
+ s += 3;
+
+ if (s[0] != 0x04 && s[1] != 0x00)
+ goto bad;
+ s += 2;
+
+ flags = s[0];
+ if ((s[0] & 0x0F) != 0)
+ goto bad;
+ s += 1;
+
+#ifdef DEBUG
+ const char *sep = "";
+ printf("flags:\t");
+ if (flags & F_UNSYNC) printf("%sunsync", sep), sep = ",";
+ if (flags & F_EXTHDR) printf("%sexthdr", sep), sep = ",";
+ if (flags & F_EXPIND) printf("%sexpind", sep), sep = ",";
+ if (flags & F_FOOTER) printf("%sfooter", sep), sep = ",";
+ printf(" (0x%x)\n", flags);
+#endif
+
+ if (flags & F_EXPIND) {
+ warnx("don't know how to handle the extended header yet.");
+ return (-1);
+ }
+
+ memcpy(&size, s, 4);
+ size = fromss32(size);
+
+ while (size > 0) {
+ if ((r = read(fd, hdr, sizeof(hdr))) == -1 ||
+ r != ID3v2_FRAME_SIZE) {
+ warn("read failed: %s", name);
+ return (-1);
+ }
+
+ memcpy(&fsize, hdr + 4, sizeof(fsize));
+ fsize = fromss32(fsize);
+ if (fsize == 0)
+ break; /* XXX padding?? */
+ if (fsize + 10 > size) {
+ warnx("bad frame length (%d vs %d)", fsize, size);
+ return (-1);
+ }
+ size -= fsize + 10;
+
+ f = bsearch(hdr, map, nitems(map), sizeof(map[0]), mapcmp);
+ if (f == NULL) {
+ if (lseek(fd, fsize, SEEK_CUR) == -1) {
+ warn("lseek");
+ return (-1);
+ }
+ continue;
+ }
+
+ /* XXX skip encoding for now */
+ lseek(fd, SEEK_CUR, 1);
+ fsize--; /* XXX */
+
+ if (readprintfield(f->name, filter, f->pretty,
+ ENC_UTF8, fd, fsize) == -1)
+ return (-1);
+ }
+
+ return (0);
+
+ bad:
+ warnx("bad ID3v2 section");
+ return (-1);
+}
blob - /dev/null
blob + 0f635293781d5e61d2433298d36c94ee782bb9ae (mode 644)
--- /dev/null
+++ songmeta/ogg.c
+/*
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * Ogg file-format handling.
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ogg.h"
+
+struct ogg {
+ FILE *fp;
+ const char *name;
+ int firstpkt;
+ uint32_t sn;
+ int chained; /* use only the sn stream */
+ size_t plen;
+};
+
+static int
+readpkt(struct ogg *ogg)
+{
+ uint32_t sn;
+ uint8_t magic[4], ssv, htype, ps, b;
+ size_t r;
+ int i;
+
+ again:
+ ogg->plen = 0;
+ if ((r = fread(magic, 1, sizeof(magic), ogg->fp)) != sizeof(magic))
+ return (-1);
+
+ if (memcmp(magic, "OggS", 4) != 0) {
+ warnx("not an ogg file: %s", ogg->name);
+ return (-1);
+ }
+
+ if (fread(&ssv, 1, 1, ogg->fp) != 1 ||
+ fread(&htype, 1, 1, ogg->fp) != 1)
+ return (-1);
+
+ /* skip the absolute granule position */
+ if (fseeko(ogg->fp, 8, SEEK_CUR) == -1)
+ return (-1);
+
+ /* the serial number of the stream */
+ if (fread(&sn, 1, sizeof(sn), ogg->fp) != sizeof(ogg->sn))
+ return (-1);
+ sn = le32toh(sn);
+
+ /* ignore sequence number and crc32 for now */
+ if (fseeko(ogg->fp, 4 + 4, SEEK_CUR) == -1)
+ return (-1);
+
+ if (fread(&ps, 1, 1, ogg->fp) != 1)
+ return (-1);
+
+ ogg->plen = 0;
+ for (i = 0; i < ps; ++i) {
+ if (fread(&b, 1, 1, ogg->fp) != 1)
+ return (-1);
+ ogg->plen += b;
+ }
+
+ /* found some data without a suitable stream */
+ if (!ogg->chained && !(htype & 0x02))
+ return (-1);
+
+ if (ogg->chained && ogg->sn != sn) {
+ /* not "our" stream */
+ if (fseeko(ogg->fp, ogg->plen, SEEK_CUR) == -1)
+ return (-1);
+ ogg->plen = 0;
+ goto again;
+ }
+
+ ogg->sn = sn;
+ return (0);
+}
+
+struct ogg *
+ogg_open(FILE *fp, const char *name)
+{
+ struct ogg *ogg;
+
+ ogg = calloc(1, sizeof(*ogg));
+ if (ogg == NULL)
+ return (NULL);
+
+ ogg->fp = fp;
+ ogg->name = name;
+ ogg->firstpkt = 1;
+
+ if (readpkt(ogg) == -1) {
+ free(ogg);
+ return (NULL);
+ }
+
+ return (ogg);
+}
+
+size_t
+ogg_read(struct ogg *ogg, void *buf, size_t len)
+{
+ size_t r;
+
+ if (len == 0)
+ return (0);
+
+ if (ogg->plen == 0 && readpkt(ogg) == -1)
+ return (0);
+
+ if (len > ogg->plen)
+ len = ogg->plen;
+
+ r = fread(buf, 1, len, ogg->fp);
+ ogg->plen -= r;
+ return (r);
+}
+
+int
+ogg_seek(struct ogg *ogg, off_t n)
+{
+ /* not implemented */
+ if (n < 0)
+ return (-1);
+
+ while (n > 0) {
+ if (ogg->plen == 0 && readpkt(ogg) == -1)
+ return (-1);
+
+ if (n >= ogg->plen) {
+ if (fseeko(ogg->fp, ogg->plen, SEEK_CUR) == -1)
+ return (-1);
+ n -= ogg->plen;
+ ogg->plen = 0;
+ continue;
+ }
+
+ if (fseeko(ogg->fp, n, SEEK_CUR) == -1)
+ return (-1);
+ ogg->plen -= n;
+ break;
+ }
+
+ return (0);
+}
+
+int
+ogg_skip_page(struct ogg *ogg)
+{
+ return (ogg_seek(ogg, ogg->plen));
+}
+
+void
+ogg_use_current_stream(struct ogg *ogg)
+{
+ ogg->chained = 1;
+}
+
+int
+ogg_rewind(struct ogg *ogg)
+{
+ if (fseeko(ogg->fp, 0, SEEK_SET) == -1)
+ return (-1);
+
+ ogg->plen = 0;
+ ogg->firstpkt = 1;
+ ogg->chained = 0;
+ return (0);
+}
+
+void
+ogg_close(struct ogg *ogg)
+{
+ /* it's up to the caller to close ogg->fp */
+ free(ogg);
+}
blob - /dev/null
blob + edc13f631642e8cc72a89b560258307443433700 (mode 644)
--- /dev/null
+++ songmeta/ogg.h
+/*
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * Ogg file-format handling.
+ */
+
+struct ogg;
+
+struct ogg *ogg_open(FILE *, const char *);
+size_t ogg_read(struct ogg *, void *, size_t);
+int ogg_seek(struct ogg *, off_t);
+int ogg_skip_page(struct ogg *);
+void ogg_use_current_stream(struct ogg *);
+int ogg_rewind(struct ogg *);
+void ogg_close(struct ogg *);
blob - /dev/null
blob + cb9c75b9e67fab9f41ba858344a0bb5d7e2b3a95 (mode 644)
--- /dev/null
+++ songmeta/opus.c
+/*
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <err.h>
+#include <endian.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "ogg.h"
+#include "songmeta.h"
+
+int
+opus_match(struct ogg *ogg)
+{
+ uint8_t hdr[8], v;
+
+ if (ogg_read(ogg, hdr, sizeof(hdr)) != sizeof(hdr))
+ return (-1);
+ if (memcmp(hdr, "OpusHead", 8) != 0)
+ return (-1);
+
+ ogg_use_current_stream(ogg);
+
+ if (ogg_read(ogg, &v, 1) != 1)
+ return (-1);
+ if (v < 1 || v > 2) {
+ warnx("unsupported opus version %d", v);
+ return (-1);
+ }
+
+ /* skip the rest of the identification header */
+ if (ogg_skip_page(ogg) == -1)
+ return (-1);
+
+ /* now there should be the optional tag section */
+ if (ogg_read(ogg, hdr, sizeof(hdr)) != sizeof(hdr))
+ return (-1);
+ if (memcmp(hdr, "OpusTags", 8) != 0)
+ return (-1);
+
+ return (0);
+}
+
+int
+opus_dump(struct ogg *ogg, const char *name, const char *filter)
+{
+ static char buf[2048]; /* should be enough... */
+ char *v;
+ uint32_t i, n, l, len;
+
+ if (ogg_read(ogg, &len, sizeof(len)) != sizeof(len))
+ return (-1);
+ len = le32toh(len);
+ if (ogg_seek(ogg, +len) == -1)
+ return (-1);
+
+ if (ogg_read(ogg, &n, sizeof(n)) != sizeof(n))
+ return (-1);
+ n = le32toh(n);
+
+ for (i = 0; i < n; ++i) {
+ if (ogg_read(ogg, &len, sizeof(len)) != sizeof(len))
+ return (-1);
+ len = le32toh(len);
+
+ l = len;
+ if (l >= sizeof(buf))
+ l = len - 1;
+ len -= l;
+
+ if (ogg_read(ogg, buf, l) != l ||
+ ogg_seek(ogg, +len) == -1)
+ return (-1);
+ buf[l] = '\0';
+
+ if ((v = strchr(buf, '=')) == NULL)
+ return (-1);
+ *v++ = '\0';
+
+ /*
+ * XXX should probably ignore R128_TRACK_GAIN and
+ * R128_ALBUM_GAIN.
+ */
+
+ printf("%s = %s\n", buf, v);
+ }
+
+
+ return (-1);
+}
blob - /dev/null
blob + 3465d7f5cfd013054d84cfeec22f70301cc13050 (mode 644)
--- /dev/null
+++ songmeta/songmeta.c
+/*
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ogg.h"
+#include "songmeta.h"
+
+int printraw;
+
+static void __dead
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-r] [-g field] files...\n",
+ getprogname());
+ exit(1);
+}
+
+int
+matchfield(const char *field, const char *filter)
+{
+ if (filter == NULL)
+ return (1);
+ return (strcasecmp(field, filter) == 0);
+}
+
+int
+printfield(const char *field, const char *filter, const char *fname,
+ int enc, const char *str)
+{
+ if (!matchfield(field, filter))
+ return (0);
+
+ if (filter == NULL) {
+ printf("%s:\t", fname);
+ if (strlen(fname) < 8)
+ printf("\t");
+ }
+
+ if (enc == ENC_GUESS) {
+ mlprint(str);
+ puts("");
+ } else
+ printf("%s\n", str);
+
+ return (0);
+}
+
+int
+readprintfield(const char *field, const char *filter, const char *fname,
+ int enc, int fd, off_t len)
+{
+ static char buf[BUFSIZ + 1];
+ size_t n;
+ ssize_t r;
+
+ if (!matchfield(field, filter))
+ return (0);
+
+ if (filter == NULL) {
+ printf("%s:\t", fname);
+ if (strlen(fname) < 8)
+ printf("\t");
+ }
+
+ while (len > 0) {
+ if ((n = len) > sizeof(buf) - 1)
+ n = sizeof(buf) - 1;
+
+ if ((r = read(fd, buf, n)) == -1) {
+ warn("read");
+ return (-1);
+ }
+ if (r == 0) {
+ warnx("unexpected EOF");
+ return (-1);
+ }
+ buf[r] = '\0';
+
+ if (enc == ENC_GUESS)
+ mlprint(buf);
+ else
+ fwrite(buf, 1, r, stdout);
+
+ len -= r;
+ }
+
+ puts("");
+ return (0);
+}
+
+static int
+dofile(FILE *fp, const char *name, const char *filter)
+{
+ static char buf[512];
+ struct ogg *ogg;
+ size_t r, ret = -1;
+
+ if ((r = fread(buf, 1, sizeof(buf), fp)) < 8) {
+ warn("failed to read %s", name);
+ return (-1);
+ }
+
+ if (fseek(fp, 0, SEEK_SET) == -1) {
+ warn("fseek failed in %s", name);
+ return (-1);
+ }
+
+ if (memcmp(buf, "fLaC", 4) == 0)
+ return flac_dump(fp, name, filter);
+
+ if (memcmp(buf, "ID3", 3) == 0)
+ return id3v2_dump(fileno(fp), name, filter);
+
+ /* maybe it's an ogg file */
+ if ((ogg = ogg_open(fp, name)) != NULL) {
+ if (vorbis_match(ogg) != -1) {
+ ret = vorbis_dump(ogg, name, filter);
+ ogg_close(ogg);
+ return (ret);
+ }
+
+ if (ogg_rewind(ogg) == -1) {
+ warn("I/O error on %s", name);
+ ogg_close(ogg);
+ return (-1);
+ }
+
+ if (opus_match(ogg) != -1) {
+ ret = opus_dump(ogg, name, filter);
+ ogg_close(ogg);
+ return (ret);
+ }
+ ogg_close(ogg);
+ }
+
+ if (ferror(fp)) {
+ warn("I/O error on %s", name);
+ return (-1);
+ }
+
+ /* TODO: id3v1? */
+
+ warnx("unknown file format: %s", name);
+ return (-1);
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *filter = NULL;
+ FILE *fp;
+ int ch;
+ int ret = 0;
+
+ if (pledge("stdio rpath", NULL) == -1)
+ err(1, "pledge");
+
+ while ((ch = getopt(argc, argv, "g:r")) != -1) {
+ switch (ch) {
+ case 'g':
+ filter = optarg;
+ break;
+ case 'r':
+ printraw = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0)
+ usage();
+
+ for (; *argv; ++argv) {
+ if ((fp = fopen(*argv, "r")) == NULL) {
+ warn("can't open %s", *argv);
+ ret = 1;
+ continue;
+ }
+
+ if (argc != 1)
+ printf("=> %s\n", *argv);
+ if (dofile(fp, *argv, filter) == -1)
+ ret = 1;
+
+ fclose(fp);
+ }
+
+ return (ret);
+}
blob - /dev/null
blob + e756735ddcceba0521a46110a9cbe161519855b9 (mode 644)
--- /dev/null
+++ songmeta/songmeta.h
+/*
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+extern int printraw;
+
+enum {
+ ENC_GUESS,
+ ENC_UTF8,
+};
+
+struct ogg;
+
+/* flac.c */
+int flac_dump(FILE *, const char *, const char *);
+
+/* id3v1.c */
+int id3v1_dump(int, const char *, const char *);
+
+/* id3v2.c */
+int id3v2_dump(int, const char *, const char *);
+
+/* opus.c */
+int opus_match(struct ogg *);
+int opus_dump(struct ogg *, const char *, const char *);
+
+/* vorbis.c */
+int vorbis_match(struct ogg *);
+int vorbis_dump(struct ogg *, const char *, const char *);
+
+/* songmeta.c */
+int matchfield(const char *, const char *);
+int printfield(const char *, const char *, const char *, int,
+ const char *);
+int readprintfield(const char *, const char *, const char *,
+ int, int, off_t);
+
+/* text.c */
+void mlprint(const char *);
blob - /dev/null
blob + 7ecd534e9c131e64ac276813a1ce5fd512b11391 (mode 644)
--- /dev/null
+++ songmeta/text.c
+/*
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "../config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+#include "songmeta.h"
+
+/*
+ * The idea is to try to decode the text as UTF-8; if it fails assume
+ * it's ISO Latin. Some strings in ISO Latin may be decoded correctly
+ * as UTF-8 (since both are a superset of ASCII). In every case, this
+ * is just a "best effort" for when we don't have other clues about
+ * the format.
+ */
+static int
+u8decode(const char *s, int print)
+{
+ wchar_t wc;
+ int len;
+
+ for (; *s != '\0'; s += len) {
+ if ((len = mbtowc(&wc, s, MB_CUR_MAX)) == -1) {
+ (void)mbtowc(NULL, NULL, MB_CUR_MAX);
+ return (0);
+ }
+ if (print) {
+ if (!printraw && wcwidth(wc) == -1)
+ putchar('?');
+ else
+ fwrite(s, 1, len, stdout);
+ }
+ }
+
+ return (1);
+}
+
+/* Print a string that may be encoded as ISO Latin. */
+void
+mlprint(const char *s)
+{
+ wchar_t wc;
+
+ if (u8decode(s, 0)) {
+ u8decode(s, 1);
+ return;
+ }
+
+ /* let's hope it's ISO Latin */
+
+ while (*s) {
+ /* the first 256 UNICODE codepoints map 1:1 ISO Latin */
+ wc = *s++;
+ if (!printraw && wcwidth(wc) == -1)
+ putchar('?');
+ else
+ fwprintf(stdout, L"%c", wc);
+ }
+}
blob - /dev/null
blob + 224b1f7b9cf83a08022e709cb3a4714c27215eae (mode 644)
--- /dev/null
+++ songmeta/vorbis.c
+/*
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <endian.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "ogg.h"
+#include "songmeta.h"
+
+int
+vorbis_match(struct ogg *ogg)
+{
+ uint8_t hdr[7]; /* packet type + "vorbis" */
+
+ if (ogg_read(ogg, hdr, 7) != 7)
+ return (-1);
+
+ if (memcmp(hdr + 1, "vorbis", 6) != 0)
+ return (-1);
+
+ ogg_use_current_stream(ogg);
+
+ /*
+ * vorbis version (4 bytes)
+ * channels (1 byte)
+ * sample rate (4 bytes)
+ * bitrate max/nominal/min (4 bytes each)
+ * blocksize_{0,1} (1 byte)
+ * framing flag (1 bit -- rounded to 1 byte)
+ */
+ /* XXX check that the framing bit is 1? */
+ if (ogg_seek(ogg, +23) == -1)
+ return (-1);
+
+ return (0);
+}
+
+int
+vorbis_dump(struct ogg *ogg, const char *name, const char *filter)
+{
+ static char buf[2048]; /* should be enough... */
+ char *v;
+ uint32_t i, n, l, len;
+ uint8_t pktype, hdr[7];
+
+ if (ogg_read(ogg, hdr, 7) != 7)
+ return (-1);
+ if (memcmp(hdr + 1, "vorbis", 6) != 0)
+ return (-1);
+
+ pktype = hdr[0];
+ if (pktype != 3) /* metadata */
+ return (-1);
+
+ if (ogg_read(ogg, &len, sizeof(len)) != sizeof(len))
+ return (-1);
+ len = le32toh(len);
+ if (ogg_seek(ogg, +len) == -1)
+ return (-1);
+
+ if (ogg_read(ogg, &n, sizeof(n)) != sizeof(n))
+ return (-1);
+ n = le32toh(n);
+
+ for (i = 0; i < n; ++i) {
+ if (ogg_read(ogg, &len, sizeof(len)) != sizeof(len))
+ return (-1);
+ len = le32toh(len);
+
+ l = len;
+ if (l >= sizeof(buf))
+ l = len - 1;
+ len -= l;
+
+ if (ogg_read(ogg, buf, l) != l ||
+ ogg_seek(ogg, +len) == -1)
+ return (-1);
+ buf[l] = '\0';
+
+ if ((v = strchr(buf, '=')) == NULL)
+ return (-1);
+ *v++ = '\0';
+
+ printf("%s = %s\n", buf, v);
+ }
+
+ return (0);
+}