Simple Playlist Modifier
This program is a search and replace program for text based files. The main goal of the program is to replace directory paths to music files.
The idea is to have the program read files from input and then validate the given parameters (a file path in this case).
The Source M3U Contains -- created by VLC
#EXTM3U
#EXTINF:330,Martin Solveig, Dragonette & Idoling!!! - Big in Japan (Les Bros Remix)
C:\User\Name\Music\Singles & Misc\House\02 Big in Japan (Les Bros Remix).mp3
... ... and many, many, more songs.
I wanted all of the files to read as below without altering each one painstakingly in File Explorer, Finder, or Nautilus.
The Modified M3U Contents -- modified with plist
#EXTM3U
#EXTINF:330,Martin Solveig, Dragonette & Idoling!!! - Big in Japan (Les Bros Remix)
D:\Music\Singles & Misc\House\02 Big in Japan (Les Bros Remix).mp3
... ... and many, many, more songs. you get the point.
I needed a way to edit large playlists with ease and speed. So I made this program to help manage that.
There are 3 distinct Arguments and each is an attribute of its base option: the source file, source file path, and target file path.
As an example:
plist [file name] [source file path pattern] [target file path pattern]
plist mymusic.m3u c:\users\username\Music d:\Music
Think of it like strstr()
with the haystack and needle, only I'm searching literally strand by strand for the needles I'm looking for and doing it for all occurrences, not just the first occurrence found.
This program is built with VLC M3U playlists in mind and no other type.
The program does produce a backup file before processing and making alterations. If all else fails, just delete the file extension .backup so that it reads .m3u only.
The backup files are truncated with each use, but does generate an identifier based on the original files name. So be cautious.
I used MinGW to compile it.
Compiled as: $ gcc -std=c99 -Wall -Wextra -Wpedantic convert_plist.c -o plist
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#ifdef BUFSIZ
#undef BUFSIZ
#define BUFSIZ 1000
#endif
// if more symbols should be addressed, adjust as neccessary.
// for example, use an array if multiple characters are to be
// utilized as tokens to ignore or tokens to acknowledge...
const int BAD_INPUT = -1; //return code for bad input
const int BAD_BAKUP = -2; //--- --- bad backup file
const int BAD_BFMOD = -3; //--- --- bad backup file modification
const int IGN_ITEMS = 1; //max number of ignored tokens
const int ACK_ITEMS = 6; //max number of acknowledged tokens
const int EXT_ITEMS = 4; //max number of file extensions
typedef _Bool bool; //typedef to prettify
//define bool keywords true and false
const bool true = 1;
const bool false = 0;
//use lables to differentiate argument types
enum ARGUMENT { self = 1, source, target, met };
const char * m3u = ".m3u";
void rmnewline(char * expression)
{//remove the newline character
char * newline = NULL;
size_t position, copylen;
size_t stringlen = strlen(expression);
if (NULL != (newline = strrchr(expression, '\n')))
{//remove the newline if expression is valid
copylen = strlen(newline);
position = stringlen - copylen;
expression[position] = '\0';
}
}
bool valid_args(int argc)
{//validate the number of correct arguments
bool status = false;
switch(argc)
{//assume at least one argument is missing, else all arguments are met (since 4 are valid)
case self:
puts("\nplist - Portable Playlist Modifier\n"
"This program replaces the Source File Path Pattern with the Target Path Pattern.\n\n"
"Usage: plist [file_name] [source_path_pattern] [target_path_pattern]\n\n"
"[file_name] is the playlist file to process.\n"
"[source_path_pattern] is the string pattern to be replaced.\n"
"[target_path_pattern] is the string pattern that replaces the source pattern.\n\n"
"Example: plist /path/to/my/playlist.m3u /current/path/to/my/music /new/path/to/my/music\n"
"Use M3U playlist files, or modify at your own expense to manage other playlist types.");
break;
case source:
puts("No [source_path_pattern] was provided.");
puts("Invoke [plist] without arguments for help.");
break;
case target:
puts("No [target_path_pattern] was provided.");
puts("Invoke [plist] without arguments for help.");
break;
case met:
status = true;
break;
default:
puts("Invalid use of [plist].");
puts("Invoke [plist] without arguments for help.");
break;
}
return status;
}
bool valid_m3u_ext(const char * filename)
{//validate the m3u file extension exclusively
bool status = false;
char * container = strrchr(filename, '.');
(!strcmp(container, m3u)) ? status = true : puts("Invalid argument(s) supplied.");
return status;
}
char * mod_filename(const char * filename)
{//modify the file name so that it contains a ".backup" extension
size_t nullchar = 1;
char * temp = malloc(strlen(filename) + strlen(".backup") + nullchar);
assert(NULL != temp);
strcpy(temp, filename);
rmnewline(temp); //if it does not find a newline, it does nothing
strcat(temp, ".backup");
return temp;
}
void copy_playlist(const char * filename, FILE * playlist, FILE * backup)
{//copy the file contents and report the number of bytes counted
int ch, count = 0;
while ((ch = getc(playlist)) != EOF)
{
putc(ch, backup);
++count;
}
printf("Backup created successfully: %s\n", filename);
printf("%d bytes were copied there.\n\n", count);
}
char * backup_playlist(char * filename)
{//backup the playlist file
char * temp = mod_filename(filename);
//open playlist file for read-only
FILE * playlist = fopen(filename, "r");
//create a new file for writing and updating
FILE * backup = fopen(temp, "w");
if (NULL == playlist || NULL == backup)
{
puts("Failed to open and backup file(s).");
return NULL;
}
copy_playlist(temp, playlist, backup);
//close the opened files
fclose(playlist);
fclose(backup);
return temp;
}
void copy_metadata(FILE * newplist, FILE * oldplist)
{
int ch;
putc('#', newplist); //make sure you add the newline!
while ('\n' != (ch = getc(oldplist)))
{//if the line starts with a '#', copy it over
putc(ch, newplist);
}
putc('\n', newplist);
}
void compare_src_txt(char * source, FILE * oldplist)
{//compare source text and if it matches, skip over it
int ch;
for (size_t i = 1; i < strlen(source); i++)
{
if ((ch = getc(oldplist)) != source[i])
{
break;
}
}
}
void write_target_txt(char * target, FILE * newplist)
{//write the target text to where the source text was
for (size_t i = 0; i < strlen(target); i++)
{
putc(target[i], newplist);
}
}
void cat_src_txt(FILE * newplist, FILE * oldplist)
{//concatenate the rest of the source text to the file
int ch;
while ('\n' != (ch = getc(oldplist)))
{//copy over the rest of the source text
putc(ch, newplist);
}
putc('\n', newplist); //make sure you add the newline!
}
bool char_filter(char * source, char * target, FILE * newplist, FILE * oldplist)
{//read file character by character -- tedious, i know, but precise.
int ch;
while (EOF != (ch = getc(oldplist)))
{
switch(ch)
{
case '#':
copy_metadata(newplist, oldplist);
break;
case '/': //"root" for unix
case 'c': //"c:\" for windows
case 'C':
case 'd': //"d:\" for windows
case 'D':
compare_src_txt(source, oldplist);
write_target_txt(target, newplist);
cat_src_txt(newplist, oldplist);
break;
default:
return false;
}
}
return true;
}
bool modify_playlist(char * filename, char * backup, char * source, char * target)
{//modify the playlist using the given source, and target, text
//open playlist file for write-only
FILE * newplist = fopen(filename, "w");
//use the backup for read-only
FILE * oldplist = fopen(backup, "r");
if (NULL == newplist || NULL == oldplist)
{//make sure the files were opened
puts("Failed to open and backup file(s).");
return false;
}
if (!char_filter(source, target, newplist, oldplist))
{
puts("Invalid initializing of a character after newline.");
return false;
}
fclose(newplist);
fclose(oldplist);
printf("%s was modified successfully!\n", filename);
return true;
}
int main(int argc, char * argv[])
{
if (!valid_args(argc))
{//must have at least one argument
return BAD_INPUT;
}
if (!valid_m3u_ext(argv[1]))
{//must be a defined file extension type
return BAD_INPUT;
}
//each pointer now has a given name instead of argv[SOME_VALUE]
char * filename = argv[1];
char * source = argv[2];
char * target = argv[3];
char * backup = backup_playlist(filename);
if (NULL == backup)
{//backup must be good before moving on to writing data
return BAD_BAKUP;
}
if (!modify_playlist(filename, backup, source, target))
{
return BAD_BFMOD;
}
return 0;
}