From e6a0e920af6a300b3f42da304574ba6541fe3bdc Mon Sep 17 00:00:00 2001 From: Marco van Dijk Date: Thu, 8 Jul 2021 00:46:54 +0200 Subject: [PATCH] fixes #7 add config file, created if it does not exist --- config.ini | 18 +++++++++++ lib/config.py | 47 +++++++++++++++++++++++++++ lib/dataStructures.py | 56 ++++++++++++++++---------------- lib/initSongs.py | 14 ++++---- main.py | 3 ++ output2img.py | 74 ++++++++++++++++++++----------------------- 6 files changed, 139 insertions(+), 73 deletions(-) create mode 100644 config.ini create mode 100644 lib/config.py diff --git a/config.ini b/config.ini new file mode 100644 index 0000000..2fcf73b --- /dev/null +++ b/config.ini @@ -0,0 +1,18 @@ +[input] +inputfolders = /mnt/koios/Band/1-sugmesties,/mnt/koios/Band/2-oefenen,/mnt/koios/Band/3-uitgewerkt +supportedextensions = .txt + +[output] +metafontfamily = fonts/CourierPrime-Regular.ttf +metafontweight = 8 +lyricfontfamily = fonts/CourierPrime-Regular.ttf +tablaturefontfamliy = fonts/CourierPrime-Bold.ttf +songfontweight = 14 +imagewidth = 595 +imageheight = 842 +backgroundcolour = 255,255,255 +fontcolour = 0,0,0 +metadatacolour = 128,128,128 +topmargin = 10 +leftmargin = 25 + diff --git a/lib/config.py b/lib/config.py new file mode 100644 index 0000000..e81c1b9 --- /dev/null +++ b/lib/config.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +## +# @file config.py +# +# @brief This program contains functions to load & create the program configuration +# +# @section description Description +# Contains the default values for all parameters +# Can create the config file update it with new settings +# +# @section notes Notes +# - +# +# @section todo TODO +# - if CMD arguments: load CMD arguments to override defaults + +import os +import configparser + +"""!@brief Load, creates and keeps the config up to date +""" +def initConfig(): + # Default config + global config + config = configparser.ConfigParser() + # If a config file exists, load it + if os.path.isfile('./config.ini'): + config.read('config.ini') + # Else load defaults + else: + config['input'] = {'inputFolders': "/mnt/koios/Band/1-sugmesties,/mnt/koios/Band/2-oefenen,/mnt/koios/Band/3-uitgewerkt", + 'supportedExtensions': ".txt"} + config['output'] = {'metafontfamily': 'fonts/CourierPrime-Regular.ttf', + 'metaFontWeight': 8, + 'lyricfontfamily': 'fonts/CourierPrime-Regular.ttf', + 'tablaturefontfamliy': 'fonts/CourierPrime-Bold.ttf', + 'songFontWeight': 14, + 'imageWidth': 595, 'imageHeight': 842, # A4 at 72dpi + 'backgroundColour': '255,255,255', + 'fontColour': '0,0,0', + 'metadataColour': '128,128,128', + 'topMargin': 10, + 'leftMargin': 25 + } + # (if CMD arguments: load CMD arguments to override specific settings) + with open('config.ini', 'w') as configfile: + config.write(configfile) \ No newline at end of file diff --git a/lib/dataStructures.py b/lib/dataStructures.py index 687b5dd..3708fe6 100644 --- a/lib/dataStructures.py +++ b/lib/dataStructures.py @@ -45,12 +45,12 @@ def isTablatureData(inputString): #print("Checking '{}' for line type".format(inputString)) # Assume tablature line if any digit if any(char.isdigit() for char in inputString): - #print("'{}' is a CHORD line, since it contains a number".format(inputString)) + #print("'{}' is a tablature line, since it contains a number".format(inputString)) return True # Assume tablature line if any character {/, #, (, ), } - chordSpecificCharacterString = r"/#" - if any(elem in inputString for elem in chordSpecificCharacterString): - #print("'{}' is a CHORD line, since it contains a chord specific character".format(inputString)) + tablatureSpecificCharacterString = r"/#" + if any(elem in inputString for elem in tablatureSpecificCharacterString): + #print("'{}' is a tablature line, since it contains a tablature specific character".format(inputString)) return True # Assume LYRIC line if any TEXT character OTHER THAN {a, b, c, d, e, f, g, h, b, x, m} lyricSpecificCharacterString = r"abcdefghbxm" @@ -64,7 +64,7 @@ def isTablatureData(inputString): if any(elem in inputString for elem in lyricSpecialChars): #print("'{}' is a LYRIC line, since it contains lyric specific special characters".format(inputString)) return False - # Else warn and assume chord line + # Else warn and assume tablature line #print("Unable to identify if '{}' is a lyric or tablature line. Assuming it is a tablature line. Please improve the isTablatureData function".format(inputString)) return True @@ -74,11 +74,11 @@ class Section: def __init__(self): # List of lines of lyrics strings self.lyrics = [] - # List of lines of chord strings - self.chords = [] + # List of lines of tablature strings + self.tablatures = [] # section type string self.header = "" - # string of chord and lyric data + # string of tablature and lyric data self.rawData = "" # Flag for succesfully parsed self.isParsed = False @@ -86,49 +86,49 @@ class Section: """!@brief Converts raw buffered data into separate Lyric and tablature lines @return None """ - # Parses self.rawData into lyrics and chord strings + # Parses self.rawData into lyrics and tablature strings def parseMe(self): isFirstLine = True - # Input sections may have chord-only or lyric-only sections - # So we have to insert empty lines if we have subsequent chord or lyric lines + # Input sections may have tablature-only or lyric-only sections + # So we have to insert empty lines if we have subsequent tablature or lyric lines lines = self.rawData.split('\r\n') for line in lines: - # Determine lyric or chord line - currentIsChord = isTablatureData(line) + # Determine lyric or tablature line + currentIsTablature = isTablatureData(line) # Initially just fill in the first line correctly if isFirstLine: isFirstLine = False - if currentIsChord: - self.chords.append(line) + if currentIsTablature: + self.tablatures.append(line) else: self.lyrics.append(line) # We want alternating lines, so if the prev is of the same type # we need to insert an empty line of the other type - elif currentIsChord == prevWasChord: - if currentIsChord: + elif currentIsTablature == prevWasTablature: + if currentIsTablature: #print("Inserting empty Lyric line") - self.chords.append(line) + self.tablatures.append(line) self.lyrics.append("") else: - #print("Inserting empty Chord line") + #print("Inserting empty tablature line") self.lyrics.append(line) - self.chords.append("") + self.tablatures.append("") # also insert the current line - elif currentIsChord: + elif currentIsTablature: #print("Inserting empty Lyric line") - self.chords.append(line) + self.tablatures.append(line) else: self.lyrics.append(line) # move on to next line, save current type - prevWasChord = currentIsChord + prevWasTablature = currentIsTablature # Simple check to see if it probably exported correctly - if abs(len(self.lyrics) - len(self.chords)) > 1: - print("Unable to parse section, since there is a mismatch between the amount of chord and lyric lines.") + if abs(len(self.lyrics) - len(self.tablatures)) > 1: + print("Unable to parse section, since there is a mismatch between the amount of tablature and lyric lines.") return # Add a trailing empty line if necessary - elif len(self.lyrics) > len(self.chords): - self.chords.append("") - elif len(self.lyrics) < len(self.chords): + elif len(self.lyrics) > len(self.tablatures): + self.tablatures.append("") + elif len(self.lyrics) < len(self.tablatures): self.lyrics.append("") self.isParsed = True diff --git a/lib/initSongs.py b/lib/initSongs.py index 6db37c3..29d9701 100644 --- a/lib/initSongs.py +++ b/lib/initSongs.py @@ -16,13 +16,12 @@ # - Support both paths to folders (like now) and to files directly # When the input is a file, check if it is .txt and init it # - Input locations should be set in a config file (init to CWD, overwrite by CMD arguments) +# - Use config supportedExtensions import lib.dataStructures +import lib.config import os -# For now manually whitelist folders to convert -whitelist = ["/mnt/koios/Band/1-sugmesties", "/mnt/koios/Band/2-oefenen", "/mnt/koios/Band/3-uitgewerkt"] - """!@brief Creates and inits a Song object This function creates a new Song object and sets the internal variables correctly Output folder name is derived from the name of the input file @@ -42,19 +41,22 @@ def initSong(filePath): """!@brief Returns the list of all Song objects created This function gets all supported input files in the specified input location(s) For each of these files it creates a Song object, ready to be read and then parsed + @param configObj configparser object @return list of intialised Song objects """ def getSongObjects(): + # Get config variables + configObj = lib.config.config['input'] # path to song folders, which MAY contain a .txt source file txtFileLocations = [] # list of Song objects songList = [] - # go through all input locations. find .txt files. - for inputFolder in whitelist: + for inputFolder in configObj['inputfolders'].split(','): + #print("Walking directory '{}'".format(inputFolder)) for root, dirs, files in os.walk(inputFolder): for name in files: - if(name[name.rfind('.'):] == ".txt"): + if(name[name.rfind('.'):] in configObj['supportedextensions']): filePath = os.path.join(root, name) #print("Found .txt file '{}'".format(filePath)) txtFileLocations.append(filePath) diff --git a/main.py b/main.py index 1758d61..75a071c 100644 --- a/main.py +++ b/main.py @@ -24,9 +24,12 @@ import lib.chordFinder import lib.dataStructures import lib.initSongs import lib.transpose +import lib.config import output2img def main(): + # Init config file + lib.config.initConfig() # Init Song objects for all songs with compatible inputs songs = lib.initSongs.getSongObjects() # Convert all songs into sections diff --git a/output2img.py b/output2img.py index 00dcc2b..0090eac 100644 --- a/output2img.py +++ b/output2img.py @@ -16,46 +16,29 @@ # to a config file on first boot. Overwrite these if they get passed via CMD arguments (or manually by user) # - Various prints should be printed at specific log levels, to easily switch between debug, info or warnings only +import lib.config import os -import lib.dataStructures from PIL import Image, ImageDraw, ImageFont -# size and font of metadata -metaFontFamily = 'fonts/CourierPrime-Regular.ttf' -metaFontWeight = 8 - -# size and font of chord and lyric text -lyricFontFamily = 'fonts/CourierPrime-Regular.ttf' -chordFontFamily = 'fonts/CourierPrime-Bold.ttf' -songFontWeight = 14 - -# image properties -imageWidth, imageHeight = (595, 842) # A4 at 72dpi -background = (255, 255, 255) -fontMetadata = ImageFont.truetype(metaFontFamily, metaFontWeight) -fontLyrics = ImageFont.truetype(lyricFontFamily, songFontWeight) -fontChords = ImageFont.truetype(chordFontFamily, songFontWeight) -fontColour = () -topMargin = 10 -leftMargin = 25 - """!@brief Calculates the height of rendered text This function calculates the dimensions of each line of text the section contains and returns the sum @param section lib.dataStructures.Section object + @param fontTablature ImageFont.truetype object of font we are using for tablature lines + @param fontLyrics ImageFont.truetype object of font we are using for lyric lines @return the total height of the section """ -def calcSectionHeight(section): +def calcSectionHeight(section, fontTablature, fontLyrics): lineIterator = 0 amountOfLines = len(section.lyrics) heightSum = 0 # consider section title - headerWidth, headerHeight = fontChords.getsize(section.header) + headerWidth, headerHeight = fontTablature.getsize(section.header) heightSum += headerHeight while lineIterator < amountOfLines: # Get chord&lyric line dimensions lyricTextWidth, lyricTextHeight = fontLyrics.getsize(section.lyrics[lineIterator]) - chordTextWidth, chordTextHeight = fontChords.getsize(section.chords[lineIterator]) + tablatureTextWidth, chordTextHeight = fontTablature.getsize(section.tablatures[lineIterator]) heightSum += lyricTextHeight + chordTextHeight lineIterator += 1 return heightSum @@ -67,22 +50,35 @@ def calcSectionHeight(section): It will overwrite existing images, but will not clear old images @param folderLocation path to where we want the images @param songObj lib.dataStructures.Song object + @param configObj configparser object @return None """ def outputToImage(folderLocation, songObj): # Create target Directory if doesn't exist if not os.path.exists(folderLocation): - os.mkdir(folderLocation) - print("Directory " , folderLocation , " Created ") + os.mkdir(folderLocation) + print("Directory " , folderLocation , " Created ") #else: - #print("Directory " , folderLocation , " already exists") + #print("Directory " , folderLocation , " already exists") + + configObj = lib.config.config['output'] + topMargin = int(configObj['topMargin']) + fontColour = tuple(int(var) for var in configObj['fontColour'].split(',')) + backgroundColour = tuple(int(var) for var in configObj['backgroundColour'].split(',')) + metadataColour = tuple(int(var) for var in configObj['metadataColour'].split(',')) + imageWidth = int(configObj['imageWidth']) + imageHeight = int(configObj['imageHeight']) + leftMargin = int(configObj['leftMargin']) + fontMetadata = ImageFont.truetype(configObj['metafontfamily'], int(configObj['metaFontWeight'])) + fontLyrics = ImageFont.truetype(configObj['lyricfontfamily'], int(configObj['songFontWeight'])) + fontTablature = ImageFont.truetype(configObj['tablaturefontfamliy'], int(configObj['songFontWeight'])) # Init image info imageNumber = 1 currentHeight = topMargin # New Image - a4image = Image.new('RGB',(imageWidth, imageHeight),(background)) + a4image = Image.new('RGB',(imageWidth, imageHeight),(backgroundColour)) draw = ImageDraw.Draw(a4image) # Write metadata @@ -93,7 +89,7 @@ def outputToImage(folderLocation, songObj): continue #print("meta line '{}'".format(line)) metadataTextWidth, metadataTextHeight = fontMetadata.getsize(line) - draw.text((leftMargin,currentHeight), line, fill=(128, 128, 128), font=fontMetadata) + draw.text((leftMargin,currentHeight), line, fill=metadataColour, font=fontMetadata) currentHeight += metadataTextHeight # Margin between metadata and the first section currentHeight += topMargin @@ -104,32 +100,32 @@ def outputToImage(folderLocation, songObj): # Reset section specific variables lineIterator = 0 amountOfLines = len(section.lyrics) - if (amountOfLines != len(section.chords)): + 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 # See if the section would fit on the current page - if it does not, write current buffered image & make the next image ready - if currentHeight + calcSectionHeight(section) > imageHeight: + if currentHeight + calcSectionHeight(section, fontTablature, fontLyrics) > imageHeight: #print("overflow! starting with a new image") outputLocation = folderLocation + "/" + str(imageNumber) + ".png" imageNumber += 1 a4image.save(outputLocation) currentHeight = topMargin - a4image = Image.new('RGB',(imageWidth, imageHeight),(background)) + a4image = Image.new('RGB',(imageWidth, imageHeight),(backgroundColour)) draw = ImageDraw.Draw(a4image) # write section title - headerWidth, headerHeight = fontChords.getsize(section.header) - draw.text((leftMargin,currentHeight), section.header, fill=(0, 0, 0), font=fontChords) + headerWidth, headerHeight = fontTablature.getsize(section.header) + draw.text((leftMargin,currentHeight), section.header, fill=fontColour, font=fontTablature) currentHeight += headerHeight # Write each line tablature&lyric data while lineIterator < amountOfLines: - #print("Printing chord line {} and lyrics line {}".format(section.chords[lineIterator], section.lyrics[lineIterator])) - # Get chord&lyric line + #print("Printing tablatures line {} and lyrics line {}".format(section.tablatures[lineIterator], section.lyrics[lineIterator])) + # Get tablatures&lyric line lyricTextWidth, lyricTextHeight = fontLyrics.getsize(section.lyrics[lineIterator]) - chordTextWidth, chordTextHeight = fontChords.getsize(section.chords[lineIterator]) + tablatureTextWidth, tablatureTextHeight = fontTablature.getsize(section.tablatures[lineIterator]) # add to image file - draw.text((leftMargin,currentHeight), section.chords[lineIterator], fill=(0, 0, 0), font=fontChords) - currentHeight += chordTextHeight - draw.text((leftMargin,currentHeight), section.lyrics[lineIterator], fill=(0, 0, 0), font=fontLyrics) + draw.text((leftMargin,currentHeight), section.tablatures[lineIterator], fill=fontColour, font=fontTablature) + currentHeight += tablatureTextHeight + draw.text((leftMargin,currentHeight), section.lyrics[lineIterator], fill=fontColour, font=fontLyrics) currentHeight += lyricTextHeight lineIterator += 1 #print("currentheight={}".format(currentHeight))