cho2txt

Extract lyrics from chordpro files
git clone git://git.relim.de/cho2txt.git
Log | Files | Refs | README | LICENSE

commit 79fc1582baced1a69dfd6bad946895596da71ae8
parent 2a1eab4ec740061b4130416f494e7f8332f531df
Author: nibo <kroekerrobin@gmail.com>
Date:   Sun,  2 Jul 2023 21:01:42 +0200

Add -t and -d options

Diffstat:
A.gitignore | 1+
Mcho2txt.1 | 26++++++++++++++++++++++----
Mcho2txt.c | 241++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
3 files changed, 208 insertions(+), 60 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +cho2txt diff --git a/cho2txt.1 b/cho2txt.1 @@ -1,10 +1,28 @@ -.TH "CHO2TXT 1" "June 2023" "User Commands" +.TH "CHO2TXT 1" "July 2023" "User Commands" .SH NAME cho2txt - Extract lyrics from chordpro files .SH SYNOPSIS .B cho2txt -[ filepath ] +[-t] +[-d] +[FILE]... .SH DESCRIPTION .PP -Provide a chordpro file via the first argument or pipe the contents of the chordpro file via stdin. -The lyrics will be extracted and '\\n', '\\t', '\\r' and ' ' will be trimmed at the beginning and the end. +Provide one or more chordpro files as arguments +or pipe the content of one or more chordpro files via stdin. +The lyrics will be extracted and '\\n', '\\t', '\\r' and ' ' +will be trimmed at the beginning and the end. +Also lines containing no lyrics and only chordpro directives will be removed. +The resulting text will be printed to stdout. +.TP +\fB\,-t\/\fR, \fB\,--title\/\fR +Print the title of the song, e.g. 'Love Me Tender'. +.TP +\fB\,-d\/\fR, \fB\,--directive\/\fR +Print the title of the song as chordpro directive, e.g. '{title: Love Me Tender}' +.SH EXAMPLES +.sp +.RS 4 +cat Love_Me_Tender.cho "You_ll_Never_Walk_Alone.cho" | cho2txt + +cho2txt -t Love_Me_Tender.cho diff --git a/cho2txt.c b/cho2txt.c @@ -4,49 +4,25 @@ #include <fcntl.h> #include <stdbool.h> #include <string.h> +#include <getopt.h> -char *extractLyrics(int fd) +enum print { - char *text = malloc(sizeof(char)); - int i = 0; - char buf; - bool isLyrics = true; - bool isBracket = false; - while (1) - { - if (read(fd, &buf, 1) == 1) - { - if (buf == '{') - isLyrics = false; - if (buf == '[') - isLyrics = false; - if (isLyrics) - { - if (buf == '\n' && isBracket) - { - isBracket = false; - continue; - } - text[i] = buf; - i++; - text = realloc(text, (i+1) * sizeof(char)); - } - if (buf == '}' || buf == ']') - { - isLyrics = true; - isBracket = true; - } - else - isBracket = false; - } - else - break; - } - text[i] = '\0'; - return text; + PRINT_NO, + PRINT_TITLE, + PRINT_TITLE_DIRECTIVE +}; + +void printHelp() +{ + static const char help[] = "Usage: cho2txt [-t] [-d] [FILE]...\n" + "Extract lyrics from chordpro files.\n\n" + " -t, --title\t\tPrint title\n" + " -d, --directive\tPrint title as chordpro directive\n"; + printf("%s", help); } -char *trimText(char *text) +char *trim(char *text) { char *trimmedText = NULL; int begin = 0; @@ -93,32 +69,185 @@ char *trimText(char *text) return trimmedText; } +char *parseTitle(const char *directive) +{ + char *title = NULL; + bool doParse = false; + int t = 0; + for (int i=0; i<strlen(directive); i++) + { + if (directive[i] == '}') + doParse = false; + if (doParse) + { + title = realloc(title, (t+1) * sizeof(char)); + title[t] = directive[i]; + t++; + } + if (directive[i] == ':') + doParse = true; + } + title = realloc(title, (t+1) * sizeof(char)); + title[t] = 0; + return trim(title); +} + +bool isTitle(const char *directive) +{ + static const char title[] = "title:"; + int t = 0; + for (int i=0; i<strlen(directive); i++) + { + if (i > 0 && t < 7) + { + if (directive[i] != title[t]) + return false; + t++; + if (t == 6) + return true; + } + } +} + +char *extractLyrics(int fd, enum print printTitle) +{ + char *text = malloc(sizeof(char)); + int i = 0; + int d = 0; + char buf; + bool isLyric = true; + bool isLyricInLine = false; + bool isDirectiveInLine = false; + bool isCurlyBrace = false; + char *directive = NULL; + while (1) + { + if (read(fd, &buf, 1) == 1) + { + if (buf == '[') + { + isLyric = false; + isDirectiveInLine = true; + } + if (buf == '{') + { + isLyric = false; + isDirectiveInLine = true; + isCurlyBrace = true; + } + if (isLyric) + { + if (buf == '\n' && !isLyricInLine && isDirectiveInLine) + goto IGNORE; + text[i] = buf; + i++; + text = realloc(text, (i+1) * sizeof(char)); + isLyricInLine = true; + IGNORE: + } + else + { + if (isCurlyBrace) + { + directive = realloc(directive, (d+1) * sizeof(char)); + directive[d] = buf; + d++; + } + } + if (buf == '}') + { + directive = realloc(directive, (d+1) * sizeof(char)); + directive[d] = 0; + if (printTitle > 0 && isTitle(directive)) + { + char *title; + if (printTitle == PRINT_TITLE) + title = parseTitle(directive); + else + { + title = malloc((strlen(directive)+1) * sizeof(char)); + strcpy(title, directive); + } + for (int k=0; k<strlen(title); k++) + { + text[i] = title[k]; + i++; + text = realloc(text, (i+1) * sizeof(char)); + } + free(title); + text[i] = '\n'; + i++; + text = realloc(text, (i+1) * sizeof(char)); + } + d = 0; + free(directive); + directive = NULL; + isCurlyBrace = false; + isLyric = true; + } + if (buf == ']') + isLyric = true; + if (buf == '\n') + { + isDirectiveInLine = false; + isLyricInLine = false; + } + } + else + break; + } + text[i] = '\0'; + return text; +} + int main(int argc, char *argv[]) { + int o = 0; + int optionIndex = 0; + enum print printTitle = PRINT_NO; char *lyrics, *trimmedLyrics; - switch (argc) + static struct option long_options[] = { + { "title", no_argument, 0, 't' }, + { "directive", no_argument, 0, 'd' }, + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } + }; + while ((o = getopt_long(argc, argv, "td", long_options, &optionIndex)) != -1) { + switch(o) { + case 't': + printTitle = PRINT_TITLE; + break; + case 'd': + printTitle = PRINT_TITLE_DIRECTIVE; + break; + case 'h': + printHelp(); + return 0; + } + } + if (argc == optind) { - case 1: - lyrics = extractLyrics(0); - trimmedLyrics = trimText(lyrics); - printf("%s\n", trimmedLyrics); - free(trimmedLyrics); - break; - case 2: - int fd = open(argv[1], O_RDONLY); + lyrics = extractLyrics(0, printTitle); + trimmedLyrics = trim(lyrics); + printf("%s\n", trimmedLyrics); + free(trimmedLyrics); + } + else + { + int fd; + for (int i=optind; i<argc; i++) + { + fd = open(argv[i], O_RDONLY); if (fd == -1) { - fprintf(stderr, "open failed.\n"); - return -1; + fprintf(stderr, "opening '%s' failed.\n", argv[i]); + continue; } - lyrics = extractLyrics(fd); - trimmedLyrics = trimText(lyrics); + lyrics = extractLyrics(fd, printTitle); + trimmedLyrics = trim(lyrics); printf("%s\n", trimmedLyrics); free(trimmedLyrics); - break; - default: - fprintf(stderr, "Either provide exactly one file or no file for reading from stdin.\n"); - return -1; + } } return 0; }