Kjetil's Information Center: A Blog About My Projects

MP3 Splitting Tool

This tool is very similar to the MP3 Cutting Tool I made some years ago, but covers different use case. The intention with this tool, is to cut away the beginning or ending of an MP3 file in a lossless manner, by splitting it at a frame boundary.

Using the tool on an MP3 file without any additional arguments will just print the total amount of frames. It will then be necessary to specify at which frame the splitting should occur, and the result will be two new (before & after the frame) MP3 files. Please note that this operation will likely mess up ID3 tags in the file, so I recommend to remove all old tags and then do a re-tagging operation afterwards.

Here is the modified source code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <limits.h>

static int bitrate_matrix[16][5] = {
  {0,   0,   0,   0,   0},
  {32,  32,  32,  32,  8},
  {64,  48,  40,  48,  16},
  {96,  56,  48,  56,  24},
  {128, 64,  56,  64,  32},
  {160, 80,  64,  80,  40},
  {192, 96,  80,  96,  48},
  {224, 112, 96,  112, 56},
  {256, 128, 112, 128, 64},
  {288, 160, 128, 144, 80},
  {320, 192, 160, 160, 96},
  {352, 224, 192, 176, 112},
  {384, 256, 224, 192, 128},
  {416, 320, 256, 224, 144},
  {448, 384, 320, 256, 160},
  {0,   0,   0,   0,   0}};

static int sampling_matrix[4][3] = {
  {44100, 22050, 11025},
  {48000, 24000, 12000},
  {32000, 16000, 8000},
  {0,     0,     0}};

static int decode_header(unsigned char *header)
{
  int version, layer, padding;
  int bitrate_row, bitrate_col, sampling_row, sampling_col;

  version = (header[1] & 0x08) >> 3; /* MPEG version. */
  layer = (header[1] & 0x06) >> 1; /* MPEG layer. */

  bitrate_row = (header[2] & 0xf0) >> 4;
  bitrate_col = -1;
  if (version == 1) {
    if (layer == 3)      /* I */
      bitrate_col = 0;
    else if (layer == 2) /* II */
      bitrate_col = 1;
    else if (layer == 1) /* III */
      bitrate_col = 2;
  } else { /* Version 2 */
    if (layer == 3)      /* I */
      bitrate_col = 3;
    else if (layer == 2) /* II */
      bitrate_col = 4;
    else if (layer == 1) /* III */
      bitrate_col = 4;
  }

  sampling_row = (header[2] & 0x0c) >> 2;
  sampling_col = (version == 0) ? 1 : 0;

  padding = (header[2] & 0x02) >> 1;

  if (sampling_matrix[sampling_row][sampling_col] == 0)
    return -1; /* Cannot divide by zero. */

  if (layer == 3) /* I */
    return (12 * (bitrate_matrix[bitrate_row][bitrate_col] * 1000) /
      sampling_matrix[sampling_row][sampling_col] + (padding * 4)) * 4;
  else if (layer == 2 || layer == 1) /* II or III */
    return 144 * (bitrate_matrix[bitrate_row][bitrate_col] * 1000) /
      sampling_matrix[sampling_row][sampling_col] + padding;
  else
    return -1;
}

static int read_frames(FILE *src, FILE *dst, int frame_limit)
{
  int c, n, frame_length, no_of_frames;
  unsigned char quad[4];

  quad[0] = quad[1] = quad[2] = quad[3] = '\0';

  frame_length = n = no_of_frames = 0;
  while ((c = fgetc(src)) != EOF) {
    if (dst != NULL)
      fputc(c, dst);
  
    if (frame_length > 0) {
      frame_length--;
      n++;

      /* While cutting the file, a frame limit is specified to stop reading. */
      if (frame_limit > 0) {
        if (frame_length == 0 && no_of_frames == frame_limit)
          return no_of_frames; /* Return early, but filehandle must be left
                                  intact by the caller to continue at the right
                                  spot! */
      }

      /* Skip ahead in stream to avoid reading garbage. */
      continue;
    }

    /* Have a potential header ready for each read. */
    quad[0] = quad[1];
    quad[1] = quad[2];
    quad[2] = quad[3];
    quad[3] = c;

    /* Match frame sync. */
    if ((quad[0] == 0xff) && ((quad[1] & 0xf0) == 0xf0)) {
      no_of_frames++;
      frame_length = decode_header(quad) - 4;
      quad[0] = quad[1] = quad[2] = quad[3] = '\0';
    }

    n++;
  }
  
  return no_of_frames;
}

int main(int argc, char *argv[])
{
  int i, no_of_frames, parts, limit;
  FILE *src, *dst;
  char filename[PATH_MAX]; /* POSIX limit. */

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

  parts = atoi(argv[2]);
  if (parts == 0) {
    fprintf(stderr, "Error: Invalid number of parts specified.\n");
    return 1;
  }

  src = fopen(argv[1], "r");
  if (src == NULL) {
    fprintf(stderr, "Error: Unable to open file for reading: %s\n",
      strerror(errno));
    return 1;
  }

  no_of_frames = read_frames(src, NULL, 0);
  if (parts > no_of_frames) {
    fprintf(stderr, "Error: More parts than available frames specified.\n");
    fclose(src);
    return 1;
  }

  rewind(src);

  for (i = 1; i <= parts; i++) {
    snprintf(filename, sizeof(filename), "%s.%02d", argv[1], i);

    dst = fopen(filename, "w");
    if (dst == NULL) {
      fprintf(stderr, "Error: Unable to open file for writing: %s\n",
        strerror(errno));
      fclose(src);
      return 1;
    }

    if (i == parts)
      limit = 0; /* Make sure all frames are read on the last part,
                    rounding errors in the formula prevents this. */
    else
      limit = no_of_frames / parts;

    fprintf(stderr, "%02d: %s: %d\n", i, filename, 
      read_frames(src, dst, limit));

    fclose(dst);
  }

  fclose(src);
  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 29/11-2014, Article Link