diff --git a/lib/config.py b/lib/config.py index b7d0a60..3761976 100644 --- a/lib/config.py +++ b/lib/config.py @@ -30,8 +30,13 @@ def initConfig(): # Else load defaults else: config['input'] = {'inputFolders': os.getcwd(), - 'supportedExtensions': ".txt", - 'maxDepth': 3 + 'maxDepth': 2, + 'readtxt': 1, + 'readraw': 1 + } + config['options'] = {'exporttoimg': 1, + 'exporttotxt': 0, + 'exporttoraw': 0 } config['output'] = {'metafontfamily': 'fonts/CourierPrime-Regular.ttf', 'metaFontWeight': 16, diff --git a/lib/dataStructures.py b/lib/dataStructures.py index 8c7dd38..fe45364 100644 --- a/lib/dataStructures.py +++ b/lib/dataStructures.py @@ -184,6 +184,7 @@ class Song: self.inputFile = "" # Path to folder self.outputLocation = "" + self.fileExtension = "" # Title - based on input file self.title = "" # List of Section objects @@ -216,7 +217,9 @@ class Song: self.rightMargin = int(configObj['rightMargin']) self.fontLyrics = ImageFont.truetype(configObj['lyricfontfamily'], self.fontSize) self.fontTablature = ImageFont.truetype(configObj['tablaturefontfamliy'], self.fontSize) - self.configObj = configObj + self.fontFamilyLyrics = configObj['lyricfontfamily'] + self.fontFamilyTablature = configObj['tablaturefontfamliy'] + """!@brief Calculates dimensions of metadata @@ -247,8 +250,8 @@ class Song: def resizeAllSections(self, mutator): #print("Resizing font by {} to {}".format(mutator, self.fontSize)) self.fontSize += mutator - self.fontLyrics = ImageFont.truetype(self.configObj['lyricfontfamily'], self.fontSize) - self.fontTablature = ImageFont.truetype(self.configObj['tablaturefontfamliy'], self.fontSize) + self.fontLyrics = ImageFont.truetype(self.fontFamilyLyrics, self.fontSize) + self.fontTablature = ImageFont.truetype(self.fontFamilyTablature, self.fontSize) self.prerenderSections() """!@brief Calculates the expected dimensions of all sections @@ -378,6 +381,13 @@ class Song: # No more sections left, so the current buffered image is ready to be written to file curPage.totalHeight = currentHeight self.pages.append(curPage) + + """!@brief Parses self.rawData into Section objects and metadata + Assumes the raw data is preprocessed, so it parses it using set rules instead of guessing line attributes + @return None + """ + def initPreprocessed(self): + pass """!@brief Parses self.rawData into Section objects and metadata @return None @@ -404,7 +414,7 @@ class Song: # Get header on the first line delimiterIndex = parseData.find("]\r\n") if delimiterIndex == -1: - print("Cannot parse input file, delimitor did not match '[]'") + print("Cannot parse input file, delimiter did not match '[]'") return # Skip the ']\r\n' characters thisSection.header = parseData[:delimiterIndex+3] diff --git a/lib/initSongs.py b/lib/initSongs.py index 3c3b3f5..ce238d5 100644 --- a/lib/initSongs.py +++ b/lib/initSongs.py @@ -27,6 +27,7 @@ import os def initSong(filePath): thisSong = lib.dataStructures.Song() thisSong.inputFile = filePath + thisSong.fileExtension = filePath[filePath.rfind('.')+1:] # set base folder name - depending on selected outputs the output folder name changes thisSong.outputLocation = filePath[:filePath.rfind('.')] # title is just the name of the .txt file @@ -71,11 +72,11 @@ def getSongObjects(): # get all files we can find, then filter on supported extensions for inputFolder in configObj['inputfolders'].split(','): for filePath in walkDirectory(inputFolder, recursionDepth): - if(filePath[filePath.rfind('.'):] in configObj['supportedextensions']): - #print("Found .txt file '{}'".format(filePath)) + if ((filePath[filePath.find('.'):] == ".txt" ) and configObj['readtxt']) or ((filePath[filePath.find('.'):] == ".rawtxt" ) and configObj['readraw']): + #print("Found supported file '{}'".format(filePath)) txtFileLocations.append(filePath) #else: - #print("Skipping file '{}' for it is not a .txt file".format(filePath)) + #print("Skipping file '{}' for it is not a supported file".format(filePath)) # create list of Song objects while(txtFileLocations): filePath = txtFileLocations.pop() diff --git a/main.py b/main.py index 3452e16..31eaba4 100644 --- a/main.py +++ b/main.py @@ -26,37 +26,63 @@ import lib.initSongs import lib.transpose import lib.config import output2img +import output2txt def main(): # Init config file lib.config.initConfig() # Init Song objects for all songs with compatible inputs songs = lib.initSongs.getSongObjects() + # Get what programs we are going to run + configObj = lib.config.config['options'] + exportToImg = configObj['exporttoimg'] + exportToTxt = configObj['exporttotxt'] + exportToRaw = configObj['exporttoraw'] + # Convert all songs into sections for song in songs: print("Start parsing of file '{}'...".format(song.inputFile)) # Initialise internal data structures - song.initSections() + if song.fileExtension == 'txt': + song.initSections() + elif song.fileExtension == 'raw': + pass#song.initPreprocessed() + # If input is .raw output. If output to raw is set, overwrite itself + # ready quickly using rules if not song.isParsed: print("Song was not initialized correctly. Skipping...") continue - # Fit all sections on each page, resizes down if it does not fit on width - song.fitSectionsByWidth() - # Prerender: calculate Pages, and move sections into Pages - song.sectionsToPages() - # Optimalisation: try to fill whitespace - while song.canFillWhitespace(): - print("Resizing down to fit whitespace more efficiently") - song.resizeAllSections(-1) + + if exportToTxt: + # Create subdirectory where we will output our images + targetDirectory = song.outputLocation + "-txt" + print("Successfully parsed file. Writing output to '{}'\n".format(targetDirectory)) + # Write out metadata and sections, as many as can fit on one page + output2txt.outputToTxt(targetDirectory, False, song) + if exportToRaw: + # Create subdirectory where we will output our images + targetDirectory = song.outputLocation + "-txt" + print("Successfully parsed file. Writing output to '{}'\n".format(targetDirectory)) + # Write out metadata and sections, as many as can fit on one page + output2txt.outputToTxt(targetDirectory, True, song) + if exportToImg: + # Fit all sections on each page, resizes down if it does not fit on width + song.fitSectionsByWidth() + # Prerender: calculate Pages, and move sections into Pages song.sectionsToPages() - # Optimalisation: increase font size as long as the amount of pages does not increase or we cause an overflow on width - song.increaseWhileSameAmountOfPages() - # Parse as PNG a4 - # Create subdirectory where we will output our images - targetDirectory = song.outputLocation + "-a4-png" - print("Successfully parsed file. Writing output to '{}'\n".format(targetDirectory)) - # Write out metadata and sections, as many as can fit on one page - output2img.outputToImage(targetDirectory, song) + # Optimalisation: try to fill whitespace + while song.canFillWhitespace(): + print("Resizing down to fit whitespace more efficiently") + song.resizeAllSections(-1) + song.sectionsToPages() + # Optimalisation: increase font size as long as the amount of pages does not increase or we cause an overflow on width + song.increaseWhileSameAmountOfPages() + # Parse as PNG a4 + # Create subdirectory where we will output our images + targetDirectory = song.outputLocation + "-a4-png" + print("Successfully parsed file. Writing output to '{}'\n".format(targetDirectory)) + # Write out metadata and sections, as many as can fit on one page + output2img.outputToImage(targetDirectory, song) if __name__ == "__main__": main() diff --git a/output2img.py b/output2img.py index 847459e..390ed92 100644 --- a/output2img.py +++ b/output2img.py @@ -86,7 +86,7 @@ def outputToImage(folderLocation, songObj): # Margin between each section currentHeight += songObj.topMargin # Got all sections in the page, so write it - outputLocation = folderLocation + "/" + str(imageNumber) + ".png" + outputLocation = folderLocation + "/" + songObj.title + '-' + str(imageNumber) + ".png" a4image.save(outputLocation) a4image = Image.new('RGB',(songObj.imageWidth, songObj.imageHeight),(songObj.backgroundColour)) draw = ImageDraw.Draw(a4image) diff --git a/output2txt.py b/output2txt.py new file mode 100644 index 0000000..cf617fe --- /dev/null +++ b/output2txt.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +## +# @file output2img.py +# +# @brief This program converts the internal data structure to an image file +# +# @section description Description +# Generates PNG images of a specific dimension (currently A4) of tablature data +# Dynamically resizes specific sections to maximize using the entire paper (and avoid awkward page flips) +# +# @section notes Notes +# - +# +# @section todo TODO +# - Various prints should be printed at specific log levels, to easily switch between debug, info or warnings only + +import os + +"""!@brief Exports the song object to a txt file + Perfect to use as source file for any program which requires + tabs as input, due to the predictable layout of + metadata + [section title] + + + ... + @param folderLocation path to where we want the text file + @param printRaw if set, prints empty lines as well and saves as .raw + if false, will print in a more readable format + @param songObj lib.dataStructures.Song object + @return None +""" +def outputToTxt(folderLocation, printRaw, songObj): + # Create target Directory if doesn't exist + if not os.path.exists(folderLocation): + os.mkdir(folderLocation) + print("Directory " , folderLocation , " Created ") + #else: + #print("Directory " , folderLocation , " already exists") + + output = "" + # Write metadata + for line in songObj.metadata.split('\n'): + # remove any unwanted characters from metadata + line = line.rstrip() + if not line: + continue + #print("meta line '{}'".format(line)) + output += line + '\r\n' + # If exporting raw, do not include the whitespace between metadata and sections + if not printRaw: + output += '\r\n' + # Draw all pages + for section in songObj.sections: + lineIterator = 0 + amountOfLines = len(section.lyrics) + if (amountOfLines != len(section.tablatures)): + print("Cannot write this section to file, since it was not processed correctly. There are {} tablature lines and {} lyric lines. Aborting...".format(len(section.chords), amountOfLines)) + return + # write section title + output += section.header.rstrip() + '\r\n' + # Write each line tablature&lyric data + while lineIterator < amountOfLines: + tabline = section.tablatures[lineIterator].rstrip() + lyricline = section.lyrics[lineIterator].rstrip() + if printRaw or len(tabline): + output += tabline + '\r\n' + if printRaw or len(lyricline): + output += lyricline + '\r\n' + lineIterator += 1 + # If exporting raw, do not include the whitespace between sections + if not printRaw: + output += '\r\n' + # Finished, so print some trailing endlines + outputLocation = "" + if not printRaw: + output += '\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n' + outputLocation = folderLocation + "/" + songObj.title + ".txt" + else: + outputLocation = folderLocation + "/" + songObj.title + ".rawtxt" + with open(outputLocation, "w") as fileOut: + fileOut.write(output) + + + + + + + + + + + + +