/* cuemaker.cpp - Copyright (C) 2007 >NIL: 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 the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include #include #include #include #include #include "getopt.h" // Needs to be defined before we include pcre.h #define PCRE_STATIC #include "pcre.h" // We'll use stderr for anything that should not go into the cue file // This way, a simple redirection is all that's needed. #define perr(...) fprintf(stderr, __VA_ARGS__) #define OVECCOUNT 30 // should be a multiple of 3 #define NB_SUPPORTED_EXT 4 #define MAX_EXT_SIZE 5 // Indexes for the location_index table #define PERFORMER 0 #define ALBUM_TITLE 1 #define TRACK_NUMBER 2 #define TRACK_TITLE 3 // Global variables // No bulletproofing here: it's only a cue file generator! char supported_ext[NB_SUPPORTED_EXT][MAX_EXT_SIZE] = { "flac", "ape", "wav", "mp3" }; int location_index[4] = {-1, -1, -1, -1}; int ovector[OVECCOUNT]; pcre *re; char *avoidstupidmicrosoftcasterrors[256]; char *filename = (char*)avoidstupidmicrosoftcasterrors; char *performer = NULL; char *album_title = NULL; int opt_pregap = 0; // Retrieve the matched pattern and print the line void cueline(char* format, int location_designator) { int i; char *substring_start; int substring_length; i = location_index[location_designator]+1; substring_start = filename + ovector[2*i]; substring_length = ovector[2*i+1] - ovector[2*i]; printf (format, substring_length, substring_start); } // Generate a cue entry according to the pattern int cueentry(int index) { int rc = pcre_exec( re, // the compiled pattern NULL, filename, // the subject string strlen(filename), 0, // start at offset 0 0, // default options ovector, // output vector for substring information OVECCOUNT); // number of elements in the output vector if (rc <= 0) // Matching failed: handle error cases { switch(rc) { case PCRE_ERROR_NOMATCH: perr("Couldn't match pattern with entry:\n \"%s\"\n", filename); break; case 0: // The output vector wasn't big enough rc = OVECCOUNT/3; perr("Pattern matching only has room for %d captured substrings\n", rc - 1); break; default: perr("Matching error %d in entry:\n \"%s\"\n", rc, filename); break; } return -1; } if(index == 1) // Before the first record, we also need to print the cue header { printf("REM COMMENT \"Generated by CueMaker v1.0\"\n"); // PERFORMER (ALBUM) if (performer != NULL) // Override Performer printf("PERFORMER \"%s\"\n", performer); else if (location_index[PERFORMER] != -1) cueline("PERFORMER \"%.*s\"\n", PERFORMER); else { perr("Performer could not be determined. Please use -p option.\n"); return -1; } // TITLE (ALBUM) if (album_title != NULL) // Override Album Title printf("TITLE \"%s\"\n", album_title); else if (location_index[ALBUM_TITLE] != -1) cueline("TITLE \"%.*s\"\n", ALBUM_TITLE); else { perr("Album Title could not be determined. Please use -a option.\n"); return -1; } } // FILE printf ("FILE \"%s.wav\" WAVE\n", filename); // TRACK // Use auto indexing if indexes are not present in filenames if (location_index[TRACK_NUMBER] != -1) cueline(" TRACK %.*s AUDIO\n", TRACK_NUMBER); else printf(" TRACK %02d AUDIO\n", index); // TITLE (TRACK) if (location_index[TRACK_TITLE] != -1) cueline(" TITLE \"%.*s\"\n", TRACK_TITLE); else { perr("Track Title could not be determined for file:\n \"%s.wav\"\n", filename); return -1; } // PERFORMER (TRACK) // Unlike what was the case for the Album Performer, the Track Performer // from the filename takes precedence over the -p option. // For overrides, just suppress (%S) the performer and use -p if (location_index[PERFORMER] != -1) cueline(" PERFORMER \"%.*s\"\n", PERFORMER); else printf(" PERFORMER \"%s\"\n", performer); // PREGAP if ((index == 1) || (opt_pregap)) // The first track should always have a 2 secs pregap // (Most cd-burning application will auto-generate it) // For other tracks, this is controlled by the -g option printf(" PREGAP 00:02:00\n"); // INDEX printf(" INDEX 01 00:00:00\n"); return 0; } int main (int argc, char *argv[]) { // Flags int opt_error=0; // getopt // General purpose int i=0; int j=0; int k=0; int location=0; char *naming_scheme = NULL; int erroffset; const char *error; char pattern[256]; // Windows directory listing operations WIN32_FIND_DATAA FindFileData; // ANSI version (*NON* UNICODE!) HANDLE hFind = INVALID_HANDLE_VALUE; DWORD dwError; char DirSpec[16] = ".\\*."; while ((i = getopt (argc, argv, "gp:a:")) != -1) switch (i) { case 'g': // Set Pregap opt_pregap = -1; break; case 'p': // Set Performer performer = optarg; break; case 'a': // Set Album Title album_title = optarg; break; case 'h': default: // Unknown option opt_error++; break; } perr ("\nCueMaker v1.0 : **NERO COMPLIANT** CUE file creator\n"); perr ("by >NIL:, November 2007\n\n"); if ( ((argc-optind) < 1) || opt_error) { puts ("usage: CueMaker [-g] [-p performer] [-a album_title] naming_scheme"); puts ("The naming scheme option can use the following expressions:"); puts (" %P : Performer"); puts (" %A : Album Title"); puts (" %N : Track Number"); puts (" %T : Track Title"); puts (" %S : Suppress (Ignore) Data"); puts ("Option -g adds a 2 second pregap to every track on the CD"); puts (""); exit (1); } // Copy naming_scheme for (i=0; i<(argc-optind); i++) { naming_scheme = argv[optind+i]; for (j=0; j<(int)strlen(naming_scheme); j++) { if (naming_scheme[j] != '%') pattern[k++] = naming_scheme[j]; else { j++; // Replace all '%P' options by a Perl-ier '(.*)' or '(\d*)' // and matches the location of that pattern with the type pattern[k++] = '('; pattern[k++] = '.'; switch(naming_scheme[j]) { case 'P': location_index[PERFORMER] = location++; break; case 'A': location_index[ALBUM_TITLE] = location++; break; case 'N': location_index[TRACK_NUMBER] = location++; // If it's an index, use digit pattern matching instead pattern[k-1] = '\\'; pattern[k++] = 'd'; break; case 'T': location_index[TRACK_TITLE] = location++; break; case 'S': location++; break; case 'd': // Matches Perl '\d' for suppression location++; pattern[k-1] = '\\'; pattern[k++] = 'd'; break; case 'w': // Matches Perl '\w' for suppression location++; pattern[k-1] = '\\'; pattern[k++] = 'w'; break; default: printf("%%%c: Unsupported pattern option\n", naming_scheme[j]); return -1; break; } pattern[k++] = '*'; pattern[k++] = ')'; } } } pattern[k] = 0; // Compile the search pattern re = pcre_compile( pattern, // the pattern 0, // default options &error, // for error message &erroffset, // for error offset NULL); // use default character tables if (re == NULL) // Compilation failed: print the error message and exit { perr("Pattern compilation failed at offset %d: %s\n", erroffset, error); exit(-1); } int found = 0; int index = 0; // In case indexes are not part of the naming scheme for (i = 0; (i < NB_SUPPORTED_EXT) && (!found); i++) { // List all entries by extension DirSpec[4] = 0; strncat_s(DirSpec, supported_ext[i], MAX_EXT_SIZE); // Find the first file in the directory. hFind = FindFirstFileA(DirSpec, &FindFileData); if (hFind != INVALID_HANDLE_VALUE) { // First entry in directory found = -1; // Get rid of extension (Yes, Microsoft even managed to screw strncpy!!) for (j=0; j<(int)(strlen(FindFileData.cFileName)- strlen(supported_ext[i])-1);j++) filename[j] = FindFileData.cFileName[j]; filename[j]=0; if (cueentry(++index)) { perr("Aborting.\n"); return -1; } while (FindNextFileA(hFind, &FindFileData) != 0) { // Subsequent directory entries for (j=0; j<(int)(strlen(FindFileData.cFileName)- strlen(supported_ext[i])-1);j++) filename[j] = FindFileData.cFileName[j]; filename[j]=0; if (cueentry(++index)) { perr("Aborting.\n"); return -1; } } dwError = GetLastError(); FindClose(hFind); if (dwError != ERROR_NO_MORE_FILES) { perr ("FindNextFile error. Error is %u\n", dwError); return -1; } } } if (!found) { perr("Could not find any supported audio files\n"); return -1; } return 0; }