Random Line Filter
This is a special tool I hacked together to solve a very specific problem. It is basically a filter that will select one random line of the input and print it. The naive way of solving this would be to store all the lines in memory and then randomly select one of them, but the problem with this method is it's O(n) memory usage. Fortunately, I discovered another interesting algorithm that does the same thing, but with O(1) memory usage, and yet provides perfectly balanced randomness.
Take a look at the code:
#include <stdio.h> #include <stdlib.h> #include <time.h> #include <limits.h> int consume_line(FILE *fh) { int c; while ((c = fgetc(fh)) != EOF) { if (c == '\n') return 0; } return -1; } int main(void) { char file[PATH_MAX]; int eof = 0, count = 2; srandom(time(NULL)); if (fgets(file, PATH_MAX, stdin) == NULL) return 1; while (! eof) { if (random() % count == 0) { if (fgets(file, PATH_MAX, stdin) == NULL) eof = 1; } else { if (consume_line(stdin) == -1) eof = 1; } count++; } printf("%s", file); return 0; }
The tool can provide a simple way of playing random MP3 files, without the need for a shuffle function in the playback program. Like this example, using mplayer:
while true; do mplayer "`find . -name "*.mp3" | ./randline`"; done
This was the original intention of the tool, but I later discovered that mplayer does in fact have a shuffle mode! Well, it's too late now, as the tool has already been created.
Send and Receive files with Netcat
Here is a very simple way to send single files over the network, from one host to another. All you need is the netcat tool (also known as "nc" on the command line) and a couple of shell scripts. The transfer method is like FTP (over a straight TCP connection), but without the control channel.
Sender script:
#!/bin/sh if [ -z "$1" ]; then echo "Usage: $0 <file to send>" exit 1 fi nc $REMOTE_HOST 10001 -q 1 < "$1"
Receiver script:
#!/bin/sh if [ -z "$1" ]; then echo "Usage: $0 <file to receive>" exit 1 fi if [ -e "$1" ]; then echo "error: file exists." exit 1 else nc -p 10001 -l > "$1" fi
In these examples, I am using a static host as the receiver ($REMOTE_HOST), so I don't need to specify it all the time. I have also decided on a static port number (10001) to use on both ends. The port number should probably be over 1024, so a normal user can create a listening socket on it. Also note that the receiver script always needs to be started before the sender script, if not, the connection will be refused.
Aspire One D250 Tweaks
I recently bought a new netbook, an Acer Aspire One D250 (HD version). Unfortunately, it was not possible to get it with Linux pre-installed, but I quickly re-formatted the hard-drive and installed Slackware 13.0. As usual with new hardware on Linux, some devices were not supported out of the box. After some tweaking however, I got it all up and running.
There were problems with both the ethernet and the wireless network interfaces, and also with the built in microphone controlled by the sound-card. Here is the "lspci -nn" information of the affected hardware devices:
0:1b.0 Audio device [0403]: Intel Corporation 82801G (ICH7 Family) High Definition Audio Controller [8086:27d8] (rev 02) 01:00.0 Network controller [0280]: Broadcom Corporation BCM4312 802.11b/g [14e4:4315] (rev 01) 03:00.0 Ethernet controller [0200]: Attansic Technology Corp. Atheros AR8132 / L1c Gigabit Ethernet Adapter [1969:1062] (rev c0)
The Wi-Fi interface can be fixed by installing the proprietary driver from Broadcom, instead of using the open source "b43" driver. (The b43 driver did not support this particular chip-set in the beginning of September when I installed everything, but this may have changed now.) I used the "hybrid-portsrc-x86_32-v5_10_91_9.tar.gz" driver with the "5_10_91_9_patch_2_6_29_kernel.zip" patch, and it has so far worked fine. I used the following steps to install it (some of the same information can also be found in the driver's readme file):
patch -p1 < patch_2.6.29_kernels make -C /lib/modules/2.6.29.6-smp/build M=`pwd` clean make -C /lib/modules/2.6.29.6-smp/build M=`pwd` sudo cp wl.ko /lib/modules/2.6.29.6-smp/updates/drivers/net/wireless/ sudo depmod reboot
The ethernet interface suffers from strange bugs in the driver (atl1c) shipped with the default kernel (2.6.29.6). Sometimes the chip itself disappears from the PCI bus, and does not show up until a complete power on/off has been done. The "ifconfig" command also shows crazy RX and TX values for the interface. I had to blacklist the kernel driver (by adding a file with "blacklist atl1c in the /etc/modprobe.d/ directory) and install the proprietary Atheros driver to fix it. I used the "AR81Family-linux-v1.0.0.10.tar.gz" file, which makes a new kernel module/driver named "atl1e".
The final thing I had issues with was the internal microphone, but it was fixed simply by updating the ALSA driver and library to version 1.0.21.
Amilo L7300 Tweaks
I have a Fujitsu Siemens Amilo L7300 laptop with Slackware Linux (kernel version 2.6.27) installed on it. As usual with laptop hardware, there are quirks and other special things, mostly because of the complex ACPI system.
In the /etc/rc.d/rc.local script that runs at start-up, I have put some commands that helps with some of the issues with this particular laptop. Hopefully this information can be of use for others also.
One issue is that the fans are turned on at power on, but they are never turned off. Normally they should be activated on demand when temperature rises and then deactivated as the temperature drops. They need to be turned on, then off and then on again manually to fix them.
Another issue is an annoying bug with the keyboard and touch-pad. Around 10% of the times the system has booted, they do not work at all, no reaction. The root cause seems to be the i8042 controller, but luckily it can be "reset".
Finally there is custom keyboard button (with a fan or atomic symbol on it) that is not recognised by the kernel. There is a command to map it, so that it can be used in X afterwards.
Here is parts of the rc.local script:
#!/bin/sh echo "Reset ACPI fan status." echo 3 >> /proc/acpi/fan/FN1/state echo 3 >> /proc/acpi/fan/FN2/state sleep 1 echo 0 >> /proc/acpi/fan/FN1/state echo 0 >> /proc/acpi/fan/FN2/state sleep 1 echo 3 >> /proc/acpi/fan/FN1/state echo 3 >> /proc/acpi/fan/FN2/state echo "Reset i8042 keyboard and touch-pad." echo -n "i8042" > /sys/bus/platform/drivers/i8042/unbind sleep 1 echo -n "i8042" > /sys/bus/platform/drivers/i8042/bind echo "Assign keycode for extra key on keyboard." /usr/bin/setkeycodes 6d 120
X Terminal Directory Hack
When working under the X Window System environment with multiple terminals opened, I usually work in the same directory on the file system. Instead of manually changing directory every time a new terminal is opened, I had an idea to do it with shell functions like this:
function mk { pwd | xclip; } function rt { cd `xclip -o`; }
These functions will use the X clipboard to store and retrieve the current directory using the xclip tool. This way, the current directory can be marked (mk) in the terminal currently in use and retrieved (rt) in the new terminal afterwards.
Colorized Character Dumper
I have noticed that when i use the hexdump tool, it is most often together with the -C option just to look at the characters. Hexdump is a bit limited, so I decided to make another tool specialized for character dumping. Instead of just printing a dot for non-printable characters, this tool will use a colorized printable character. Escape characters for instance, are represented in green. The tool also tries to use as much terminal real-estate as possible, so it will look at the current column width to expand itself.
Here is how it looks when dumping a binary file. (Actually itself.):

To keep it simple, I just used the standard ANSI way of drawing colors. An alternative would have been to use (n)curses. Check out the code:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <unistd.h> static void fputc_color(int c, FILE *fh) { /* Based on vim's "unprintable" definition instead of ctype's isprint(). */ if (c < 0x20) /* Escape characters. */ fprintf(fh, "\e[32m%c\e[0m", c + 0x40); else if (c < 0x7F) /* Standard ASCII characters. */ fputc(c, fh); else if (c == 0x7F) /* The DEL character. */ fprintf(fh, "\e[32m%c\e[0m", '?'); else if (c < 0xA1) /* Special unprintable characters and the NBSP. */ fprintf(fh, "\e[31m%c\e[0m", c - 0x40); else /* "High" (Latin-1) characters. */ fprintf(fh, "\e[33m%c\e[0m", c); } static void dump_file(FILE *fh, int columns, int in_color) { int c, n; columns -= 12; /* Subtract the extra visualization. */ n = 0; while ((c = fgetc(fh)) != EOF) { if (n % columns == 0) { if (n > 0) printf("|\n"); printf("%08x |", n); } if (in_color) { fputc_color(c, stdout); } else { if (isprint(c)) fputc(c, stdout); else fputc('.', stdout); } n++; } if (n % columns < columns) printf("|\n"); } static void display_banner(char *text, int columns) { int i; printf("--- %s ", text); for (i = (strlen(text) + 5); i < columns; i++) printf("-"); printf("\n"); } int main(int argc, char *argv[]) { int i, columns, in_color; char *p; FILE *fh; p = getenv("COLUMNS"); if (p == NULL) columns = 80; /* Default, and most likely. */ else columns = atoi(p); in_color = 0; if (isatty(STDOUT_FILENO)) { p = getenv("TERM"); if (p != NULL) { if (strncmp(p, "xterm", 5) == 0) in_color = 1; } } if (argc > 1) { for (i = 1; i < argc; i++) { fh = fopen(argv[i], "r"); if (fh == NULL) continue; if (argc > 2) display_banner(argv[i], columns); dump_file(fh, columns, in_color); fclose(fh); } } else dump_file(stdin, columns, in_color); return 0; }
Python Menu System
To fit my specific requirements for a media center PC, I made a simple custom menu system. I also used the opportunity to learn some Python in the process. Basically, it reads a textual configuration file and sets up a graphical menu using Tk. The menu entries will execute a specific command when clicked, and can be wrapped in a file or numeric selection mechanism, if required.
Sorry for not using objects in the code, and I will admit that it's ugly with global variables in Python. But I'm a C-guy, and old habits die hard. :-)
#!/usr/bin/python import sys import os import Tkinter import tkFileDialog _num_value = 1 _num_label = None _num_top = None def read_config_file(path): try: fh = open(path, "r") list = [] for s in fh: list.append(s.split("#")) return list except IOError: print "Error: Unable to open config file for reading!" return None def menu_direct(command): print "Info: Direct execute: " + command os.system(command) def num_increment(): global _num_value, _num_label _num_value += 1 _num_label.config(text=str(_num_value)) def num_decrement(): global _num_value, _num_label _num_value -= 1 _num_label.config(text=str(_num_value)) def num_exec(command): global _num_value, _num_top new = command.replace("$", str(_num_value)) print "Info: Num execute: " + new os.system(new) _num_top.destroy() def menu_num_select(command): global _num_top, _num_label, _num_value _num_top = Tkinter.Tk() label = Tkinter.Label(_num_top, text="Number:") label.pack(fill="x", expand=True) plus = Tkinter.Button(_num_top, text="+", command=num_increment) plus.pack(fill="x", expand=True) _num_label = Tkinter.Label(_num_top, text=str(_num_value)) _num_label.pack(fill="x", expand=True) plus = Tkinter.Button(_num_top, text="-", command=num_decrement) plus.pack(fill="x", expand=True) f = lambda x = command: num_exec(x) go = Tkinter.Button(_num_top, text="GO!", command=f) go.pack(fill="x", expand=True) Tkinter.mainloop() def menu_file_select(command): file = tkFileDialog.askopenfilename() if file == (): return new = command.replace("$", str(file)) print "Info: File execute: " + new os.system(new) def main(): if len(sys.argv) != 2: print "Usage: %s <config file>" % sys.argv[0] sys.exit(1) list = read_config_file(sys.argv[1]) if list == None: sys.exit(1) top = Tkinter.Tk() for sublist in list: if sublist[0] == "Direct": # NOTE: This does not work: # f = lambda: menu_direct(sublist[2]) f = lambda x = sublist[2]: menu_direct(x) elif sublist[0] == "NumSelect": f = lambda x = sublist[2]: menu_num_select(x) elif sublist[0] == "FileSelect": f = lambda x = sublist[2]: menu_file_select(x) else: print "Error: Unknown menu type!" sys.exit(1) button = Tkinter.Button(top, text=sublist[1], command=f) button.pack(fill="x", expand=True) Tkinter.mainloop() if __name__ == "__main__": main()
Here is a typical config file (not very unlike the one I currently use):
FileSelect#Play File#/usr/bin/xine -pfq "$" NumSelect#Play DVD#/usr/local/bin/mplayer -fs -zoom dvd://$ Direct#Power Off#/sbin/poweroff
I used to use tabulators instead of '#' characters to separate the columns, but this really messes up any copy/paste/transfer, etc, so I just had to pick a character that is unlikely to be used in a command. Please just change the code if you don't like it. The '$' characters, are interpolated with the value from the file or numeric selection mechanism.
The final graphical representation of the menu will actually depend heavily on what version of Tk is installed, what window manager is used and font is used as default. Here is how it looks on one of my systems:

ASCII-fication Filter
I made a filter program to convert normal images into random colored ASCII characters. It is similar to aalib, but aalib does not use random characters, and uses only a few colors, (in order to comply with text terminals I think).
It operates on plain PGM (Portable Greymap) image files only, since this is easy to parse. You can involve the convert tool from ImageMagick to use any kind of image file, like this:
convert foo.jpg -compress none pgm:- | ./pgm-asciify | convert pgm:- foo.png
Please note that you will get the best results by using big images with high contrast.
The code is released under a MIT licence, and can be fetched here.
Here is an example:

Filtered from this image:

Number Conversion Tool
Here is another homemade tool that I use when dealing with numbers during programming, analysis, etc. The program will simply output its argument in decimal, hex and binary notation. The argument can be specified in any of the three notations by prefixing the number with 0x or 0b, or no prefix for decimal numbers. There is also support for specifying addition (+), subtraction (-) or xor (^), and then a second number to perform this operation.
The code is loosely based on the binary dumping tool presented earlier, and can be found here:
#include <stdio.h> #include <stdlib.h> #include <string.h> static int power_of_two(int n) { if (n == 0) return 1; else return 2 * power_of_two(n - 1); } static char *convert_to_bin(int integer) { static char buffer[33]; /* Should not be longer than 32-bits + NULL. */ int i, first_one; buffer[32] = '\0'; /* Always terminate. */ first_one = 31; for (i = 31; i >= 0; i--) { if (integer - power_of_two(i) >= 0) { buffer[31 - i] = '1'; if (first_one > (31 - i)) first_one = (31 - i); integer -= power_of_two(i); } else buffer[31 - i] = '0'; } return &buffer[first_one]; } static int convert_from_bin(char *number) { int value, n; char *p; value = 0; n = strlen(number) - 3; /* Also minus "0b". */ p = &number[2]; do { if (*p == '0') { n--; } else if (*p == '1') { value += power_of_two(n); n--; } } while (*p++ != '\0'); return value; } static int convert_to_int(char *number) { int integer; if (strncmp(number, "0x", 2) == 0) { sscanf(number, "0x%x", &integer); } else if (strncmp(number, "0b", 2) == 0) { integer = convert_from_bin(number); } else { integer = atoi(number); } return integer; } static void display_int(int integer, char *prefix) { printf("%s%-10u 0x%-10x 0b%s\n", prefix, integer, integer, convert_to_bin(integer)); } int main(int argc, char *argv[]) { int n1, n2, result; char *prefix; if (argc == 2) { /* Just display the number in different forms. */ n1 = convert_to_int(argv[1]); display_int(n1, " "); } else if (argc == 4) { /* Perform extra operation. */ n1 = convert_to_int(argv[1]); n2 = convert_to_int(argv[3]); if (argv[2][0] == '+') { result = n1 + n2; prefix = " + "; } else if (argv[2][0] == '-') { result = n1 - n2; prefix = " - "; } else if (argv[2][0] == '^') { result = n1 ^ n2; prefix = " ^ "; } else { fprintf(stderr, "%s: error: invalid operator.\n", argv[0]); return -1; } display_int(n1, " "); display_int(n2, prefix); display_int(result, " = "); } else { fprintf(stderr, "Usage: %s <number> [<+|-|^> <number>]\n", argv[0]); fprintf(stderr, " number can be decimal, hex (0x) or binary (0b)\n"); return -1; } return 0; }
Deus Ex vblank fix.
Deus Ex is one of those games that seems to work flawlessly under Wine in Linux, except for this issue I will mention below.
If you play Deus Ex on a fast machine, you may experience that the audio dialogs cuts off to early. According to information lying around on the Internet, this seems to be a generic bug/weakness in the Unreal engine, and apparently affects all platforms(?). But it can be fixed by forcing sync to vblank, which slows down the (too fast) frame rate. There does not seem to be any way of doing this from the menus in Deus Ex, but it can be done by configuring it in the graphics driver.
On Linux (at least with the proprietary Nvidia drivers) there is an easy way to setup sync to vblank automatically, by setting an environment variable.
You can just wrap/replace the actual binary with a small shell script like this:
#!/bin/sh export __GL_SYNC_TO_VBLANK=1 exec /path/to/deusex
RPN FPU Calculator
Just for fun, I wrote a RPN (Reverse Polish Notation) calculator in x86 assembler. It uses the FPU (Floating-Point Unit) for all the calculations, and some common C-library calls for input/output.
It only supports the basic arithmetic operations like addition, subtraction, multiplication and division, and only operates on integers. I have tried to emulate the basic behaviour of the common dc Unix command-line RPN calculator, so it will print the result when getting a 'p', and quit on a 'q'. Also be aware that it expects each operator or operand on a single line by itself.
It is written using AT&T syntax and will probably only assemble/link on Linux x86/i386 platforms. Check out the code:
/* * Assembling and linking commands: * as -o fpu-rpn.o fpu-rpn.s * ld -dynamic-linker /lib/ld-linux.so.2 -lc -o fpu-rpn fpu-rpn.o */ .section .data output_format: .asciz "%d\n" error_input: .asciz "Error: Unknown input.\n" error_stack: .asciz "Error: Stack empty.\n" .section .bss .lcomm input, 16 .lcomm buffer, 4 .lcomm status, 2 .section .text .globl _start _start: finit read_input: pushl stdin pushl $16 pushl $input call fgets addl $12, %esp /* End if EOF is reached (fgets returns NULL). */ cmpl $0, %eax je end /* Extract first character of returned input string. */ movl (%eax), %ebx /* Clear any errors on the FPU each round. */ fclex /* Switch-case like check on the input. */ cmpb $0x71, %bl # 'q' je end cmpb $0x70, %bl # 'p' je display_result cmpb $0x2B, %bl # '+' je addition cmpb $0x2D, %bl # '-' je subtraction cmpb $0x2A, %bl # '*' je multiplication cmpb $0x2F, %bl # '/' je division /* Check if between 0-9 to determine if it's a number. */ cmpb $0x30, %bl # '0' jl input_error cmpb $0x39, %bl # '9' jg input_error /* Convert number from string and push onto FPU's stack. */ pushl %eax call atoi addl $4, %esp movl %eax, buffer filds buffer jmp read_input input_error: push stdout push $error_input call fputs addl $8, %esp jmp read_input addition: faddp jmp check_stack subtraction: fsubrp jmp check_stack multiplication: fmulp jmp check_stack division: fdivrp jmp check_stack check_stack: fstsw status testw $0b1000000, status /* Test for "Stack Fault" flag. */ jz read_input fdecstp /* Decrement stack to avoid displaying the fake result. */ push stdout push $error_stack call fputs addl $8, %esp jmp read_input display_result: /* Display the value at the top of the FPU's stack. */ fistpl buffer fstsw status testw $0b1000000, status jnz read_input /* Just do the check again to receive error messsage. */ pushl buffer pushl $output_format call printf addl $8, %esp jmp read_input end: /* Tell the Linux kernel to end this process. */ movl $1, %eax # exit() movl $0, %ebx int $0x80
Byte Value Visualization
While doing cryptanalysis on some XOR-encrypted files, I came up with the idea of visualizing byte values in these files in order to discover patterns. Having used the SDL library on previous projects, this was a good candidate to draw the graphics.
Here is an output example, visualizing the source code of this application:

It is easy to see that this is a plain text file, since there are mostly printable ASCII characters in the 0x20 to 0x7F area. You can also see the low-value newline characters (0x0A) clearly.
Here is another example, visualizing the application in binary form after compilation:

This is clearly not human-readable text, as there is a lot of NULL (0x00) bytes, and some bytes with very high values (0xFF).
I believe it's a lot easier to spot patterns this way, instead of using a hex editor. The application reads data (byte values) from standard in and stops after 1024 bytes or EOF.
But enough talk, here is the source code:
#include <stdio.h> #include <stdlib.h> #include <SDL/SDL.h> #define MAX_DATA 1024 int main(int argc, char *argv[]) { int c, n; unsigned char data[MAX_DATA]; SDL_Surface *screen; SDL_Rect rect; SDL_Event event; n = 0; while ((c = fgetc(stdin)) != EOF) { data[n] = c; n++; if (n >= MAX_DATA) break; } if (SDL_Init(SDL_INIT_VIDEO) != 0) { fprintf(stderr, "Error: Unable to initalize SDL: %s\n", SDL_GetError()); return 1; } atexit(SDL_Quit); screen = SDL_SetVideoMode(n, 255, 16, SDL_DOUBLEBUF); if (screen == NULL) { fprintf(stderr, "Error: Unable to set video mode: %s\n", SDL_GetError()); return 1; } for (n = n - 1; n >= 0; n--) { rect.x = n; rect.y = 255 - data[n]; rect.w = 1; rect.h = data[n]; SDL_FillRect(screen, &rect, 0xffffff); } SDL_Flip(screen); while (1) { if (SDL_PollEvent(&event) == 1) { if (event.type == SDL_KEYDOWN) { if (event.key.keysym.sym == SDLK_q) break; } else if (event.type == SDL_QUIT) { break; } } SDL_Delay(50); } return 0; }
MP3 Cutting Tool
It is not uncommon to have a (dumb) MP3-player that fast-forwards at a static speed, regardless of the file size/length. This is very annoying when you want to skip ahead in a pod-cast or audio-book file that lasts for over an hour. To overcome this problem, I decided to make a tool to split/cut MP3 files into smaller parts.
In order to correctly cut MP3 files, you need to know exactly where the frame boundaries are, and perform the incisions there. This tool will find the boundaries and calculate where to cut, based on the number of parts the user has specified as input.
Note that ID3 tags will not be replicated in any way, so they will probably remain in only one of the produced parts.
#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; }
You will notice that the tool creates file names ending in 01, 02, etc. If this is a problem for you (or your MP3-player), simply issue a shell command like this (swaps the ".mp3" and the number):
for i in *.mp3*; do mv "$i" "${i/.mp3/}.mp3"; done