mirror of
https://github.com/stronk-dev/Guitar-Sheet-Parser.git
synced 2025-07-05 00:25:08 +02:00
parent
5be0f83c0d
commit
e6a0e920af
18
config.ini
Normal file
18
config.ini
Normal file
@ -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
|
||||
|
47
lib/config.py
Normal file
47
lib/config.py
Normal file
@ -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)
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
3
main.py
3
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
|
||||
|
@ -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))
|
||||
|
Loading…
x
Reference in New Issue
Block a user