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,
|
||||
'lyricfontfamily': 'fonts/CourierPrime-Regular.ttf',
|
||||
'tablaturefontfamliy': 'fonts/CourierPrime-Bold.ttf',
|
||||
'songFontWeight': 14,
|
||||
'songFontWeight': 18,
|
||||
'imageWidth': 595, 'imageHeight': 842, # A4 at 72dpi
|
||||
'backgroundColour': '255,255,255',
|
||||
'fontColour': '0,0,0',
|
||||
|
@ -14,6 +14,8 @@
|
||||
# - Move read functions to separate input functions (also to support more types of inputs)
|
||||
|
||||
import re
|
||||
import lib.config
|
||||
from PIL import ImageFont
|
||||
|
||||
"""!@brief Removes empty lines and makes sure every line ends with \r\n
|
||||
@param inputString raw txt input
|
||||
@ -82,12 +84,43 @@ class Section:
|
||||
self.rawData = ""
|
||||
# Flag for succesfully parsed
|
||||
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
|
||||
@return None
|
||||
"""
|
||||
# Parses self.rawData into lyrics and tablature strings
|
||||
def parseMe(self):
|
||||
def initSections(self):
|
||||
isFirstLine = True
|
||||
# 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
|
||||
@ -150,11 +183,51 @@ class Song:
|
||||
self.rawData = ""
|
||||
# Flag for succesfully parsed
|
||||
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
|
||||
@return None
|
||||
"""
|
||||
def parseMe(self):
|
||||
def initSections(self):
|
||||
# Get raw data
|
||||
self.rawData = readSourceFile(self.inputFile)
|
||||
# Clean up input
|
||||
@ -194,7 +267,7 @@ class Song:
|
||||
#print("set rawData of '{}' to this section".format(thisSection.rawData))
|
||||
parseData = parseData[delimiterIndex:]
|
||||
# Finally parse section data
|
||||
thisSection.parseMe()
|
||||
thisSection.initSections()
|
||||
if thisSection.isParsed:
|
||||
self.sections.append(thisSection)
|
||||
else:
|
||||
|
12
main.py
12
main.py
@ -34,12 +34,20 @@ def main():
|
||||
songs = lib.initSongs.getSongObjects()
|
||||
# Convert all songs into sections
|
||||
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
|
||||
if song.isParsed:
|
||||
# Create subdirectory where we will output our images
|
||||
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
|
||||
output2img.outputToImage(targetDirectory, song)
|
||||
|
||||
|
@ -12,36 +12,10 @@
|
||||
# -
|
||||
#
|
||||
# @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
|
||||
|
||||
import lib.config
|
||||
import os
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
"""!@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
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
"""!@brief Exports the song object to images
|
||||
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
|
||||
@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):
|
||||
@ -61,24 +34,12 @@ def outputToImage(folderLocation, songObj):
|
||||
#else:
|
||||
#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
|
||||
currentHeight = songObj.topMargin
|
||||
|
||||
# New Image
|
||||
a4image = Image.new('RGB',(imageWidth, imageHeight),(backgroundColour))
|
||||
a4image = Image.new('RGB',(songObj.imageWidth, songObj.imageHeight),(songObj.backgroundColour))
|
||||
draw = ImageDraw.Draw(a4image)
|
||||
|
||||
# Write metadata
|
||||
@ -88,11 +49,11 @@ def outputToImage(folderLocation, songObj):
|
||||
if not line:
|
||||
continue
|
||||
#print("meta line '{}'".format(line))
|
||||
metadataTextWidth, metadataTextHeight = fontMetadata.getsize(line)
|
||||
draw.text((leftMargin,currentHeight), line, fill=metadataColour, font=fontMetadata)
|
||||
metadataTextWidth, metadataTextHeight = songObj.fontMetadata.getsize(line)
|
||||
draw.text((songObj.leftMargin,currentHeight), line, fill=songObj.metadataColour, font=songObj.fontMetadata)
|
||||
currentHeight += metadataTextHeight
|
||||
# Margin between metadata and the first section
|
||||
currentHeight += topMargin
|
||||
currentHeight += songObj.topMargin
|
||||
# Iterate over each section
|
||||
# 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
|
||||
@ -103,34 +64,37 @@ def outputToImage(folderLocation, songObj):
|
||||
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
|
||||
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
|
||||
if currentHeight + calcSectionHeight(section, fontTablature, fontLyrics) > imageHeight:
|
||||
if currentHeight + section.expectedHeight > songObj.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),(backgroundColour))
|
||||
currentHeight = songObj.topMargin
|
||||
a4image = Image.new('RGB',(songObj.imageWidth, songObj.imageHeight),(songObj.backgroundColour))
|
||||
draw = ImageDraw.Draw(a4image)
|
||||
# write section title
|
||||
headerWidth, headerHeight = fontTablature.getsize(section.header)
|
||||
draw.text((leftMargin,currentHeight), section.header, fill=fontColour, font=fontTablature)
|
||||
headerWidth, headerHeight = songObj.fontTablature.getsize(section.header)
|
||||
draw.text((songObj.leftMargin,currentHeight), section.header, fill=songObj.fontColour, font=songObj.fontTablature)
|
||||
currentHeight += headerHeight
|
||||
# Write each line tablature&lyric data
|
||||
while lineIterator < amountOfLines:
|
||||
#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])
|
||||
tablatureTextWidth, tablatureTextHeight = fontTablature.getsize(section.tablatures[lineIterator])
|
||||
lyricTextWidth, lyricTextHeight = songObj.fontLyrics.getsize(section.lyrics[lineIterator])
|
||||
tablatureTextWidth, tablatureTextHeight = songObj.fontTablature.getsize(section.tablatures[lineIterator])
|
||||
# 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
|
||||
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
|
||||
lineIterator += 1
|
||||
#print("currentheight={}".format(currentHeight))
|
||||
# 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
|
||||
outputLocation = folderLocation + "/" + str(imageNumber) + ".png"
|
||||
a4image.save(outputLocation)
|
||||
|
Loading…
x
Reference in New Issue
Block a user