mirror of
https://github.com/stronk-dev/Guitar-Sheet-Parser.git
synced 2025-07-05 08:25:09 +02:00
Added simple prerender functions to estimate seciton dimensions
Added width-overflow check. Program will downsize the fontSize down untill all lines are readable Moved config options into the song object Fixes #3
This commit is contained in:
parent
55daf981cc
commit
db9633a855
@ -36,7 +36,7 @@ def initConfig():
|
|||||||
'metaFontWeight': 8,
|
'metaFontWeight': 8,
|
||||||
'lyricfontfamily': 'fonts/CourierPrime-Regular.ttf',
|
'lyricfontfamily': 'fonts/CourierPrime-Regular.ttf',
|
||||||
'tablaturefontfamliy': 'fonts/CourierPrime-Bold.ttf',
|
'tablaturefontfamliy': 'fonts/CourierPrime-Bold.ttf',
|
||||||
'songFontWeight': 14,
|
'songFontWeight': 18,
|
||||||
'imageWidth': 595, 'imageHeight': 842, # A4 at 72dpi
|
'imageWidth': 595, 'imageHeight': 842, # A4 at 72dpi
|
||||||
'backgroundColour': '255,255,255',
|
'backgroundColour': '255,255,255',
|
||||||
'fontColour': '0,0,0',
|
'fontColour': '0,0,0',
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
# - Move read functions to separate input functions (also to support more types of inputs)
|
# - Move read functions to separate input functions (also to support more types of inputs)
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import lib.config
|
||||||
|
from PIL import ImageFont
|
||||||
|
|
||||||
"""!@brief Removes empty lines and makes sure every line ends with \r\n
|
"""!@brief Removes empty lines and makes sure every line ends with \r\n
|
||||||
@param inputString raw txt input
|
@param inputString raw txt input
|
||||||
@ -82,12 +84,43 @@ class Section:
|
|||||||
self.rawData = ""
|
self.rawData = ""
|
||||||
# Flag for succesfully parsed
|
# Flag for succesfully parsed
|
||||||
self.isParsed = False
|
self.isParsed = False
|
||||||
|
# Expected dimensions of this section
|
||||||
|
self.expectedWidth = -1
|
||||||
|
self.expectedHeight = -1
|
||||||
|
|
||||||
|
"""!@brief Calculates dimensions of rendered text
|
||||||
|
This function calculates the dimensions of each line of text
|
||||||
|
the section contains and sets the internal variables
|
||||||
|
@param section lib.dataStructures.Section object
|
||||||
|
@return None
|
||||||
|
"""
|
||||||
|
def calculateSectionDimensions(self, fontTablature, fontLyrics):
|
||||||
|
lineIterator = 0
|
||||||
|
amountOfLines = len(self.lyrics)
|
||||||
|
heightSum = 0
|
||||||
|
maxWidth = 0
|
||||||
|
# consider section title
|
||||||
|
headerWidth, headerHeight = fontTablature.getsize(self.header)
|
||||||
|
heightSum += headerHeight
|
||||||
|
maxWidth = headerWidth
|
||||||
|
while lineIterator < amountOfLines:
|
||||||
|
# Get chord&lyric line dimensions
|
||||||
|
lyricTextWidth, lyricTextHeight = fontLyrics.getsize(self.lyrics[lineIterator])
|
||||||
|
tablatureTextWidth, chordTextHeight = fontTablature.getsize(self.tablatures[lineIterator])
|
||||||
|
heightSum += lyricTextHeight + chordTextHeight
|
||||||
|
if lyricTextWidth > maxWidth:
|
||||||
|
maxWidth = lyricTextWidth
|
||||||
|
if tablatureTextWidth > maxWidth:
|
||||||
|
maxWidth = tablatureTextWidth
|
||||||
|
lineIterator += 1
|
||||||
|
self.expectedWidth = maxWidth
|
||||||
|
self.expectedHeight = heightSum
|
||||||
|
|
||||||
"""!@brief Converts raw buffered data into separate Lyric and tablature lines
|
"""!@brief Converts raw buffered data into separate Lyric and tablature lines
|
||||||
@return None
|
@return None
|
||||||
"""
|
"""
|
||||||
# Parses self.rawData into lyrics and tablature strings
|
# Parses self.rawData into lyrics and tablature strings
|
||||||
def parseMe(self):
|
def initSections(self):
|
||||||
isFirstLine = True
|
isFirstLine = True
|
||||||
# Input sections may have tablature-only or lyric-only sections
|
# 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
|
# So we have to insert empty lines if we have subsequent tablature or lyric lines
|
||||||
@ -150,11 +183,51 @@ class Song:
|
|||||||
self.rawData = ""
|
self.rawData = ""
|
||||||
# Flag for succesfully parsed
|
# Flag for succesfully parsed
|
||||||
self.isParsed = False
|
self.isParsed = False
|
||||||
|
configObj = lib.config.config['output']
|
||||||
|
self.topMargin = int(configObj['topMargin'])
|
||||||
|
self.fontColour = tuple(int(var) for var in configObj['fontColour'].split(','))
|
||||||
|
self.backgroundColour = tuple(int(var) for var in configObj['backgroundColour'].split(','))
|
||||||
|
self.metadataColour = tuple(int(var) for var in configObj['metadataColour'].split(','))
|
||||||
|
self.imageWidth = int(configObj['imageWidth'])
|
||||||
|
self.imageHeight = int(configObj['imageHeight'])
|
||||||
|
self.leftMargin = int(configObj['leftMargin'])
|
||||||
|
self.fontMetadata = ImageFont.truetype(configObj['metafontfamily'], int(configObj['metaFontWeight']))
|
||||||
|
self.fontSize = int(configObj['songFontWeight'])
|
||||||
|
self.fontLyrics = ImageFont.truetype(configObj['lyricfontfamily'], self.fontSize)
|
||||||
|
self.fontTablature = ImageFont.truetype(configObj['tablaturefontfamliy'], self.fontSize)
|
||||||
|
self.configObj = configObj
|
||||||
|
|
||||||
|
"""!@brief Calculates the expected dimensions of all sections
|
||||||
|
@return None
|
||||||
|
"""
|
||||||
|
def prerenderSections(self):
|
||||||
|
for section in self.sections:
|
||||||
|
section.calculateSectionDimensions(self.fontTablature, self.fontLyrics)
|
||||||
|
|
||||||
|
"""!@brief Checks whether we are overflowing on the width of the page
|
||||||
|
@return True if everything OK, False if overflowing
|
||||||
|
"""
|
||||||
|
def checkOverflowX(self):
|
||||||
|
for section in self.sections:
|
||||||
|
if section.expectedWidth > self.imageWidth:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
"""!@brief Resizes all sections by a specified amount
|
||||||
|
Also recalculates all section sizes afterwards
|
||||||
|
@param mutator amount of fontSize to add/dec from current font size
|
||||||
|
@return None
|
||||||
|
"""
|
||||||
|
def resizeAllSections(self, mutator):
|
||||||
|
self.fontSize += mutator
|
||||||
|
self.fontLyrics = ImageFont.truetype(self.configObj['lyricfontfamily'], self.fontSize)
|
||||||
|
self.fontTablature = ImageFont.truetype(self.configObj['tablaturefontfamliy'], self.fontSize)
|
||||||
|
self.prerenderSections()
|
||||||
|
|
||||||
"""!@brief Parses self.rawData into Section objects and metadata
|
"""!@brief Parses self.rawData into Section objects and metadata
|
||||||
@return None
|
@return None
|
||||||
"""
|
"""
|
||||||
def parseMe(self):
|
def initSections(self):
|
||||||
# Get raw data
|
# Get raw data
|
||||||
self.rawData = readSourceFile(self.inputFile)
|
self.rawData = readSourceFile(self.inputFile)
|
||||||
# Clean up input
|
# Clean up input
|
||||||
@ -194,7 +267,7 @@ class Song:
|
|||||||
#print("set rawData of '{}' to this section".format(thisSection.rawData))
|
#print("set rawData of '{}' to this section".format(thisSection.rawData))
|
||||||
parseData = parseData[delimiterIndex:]
|
parseData = parseData[delimiterIndex:]
|
||||||
# Finally parse section data
|
# Finally parse section data
|
||||||
thisSection.parseMe()
|
thisSection.initSections()
|
||||||
if thisSection.isParsed:
|
if thisSection.isParsed:
|
||||||
self.sections.append(thisSection)
|
self.sections.append(thisSection)
|
||||||
else:
|
else:
|
||||||
|
12
main.py
12
main.py
@ -34,12 +34,20 @@ def main():
|
|||||||
songs = lib.initSongs.getSongObjects()
|
songs = lib.initSongs.getSongObjects()
|
||||||
# Convert all songs into sections
|
# Convert all songs into sections
|
||||||
for song in songs:
|
for song in songs:
|
||||||
song.parseMe()
|
print("Start parsing of file '{}'...".format(song.inputFile))
|
||||||
|
# Initialise internal data structures
|
||||||
|
song.initSections()
|
||||||
|
# Prerender: calculate the expected dimensions for each section
|
||||||
|
song.prerenderSections()
|
||||||
|
# While we overflow on X: resize all sections down and recalculate
|
||||||
|
while not song.checkOverflowX():
|
||||||
|
#print("Overflowing on width of the page. Decreasing font size by 2...")
|
||||||
|
song.resizeAllSections(-2)
|
||||||
# Parse as PNG a4
|
# Parse as PNG a4
|
||||||
if song.isParsed:
|
if song.isParsed:
|
||||||
# Create subdirectory where we will output our images
|
# Create subdirectory where we will output our images
|
||||||
targetDirectory = song.outputLocation + "-a4-png"
|
targetDirectory = song.outputLocation + "-a4-png"
|
||||||
print("Successfully parsed '{}' file. Writing output to '{}'".format(song.inputFile, targetDirectory))
|
print("Successfully parsed file. Writing output to '{}'".format(song.inputFile, targetDirectory))
|
||||||
# Write out metadata and sections, as many as can fit on one page
|
# Write out metadata and sections, as many as can fit on one page
|
||||||
output2img.outputToImage(targetDirectory, song)
|
output2img.outputToImage(targetDirectory, song)
|
||||||
|
|
||||||
|
@ -12,36 +12,10 @@
|
|||||||
# -
|
# -
|
||||||
#
|
#
|
||||||
# @section todo TODO
|
# @section todo TODO
|
||||||
# - A lot of this stuff is hardcoded. We want to write default fonts, sizes, colours, margins, dimensions, wanted amount of pages
|
|
||||||
# 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
|
# - Various prints should be printed at specific log levels, to easily switch between debug, info or warnings only
|
||||||
|
|
||||||
import lib.config
|
|
||||||
import os
|
import os
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw
|
||||||
|
|
||||||
"""!@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, fontTablature, fontLyrics):
|
|
||||||
lineIterator = 0
|
|
||||||
amountOfLines = len(section.lyrics)
|
|
||||||
heightSum = 0
|
|
||||||
# consider section title
|
|
||||||
headerWidth, headerHeight = fontTablature.getsize(section.header)
|
|
||||||
heightSum += headerHeight
|
|
||||||
while lineIterator < amountOfLines:
|
|
||||||
# Get chord&lyric line dimensions
|
|
||||||
lyricTextWidth, lyricTextHeight = fontLyrics.getsize(section.lyrics[lineIterator])
|
|
||||||
tablatureTextWidth, chordTextHeight = fontTablature.getsize(section.tablatures[lineIterator])
|
|
||||||
heightSum += lyricTextHeight + chordTextHeight
|
|
||||||
lineIterator += 1
|
|
||||||
return heightSum
|
|
||||||
|
|
||||||
"""!@brief Exports the song object to images
|
"""!@brief Exports the song object to images
|
||||||
This function renders the metadata and sections
|
This function renders the metadata and sections
|
||||||
@ -50,7 +24,6 @@ def calcSectionHeight(section, fontTablature, fontLyrics):
|
|||||||
It will overwrite existing images, but will not clear old images
|
It will overwrite existing images, but will not clear old images
|
||||||
@param folderLocation path to where we want the images
|
@param folderLocation path to where we want the images
|
||||||
@param songObj lib.dataStructures.Song object
|
@param songObj lib.dataStructures.Song object
|
||||||
@param configObj configparser object
|
|
||||||
@return None
|
@return None
|
||||||
"""
|
"""
|
||||||
def outputToImage(folderLocation, songObj):
|
def outputToImage(folderLocation, songObj):
|
||||||
@ -61,24 +34,12 @@ def outputToImage(folderLocation, songObj):
|
|||||||
#else:
|
#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
|
# Init image info
|
||||||
imageNumber = 1
|
imageNumber = 1
|
||||||
currentHeight = topMargin
|
currentHeight = songObj.topMargin
|
||||||
|
|
||||||
# New Image
|
# New Image
|
||||||
a4image = Image.new('RGB',(imageWidth, imageHeight),(backgroundColour))
|
a4image = Image.new('RGB',(songObj.imageWidth, songObj.imageHeight),(songObj.backgroundColour))
|
||||||
draw = ImageDraw.Draw(a4image)
|
draw = ImageDraw.Draw(a4image)
|
||||||
|
|
||||||
# Write metadata
|
# Write metadata
|
||||||
@ -88,11 +49,11 @@ def outputToImage(folderLocation, songObj):
|
|||||||
if not line:
|
if not line:
|
||||||
continue
|
continue
|
||||||
#print("meta line '{}'".format(line))
|
#print("meta line '{}'".format(line))
|
||||||
metadataTextWidth, metadataTextHeight = fontMetadata.getsize(line)
|
metadataTextWidth, metadataTextHeight = songObj.fontMetadata.getsize(line)
|
||||||
draw.text((leftMargin,currentHeight), line, fill=metadataColour, font=fontMetadata)
|
draw.text((songObj.leftMargin,currentHeight), line, fill=songObj.metadataColour, font=songObj.fontMetadata)
|
||||||
currentHeight += metadataTextHeight
|
currentHeight += metadataTextHeight
|
||||||
# Margin between metadata and the first section
|
# Margin between metadata and the first section
|
||||||
currentHeight += topMargin
|
currentHeight += songObj.topMargin
|
||||||
# Iterate over each section
|
# Iterate over each section
|
||||||
# NOTE: sections might be split into lists of pages containing a list of sections
|
# NOTE: sections might be split into lists of pages containing a list of sections
|
||||||
# This change will occur when we add an arranger which resizes sections to fit pages better
|
# This change will occur when we add an arranger which resizes sections to fit pages better
|
||||||
@ -103,34 +64,37 @@ def outputToImage(folderLocation, songObj):
|
|||||||
if (amountOfLines != len(section.tablatures)):
|
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))
|
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
|
return
|
||||||
|
if (section.expectedHeight == -1 or section.expectedWidth == -1):
|
||||||
|
print("Cannot write this section to file, since it was not processed correctly. The expected dimensions are not set. Aborting...")
|
||||||
|
return
|
||||||
# See if the section would fit on the current page - if it does not, write current buffered image & make the next image ready
|
# 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, fontTablature, fontLyrics) > imageHeight:
|
if currentHeight + section.expectedHeight > songObj.imageHeight:
|
||||||
#print("overflow! starting with a new image")
|
#print("overflow! starting with a new image")
|
||||||
outputLocation = folderLocation + "/" + str(imageNumber) + ".png"
|
outputLocation = folderLocation + "/" + str(imageNumber) + ".png"
|
||||||
imageNumber += 1
|
imageNumber += 1
|
||||||
a4image.save(outputLocation)
|
a4image.save(outputLocation)
|
||||||
currentHeight = topMargin
|
currentHeight = songObj.topMargin
|
||||||
a4image = Image.new('RGB',(imageWidth, imageHeight),(backgroundColour))
|
a4image = Image.new('RGB',(songObj.imageWidth, songObj.imageHeight),(songObj.backgroundColour))
|
||||||
draw = ImageDraw.Draw(a4image)
|
draw = ImageDraw.Draw(a4image)
|
||||||
# write section title
|
# write section title
|
||||||
headerWidth, headerHeight = fontTablature.getsize(section.header)
|
headerWidth, headerHeight = songObj.fontTablature.getsize(section.header)
|
||||||
draw.text((leftMargin,currentHeight), section.header, fill=fontColour, font=fontTablature)
|
draw.text((songObj.leftMargin,currentHeight), section.header, fill=songObj.fontColour, font=songObj.fontTablature)
|
||||||
currentHeight += headerHeight
|
currentHeight += headerHeight
|
||||||
# Write each line tablature&lyric data
|
# Write each line tablature&lyric data
|
||||||
while lineIterator < amountOfLines:
|
while lineIterator < amountOfLines:
|
||||||
#print("Printing tablatures line {} and lyrics line {}".format(section.tablatures[lineIterator], section.lyrics[lineIterator]))
|
#print("Printing tablatures line {} and lyrics line {}".format(section.tablatures[lineIterator], section.lyrics[lineIterator]))
|
||||||
# Get tablatures&lyric line
|
# Get tablatures&lyric line
|
||||||
lyricTextWidth, lyricTextHeight = fontLyrics.getsize(section.lyrics[lineIterator])
|
lyricTextWidth, lyricTextHeight = songObj.fontLyrics.getsize(section.lyrics[lineIterator])
|
||||||
tablatureTextWidth, tablatureTextHeight = fontTablature.getsize(section.tablatures[lineIterator])
|
tablatureTextWidth, tablatureTextHeight = songObj.fontTablature.getsize(section.tablatures[lineIterator])
|
||||||
# add to image file
|
# add to image file
|
||||||
draw.text((leftMargin,currentHeight), section.tablatures[lineIterator], fill=fontColour, font=fontTablature)
|
draw.text((songObj.leftMargin,currentHeight), section.tablatures[lineIterator], fill=songObj.fontColour, font=songObj.fontTablature)
|
||||||
currentHeight += tablatureTextHeight
|
currentHeight += tablatureTextHeight
|
||||||
draw.text((leftMargin,currentHeight), section.lyrics[lineIterator], fill=fontColour, font=fontLyrics)
|
draw.text((songObj.leftMargin,currentHeight), section.lyrics[lineIterator], fill=songObj.fontColour, font=songObj.fontLyrics)
|
||||||
currentHeight += lyricTextHeight
|
currentHeight += lyricTextHeight
|
||||||
lineIterator += 1
|
lineIterator += 1
|
||||||
#print("currentheight={}".format(currentHeight))
|
#print("currentheight={}".format(currentHeight))
|
||||||
# Margin between each section
|
# Margin between each section
|
||||||
currentHeight += topMargin
|
currentHeight += songObj.topMargin
|
||||||
# No more sections left, so the current buffered image is ready to be written to file
|
# No more sections left, so the current buffered image is ready to be written to file
|
||||||
outputLocation = folderLocation + "/" + str(imageNumber) + ".png"
|
outputLocation = folderLocation + "/" + str(imageNumber) + ".png"
|
||||||
a4image.save(outputLocation)
|
a4image.save(outputLocation)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user