Information wants to be free...

ID3 Tools

For some reason, it's not that easy to find command line programs that deal with ID3 tags (as used in MP3 files) on Linux anymore. There are some updated libraries, but most of the programs I found were somewhat outdated. Because of this, I found the ID3 specifications and wrote a couple of tools my self. The first tool just checks for tag presence in a file, while the second one strips all tags. There are actually three kinds of tags to look for; ID3v1, ID3v2 and the strange MusicMatch tag.

Here's the tag detector:

#include <stdio.h>
#include <string.h>

static int has_id3v1(FILE *fh)
{
  unsigned char tag[3];

  if (fseek(fh, -128, SEEK_END) == -1)
    return 0;

  fread(tag, sizeof(unsigned char), 3, fh);

  if (tag[0] == 'T' && tag[1] == 'A' && tag[2] == 'G')
    return 1;
  else
    return 0;
}

static int has_id3v2(FILE *fh)
{
  unsigned char tag[10];

  if (fseek(fh, 0, SEEK_SET) == -1)
    return 0;

  fread(tag, sizeof(unsigned char), 10, fh);

  if (tag[0] == 'I' &&
      tag[1] == 'D' &&
      tag[2] == '3' &&
      tag[3] < 0xFF &&
      tag[4] < 0xFF &&
      tag[6] < 0x80 &&
      tag[7] < 0x80 &&
      tag[8] < 0x80 &&
      tag[9] < 0x80) {
    return 1;
  }

  return 0;
}

static int has_musicmatch(FILE *fh)
{
  char tag[19];

  if (fseek(fh, -48, SEEK_END) == -1)
    return 0;

  fread(tag, sizeof(char), 19, fh);

  if (memcmp(tag, "Brava Software Inc.", 19) == 0)
    return 1;
  else
    return 0;
}

int main(int argc, char *argv[])
{
  FILE *fh;

  if (argc != 2) {
    fprintf(stderr, "Usage: %s <mp3-file>\n", argv[0]);
    return 1;
  }

  fh = fopen(argv[1], "r");
  if (fh == NULL)
    return 1;

  printf("%s:%s%s%s\n", argv[1], 
    (has_id3v1(fh)) ? " id3v1" : "",
    (has_id3v2(fh)) ? " id3v2" : "",
    (has_musicmatch(fh)) ? " musicmatch" : "");

  fclose(fh);
  return 0;
}
          


Here's the tag stripper:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#define BUFFER_SIZE 512

static int has_id3v1(FILE *fh)
{
  unsigned char tag[3];

  if (fseek(fh, -128, SEEK_END) == -1)
    return 0;

  fread(tag, sizeof(unsigned char), 3, fh);

  if (tag[0] == 'T' && tag[1] == 'A' && tag[2] == 'G')
    return 1;
  else
    return 0;
}

static int has_id3v2(FILE *fh)
{
  unsigned char tag[10];

  if (fseek(fh, 0, SEEK_SET) == -1)
    return 0;

  fread(tag, sizeof(unsigned char), 10, fh);

  if (tag[0] == 'I' &&
      tag[1] == 'D' &&
      tag[2] == '3' &&
      tag[3] < 0xFF &&
      tag[4] < 0xFF &&
      tag[6] < 0x80 &&
      tag[7] < 0x80 &&
      tag[8] < 0x80 &&
      tag[9] < 0x80) {
    return tag[9] + (tag[8] << 7) + (tag[7] << 14) + (tag[6] << 21) + 10;
  }

  return 0;
}

int main(int argc, char *argv[])
{
  FILE *in, *out;
  int start, remaining, bytes;
  struct stat st;
  char buffer[BUFFER_SIZE];

  if (argc != 3) {
    fprintf(stderr, "Usage: %s <in> <out>\n", argv[0]);
    return 1;
  }

  if (stat(argv[1], &st) == -1) {
    fprintf(stderr, "Error: Could not stat file: %s\n", argv[1]);
    return 1;
  }

  in = fopen(argv[1], "r");
  if (in == NULL) {
    fprintf(stderr, "Error: Could not open file for reading: %s\n", argv[1]);
    return 1;
  }

  out = fopen(argv[2], "w");
  if (out == NULL) {
    fprintf(stderr, "Error: Could not open file for writing: %s\n", argv[2]);
    fclose(in);
    return 1;
  }

  if (has_id3v1(in))
    remaining = st.st_size - 128;
  else 
    remaining = st.st_size;

  start = has_id3v2(in);
  remaining -= start;
  fseek(in, start, SEEK_SET);

  while (remaining > 0) {
    bytes = fread(buffer, sizeof(char), 
      (remaining > BUFFER_SIZE) ? BUFFER_SIZE : remaining, in);
    fwrite(buffer, sizeof(char), bytes, out);
    remaining -= bytes;
  }

  fclose(in);
  fclose(out);
  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 06/11-2012, Article Link