Compare commits

...

6 Commits
0.1 ... master

Author SHA1 Message Date
2571dcec5b Set a minimum font size to PPI/6 to make sure text stays readable 2022-01-15 02:40:38 +01:00
Marco van Dijk
4ab8f6c836 Replaced prints with logging module 2021-07-27 16:19:08 +02:00
Marco van Dijk
30109a0a7f Option to prefer even amount of pages
Tweaked whitespace allowed to prefer 4 pages big over 2 pages with very small text
2021-07-27 15:34:55 +02:00
Marco van Dijk
568054b986 Variable margins based on page number 2021-07-27 14:48:57 +02:00
Marco van Dijk
9992b7ac36 Added max pages 2021-07-27 14:18:30 +02:00
Marco van Dijk
f26e80b245 Added minimum page config entry
Fixed expected height calculation to either take the whitespace in the input file or a set margin in between sections
Changed default settings slightly
2021-07-23 23:09:05 +02:00
6 changed files with 183 additions and 126 deletions

View File

@ -30,13 +30,14 @@ def initConfig():
# Else load defaults
else:
config['input'] = {'inputFolders': os.getcwd(),
'maxDepth': 2,
'maxDepth': 1,
'readtxt': 1,
'readraw': 1
}
config['options'] = {'exporttoimg': 1,
'exporttotxt': 0,
'exporttoraw': 0
'exporttoraw': 0,
'logLevel': 3
}
config['output'] = {'metafontfamily': 'fonts/CourierPrime-Regular.ttf',
'metaFontWeight': 32,
@ -46,14 +47,17 @@ def initConfig():
'backgroundColour': '255,255,255',
'fontColour': '0,0,0',
'metadataColour': '128,128,128',
'topMargin': 50,
'leftMargin': 50,
'rightMargin': 50,
'tryToShrinkRatio' : 0.25,
'lowestwhitespaceonwidthratioallowed': 0.90,
'highestwhitespaceonwidthratioallowed': 0.40,
'verticalMargin': 50,
'horizontalMargin': 100,
'extraHorizontalMargin': 100,
'tryToShrinkRatio' : 0.4,
'shortestlinewhitespaceratioallowed': 0.95,
'longestlinewhitespaceratioallowed': 0.30,
'keepEmptyLines': 1,
'writeheaderfile': 0
'writeheaderfile': 0,
'minPages': 2,
'maxPages': 4,
'preferEvenPageNumbers': 0
}
# (if CMD arguments: load CMD arguments to override specific settings)
with open('config.ini', 'w') as configfile:

View File

@ -9,13 +9,11 @@
#
# @section notes Notes
#
# @section todo TODO
# - Move helper functions like stripEmptyLines to a separate file for
# - Move read functions to separate input functions (also to support more types of inputs)
import re
import lib.config
from PIL import ImageFont
import logging
A4 = {'width': 210, 'height': 297}
A5 = {'width': 210, 'height': 148}
@ -47,30 +45,30 @@ def readSourceFile(inputFile):
def isTablatureData(inputString):
if not inputString:
return
#print("Checking '{}' for line type".format(inputString))
logging.debug("Checking '{}' for line type".format(inputString))
# Assume tablature line if any character {/, #, (, ), }
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))
logging.debug("'{}' 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, j, n}
lyricSpecificCharacterString = r"abcdefghbxmjn"
for char in inputString:
if char.isalpha():
if not char.lower() in lyricSpecificCharacterString:
#print("'{}' is a LYRIC line, since it contains lyric specific text characters".format(inputString))
logging.debug("'{}' is a lyric line, since it contains lyric specific text characters".format(inputString))
return False
# Assume tablature line if any digit
if any(char.isdigit() for char in inputString):
#print("'{}' is a tablature line, since it contains a number".format(inputString))
logging.debug("'{}' is a tablature line, since it contains a number".format(inputString))
return True
# Assume LYRIC line if any character {.}
lyricSpecialChars = r"."
if any(elem in inputString for elem in lyricSpecialChars):
#print("'{}' is a LYRIC line, since it contains lyric specific special characters".format(inputString))
logging.debug("'{}' is a lyric line, since it contains lyric specific special characters".format(inputString))
return False
# 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))
# logging.warn("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
"""!@brief Class containing Section specific data
@ -100,20 +98,23 @@ class Section:
heightSum = 0
maxWidth = 0
# consider section title
logging.debug("Init size with header '{}'".format(self.header))
headerWidth, headerHeight = fontTablature.getsize(self.header)
heightSum += headerHeight
maxWidth = headerWidth
#print("With header, dimensions of section '{}' start at {}H{}B".format(self.header[:-2], heightSum, maxWidth))
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:
logging.debug("Found line '{}' with a width of {}".format(self.lyrics[lineIterator], lyricTextWidth))
maxWidth = lyricTextWidth
if tablatureTextWidth > maxWidth:
logging.debug("Found line '{}' with a width of {}".format(self.tablatures[lineIterator], tablatureTextWidth))
maxWidth = tablatureTextWidth
lineIterator += 1
logging.debug("Setting section to W:{} H:{}".format(maxWidth, heightSum))
self.expectedWidth = maxWidth
self.expectedHeight = heightSum
@ -134,7 +135,7 @@ class Section:
continue
# Determine lyric or tablature line
currentIsTablature = isTablatureData(line)
#print("Have line {} isTab={}, isLyric={}".format(line, currentIsTablature, not currentIsTablature))
logging.debug("Have line {} isTab={}, isLyric={}".format(line, currentIsTablature, not currentIsTablature))
# Initially just fill in the first line correctly
if isFirstLine:
isFirstLine = False
@ -146,16 +147,16 @@ class Section:
# we need to insert an empty line of the other type
elif currentIsTablature == prevWasTablature:
if currentIsTablature:
#print("Inserting empty Lyric line")
logging.debug("Inserting empty Lyric line")
self.tablatures.append(line)
self.lyrics.append("")
else:
#print("Inserting empty tablature line")
logging.debug("Inserting empty tablature line")
self.lyrics.append(line)
self.tablatures.append("")
# also insert the current line
elif currentIsTablature:
#print("Inserting empty Lyric line")
logging.debug("Inserting empty Lyric line")
self.tablatures.append(line)
else:
self.lyrics.append(line)
@ -163,7 +164,7 @@ class Section:
prevWasTablature = currentIsTablature
# Simple check to see if it probably exported correctly
if abs(len(self.lyrics) - len(self.tablatures)) > 1:
print("Unable to parse section {}, since there is a mismatch between the amount of lyrics ({}) and tablature ({}) lines.".format(self.header, len(self.lyrics), len(self.tablatures)))
logging.error("Unable to parse section {}, since there is a mismatch between the amount of lyrics ({}) and tablature ({}) lines.".format(self.header, len(self.lyrics), len(self.tablatures)))
return
# Add a trailing empty line if necessary
elif len(self.lyrics) > len(self.tablatures):
@ -203,7 +204,9 @@ class Song:
# Flag for succesfully parsed
self.isParsed = False
configObj = lib.config.config['output']
self.topMargin = int(configObj['topMargin'])
self.verticalMargin = int(configObj['verticalMargin'])
self.horizontalMargin = int(configObj['horizontalMargin'])
self.extraHorizontalMargin = int(configObj['extraHorizontalMargin'])
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(','))
@ -214,9 +217,7 @@ class Song:
# With a PPI of 72, a font size of 14-18 is a good starting point (PPI / 4 or 4.5)
# Since font size is then shrunk and grown to fit whitespace we do not need to be as accurate
# PPI of 144 -> fontSize of 32
self.fontSize = int(self.ppi / 4.5)
self.leftMargin = int(configObj['leftMargin'])
self.rightMargin = int(configObj['rightMargin'])
self.fontSize = int(self.ppi / 4)
self.fontLyrics = ImageFont.truetype(configObj['lyricfontfamily'], self.fontSize)
self.fontTablature = ImageFont.truetype(configObj['tablaturefontfamliy'], self.fontSize)
self.fontFamilyLyrics = configObj['lyricfontfamily']
@ -229,14 +230,18 @@ class Song:
self.tryToShrinkRatio = float(configObj['tryToShrinkRatio'])
# Setting this makes sure that the largest section on the page fills at least this percentage of total width
# The higher this is, the more it is allowed to shrink
self.lowestWhitespaceOnWidthRatioAllowed = float(configObj['lowestWhitespaceOnWidthRatioAllowed'])
# Some sections are very small, so the highest whitespace can be very large.
# It is advised to keep this value relatively small
self.hightestWhitespaceOnWidthRatioAllowed = float(configObj['highestwhitespaceonwidthratioallowed'])
self.longestLineWhitespaceRatioAllowed = float(configObj['longestLineWhitespaceRatioAllowed'])
# Some lines are very small, so the highest whitespace can be very large.
# It is advised to keep this ratio high for that reason
self.shortestLineWhitespaceRatioAllowed = float(configObj['shortestLineWhitespaceRatioAllowed'])
# Strip empty lines from input or keep em
self.keepEmptyLines = configObj['keepEmptyLines'] == '1'
# Strip empty lines from input or keep em
self.writeMetadata = configObj['writeheaderfile'] == '1'
# Don't go under this number
self.minPages = int(configObj['minPages'])
self.preferEvenPageNumbers = int(configObj['preferEvenPageNumbers'])
self.maxPages = max(int(configObj['minPages']), int(configObj['maxPages']))
"""!@brief Calculates dimensions of metadata
@ -245,7 +250,7 @@ class Song:
"""
def calculateMetadataDimensions(self):
# metadata starts topMargin removed from top
currentHeight = self.topMargin
currentHeight = self.verticalMargin
maxWidth = 0
for line in self.metadata.split('\n'):
line = line.rstrip()
@ -253,11 +258,12 @@ class Song:
continue
metadataTextWidth, metadataTextHeight = self.fontMetadata.getsize(line)
if metadataTextWidth > maxWidth:
logging.debug("Found line '{}' with a width of {}".format(line, metadataTextWidth))
maxWidth = metadataTextWidth
currentHeight += metadataTextHeight
self.metadataWidth = maxWidth
self.metadataHeight = currentHeight
#print("metadata dimensions are {}h : {}w".format(currentHeight, maxWidth))
logging.debug("metadata dimensions are {}h : {}w".format(currentHeight, maxWidth))
"""!@brief Resizes all sections by a specified amount
Also recalculates all section sizes afterwards
@ -265,7 +271,7 @@ class Song:
@return None
"""
def resizeAllSections(self, mutator):
#print("Resizing font by {} to {}".format(mutator, self.fontSize))
logging.debug("Resizing font by {} to {}".format(mutator, self.fontSize))
self.fontSize += mutator
self.fontLyrics = ImageFont.truetype(self.fontFamilyLyrics, self.fontSize)
self.fontTablature = ImageFont.truetype(self.fontFamilyTablature, self.fontSize)
@ -294,10 +300,10 @@ class Song:
def fitSectionsByWidth(self):
self.prerenderSections()
while not self.checkOverflowX():
#print("Resizing down to prevent overflow on the width of the page")
logging.debug("Resizing down to prevent overflow on the width of the page")
self.resizeAllSections(-1)
while not self.checkOverflowMetadata():
#print("Resizing down to prevent overflow on the width of the page")
logging.debug("Resizing down to prevent metadata overflow on the width of the page")
self.resizeMetadata(-1)
"""!@brief Checks whether we are overflowing on the width of the page
@ -305,8 +311,8 @@ class Song:
"""
def checkOverflowX(self):
for section in self.sections:
if section.expectedWidth > self.imageWidth - self.leftMargin - self.rightMargin:
print("There is an overflow on width: this section has a width of {}, but we have {} ({}-{}-{}) amount of space".format(section.expectedWidth, self.imageWidth - self.leftMargin - self.rightMargin, self.imageWidth, self.leftMargin, self.rightMargin))
if section.expectedWidth > self.imageWidth - self.extraHorizontalMargin - self.horizontalMargin - self.horizontalMargin:
logging.debug("There is an overflow on width: this section has a width of {}, but we have {} ({}-{}-{}*2) amount of space".format(section.expectedWidth, self.imageWidth - self.extraHorizontalMargin - self.horizontalMargin - self.horizontalMargin, self.imageWidth, self.extraHorizontalMargin, self.horizontalMargin))
return False
return True
@ -314,32 +320,37 @@ class Song:
@return True if everything OK, False if overflowing
"""
def checkOverflowMetadata(self):
if self.metadataWidth > self.imageWidth - self.leftMargin - self.rightMargin:
if self.metadataWidth > self.imageWidth - self.extraHorizontalMargin - self.horizontalMargin - self.horizontalMargin:
return False
return True
"""!@brief Checks whether we can increase the font size without creating more pages
"""!@brief Resizes the page to fit reminaing whitespace, and increases the number of pages to stay above the minimum amount of pages
@return None
"""
def increaseWhileSameAmountOfPages(self):
targetPageAmount = len(self.pages)
def increaseToMinPages(self):
targetPageAmount = max(len(self.pages), self.minPages)
if (targetPageAmount % 2) != 0 and self.preferEvenPageNumbers:
targetPageAmount += 1
logging.info("Increasing target page amount to {} to make it an even number".format(targetPageAmount))
originalFontsize = self.fontSize
self.resizeAllSections(1)
logging.debug("Starting font size increase with {} pages and {} font size".format(targetPageAmount, originalFontsize))
self.resizeAllSections(+1)
self.sectionsToPages()
currentPageAmount = len(self.pages)
# Increase fontSize as long as we do not add a page
while currentPageAmount <= targetPageAmount and self.checkOverflowX():
# Increase fontSize as long as we stay under the target max pages
while ((currentPageAmount <= targetPageAmount) and self.checkOverflowX()):
self.resizeAllSections(+1)
self.sectionsToPages()
currentPageAmount = len(self.pages)
logging.debug("Current page amount is {} with font size {}".format(currentPageAmount, self.fontSize))
# Now undo latest increase to go back to target page amount
self.resizeAllSections(-1)
self.sectionsToPages()
currentPageAmount = len(self.pages)
if targetPageAmount != currentPageAmount:
print("Oops! While resizing up we changed the amount of pages from {} to {}".format(targetPageAmount, currentPageAmount))
logging.warning("Oops! While resizing up we changed the amount of pages from {} to {}".format(targetPageAmount, currentPageAmount))
if self.fontSize != originalFontsize:
print("Managed to change the font size from {} to {}".format(originalFontsize, self.fontSize))
logging.debug("Managed to change the font size from {} to {}".format(originalFontsize, self.fontSize))
"""!@brief Tries to fill in the whitespace on the current render
@ -350,6 +361,8 @@ class Song:
def canFillWhitespace(self):
amountOfPages = len(self.pages)
currentPageIt = 0
totalHorizontalMargin = self.extraHorizontalMargin + self.horizontalMargin + self.horizontalMargin
imageWidthWithoutMargins = self.imageWidth - totalHorizontalMargin
if not amountOfPages:
return False
# Stop resizing if we are creating too much widespace on the width
@ -357,19 +370,25 @@ class Song:
biggestWhitespace = -1
for page in self.pages:
for section in page.sections:
whitespaceOnWidth = self.imageWidth - self.leftMargin - self.rightMargin - section.expectedWidth
# We have 2* horizontal whitespace
whitespaceOnWidth = self.imageWidth - totalHorizontalMargin - section.expectedWidth
if whitespaceOnWidth < smallestWhitespace:
smallestWhitespace = whitespaceOnWidth
if whitespaceOnWidth > biggestWhitespace:
biggestWhitespace = whitespaceOnWidth
# Sections vary in width, some are very small to begin with
# Since (almost empty) lines will result in large whitespace sizes, we are less strict on checking that
if biggestWhitespace / self.imageWidth > self.lowestWhitespaceOnWidthRatioAllowed:
print("Stopping resizing down, since the smallest section has {}% whitespace on the width of the image".format((biggestWhitespace / self.imageWidth )* 100))
logging.debug("The shortest line has {} whitespace, the largest line {}. The image is {} wide with {} total horizontal margins (={}), resulting in a {} min ratio and {} max ratio, with a min limit of {} and a max limit of {}".format(biggestWhitespace, smallestWhitespace, self.imageWidth, totalHorizontalMargin, imageWidthWithoutMargins, biggestWhitespace / imageWidthWithoutMargins, smallestWhitespace / imageWidthWithoutMargins, self.shortestLineWhitespaceRatioAllowed, self.longestLineWhitespaceRatioAllowed))
# Make sure small lines fill the page enough
if biggestWhitespace / imageWidthWithoutMargins > self.shortestLineWhitespaceRatioAllowed:
logging.debug("Stopping resizing down, since the smallest section has {}% whitespace on the width of the image".format((biggestWhitespace / imageWidthWithoutMargins )* 100))
return False
# But the largest section on the page should be able to fit at least half of the available page
if smallestWhitespace / self.imageWidth > self.hightestWhitespaceOnWidthRatioAllowed:
print("Stopping resizing down, since we largest section has {}% whitespace on the width of the image".format((smallestWhitespace / self.imageWidth )* 100))
# Make sure the longest lines fill the page enough
if smallestWhitespace / imageWidthWithoutMargins > self.longestLineWhitespaceRatioAllowed:
logging.debug("Stopping resizing down, since we largest section has {}% whitespace on the width of the image".format((smallestWhitespace / imageWidthWithoutMargins )* 100))
return False
# Make sure the longest lines fill the page enough
if self.fontSize < int(self.ppi / 6):
logging.debug("Stopping resizing down, since the font size is becoming too small at {}".format(self.fontSize))
return False
# get first section on next page, if we have a next page to begin with
while currentPageIt < amountOfPages - 1:
@ -379,8 +398,8 @@ class Song:
whitespace = self.imageHeight - curPage.totalHeight
amountWeAreShort = nextFirstSection.expectedHeight - whitespace
shortInPercentages = amountWeAreShort / self.imageHeight
#print("Whitespace {} vs next section height {}".format(whitespace, nextFirstSection.expectedHeight))
#print("We are {} short to fit the next image (total image height {} => {}% of total height)".format(amountWeAreShort, self.imageHeight, shortInPercentages*100))
logging.debug("Whitespace {} vs next section height {}".format(whitespace, nextFirstSection.expectedHeight))
logging.debug("We are {} short to fit the next image (total image height {} => {}% of total height)".format(amountWeAreShort, self.imageHeight, shortInPercentages*100))
# Since we also resize based on minimum required whitespaces, we can be a bit more aggressive with this
if shortInPercentages < self.tryToShrinkRatio:
return True
@ -392,22 +411,28 @@ class Song:
@return None
"""
def sectionsToPages(self):
# If we are keeping whitespace, don't count the whitespace in between sections
sectionWhitespace = self.verticalMargin
if self.keepEmptyLines:
sectionWhitespace = 0
self.prerenderSections()
self.pages = []
# First page contains metadata
currentHeight = self.topMargin
currentHeight = self.verticalMargin
currentHeight += self.metadataHeight
currentHeight += self.topMargin
currentHeight += sectionWhitespace
curPage = Page()
# Now fit all sections
for section in self.sections:
if (section.expectedHeight == -1 or section.expectedWidth == -1):
print("Warning: this file was not processed correctly. The expected dimensions are not set")
logging.critical("Warning: this file was not processed correctly. The expected dimensions are not set")
self.pages = []
return
# See if the section would fit on the current page - if it does not, we have a filled page
if currentHeight + section.expectedHeight > self.imageHeight:
curPage.totalHeight = currentHeight
self.pages.append(curPage)
currentHeight = self.topMargin
currentHeight = sectionWhitespace
curPage = Page()
# Add setion header size and size of lines of data
headerWidth, headerHeight = self.fontTablature.getsize(section.header)
@ -415,7 +440,7 @@ class Song:
currentHeight += section.expectedHeight
curPage.sections.append(section)
# Margin between each section
currentHeight += self.topMargin
currentHeight += sectionWhitespace
# No more sections left, so the current buffered image is ready to be written to file
curPage.totalHeight = currentHeight
self.pages.append(curPage)
@ -431,23 +456,23 @@ class Song:
# While not EOF: build sections until new section found.
delimiterIndex = parseData.find("[")
if delimiterIndex == -1:
print("Cannot parse input file, since it is not delimited by '[<sectionName>]' entries")
logging.error("Cannot parse input file, since it is not delimited by '[<sectionName>]' entries")
return
# Start with metadata
self.metadata = parseData[:delimiterIndex]
print("Set '{}' as metadata".format(self.metadata))
logging.debug("Set '{}' as metadata".format(self.metadata))
parseData = parseData[delimiterIndex:]
# We are now at the start of the first section, at the '[' character
lines = parseData.splitlines(True)
if not len(lines):
return
#print("We found {} lines of data".format(len(lines)))
logging.debug("We found {} lines of data".format(len(lines)))
# Init first section by popping the delimiter
thisSection = Section()
thisSection.header = lines.pop(0)
# First line is always tab->lyric
isTabLine = True
print("First header is '{}'".format(thisSection.header))
logging.debug("First header is '{}'".format(thisSection.header))
for line in lines:
# If it is a [header], it is a new section
if line[0] == '[':
@ -456,20 +481,20 @@ class Song:
if thisSection.isParsed:
self.sections.append(thisSection)
else:
print("Aborting parse due to section not being parseable.")
logging.error("Aborting parse due to section not being parseable.")
return
# Reset, new section
thisSection = Section()
thisSection.header = line
#print("Header is '{}'".format(thisSection.header))
logging.debug("Header is '{}'".format(thisSection.header))
isTabLine = True
# Else is has lines in order tabline->lyricline->repeat
elif isTabLine:
#print("Adding Tabline is '{}'".format(line))
logging.debug("Adding Tabline is '{}'".format(line))
thisSection.tablatures.append(line)
isTabLine = False
else:
#print("Adding Lyricline is '{}'".format(line))
logging.debug("Adding Lyricline is '{}'".format(line))
thisSection.lyrics.append(line)
isTabLine = True
# Add final section data
@ -477,7 +502,7 @@ class Song:
if thisSection.isParsed:
self.sections.append(thisSection)
else:
print("Aborting parse due to section not being parseable.")
logging.error("Aborting parse due to section not being parseable.")
return
self.isParsed = True
@ -489,15 +514,15 @@ class Song:
self.rawData = readSourceFile(self.inputFile)
# Clean up input
parseData = stripEmptyLines(self.rawData, self.keepEmptyLines)
#print("Clean data='{}'\n".format(parseData))
logging.debug("Clean data='{}'\n".format(parseData))
# While not EOF: build sections until new section found.
delimiterIndex = parseData.find("[")
if delimiterIndex == -1:
print("Cannot parse input file, since it is not delimited by '[<sectionName>]' entries")
logging.error("Cannot parse input file, since it is not delimited by '[<sectionName>]' entries")
return
# Start with metadata
self.metadata = parseData[:delimiterIndex]
#print("Set '{}' as metadata".format(self.metadata))
logging.debug("Set '{}' as metadata".format(self.metadata))
parseData = parseData[delimiterIndex:]
# We are now at the start of the first section, at the '[' character
while parseData:
@ -506,7 +531,7 @@ class Song:
# Get header on the first line
delimiterIndex = parseData.find("]\r\n")
if delimiterIndex == -1:
print("Cannot parse input file, delimiter did not match '[<sectionName>]'")
logging.error("Cannot parse input file, delimiter did not match '[<sectionName>]'")
return
# Skip the ']\r\n' characters
thisSection.header = parseData[:delimiterIndex+3]
@ -521,14 +546,14 @@ class Song:
else:
# Set thisSection's data and remove it from the buffer
thisSection.rawData = parseData[:delimiterIndex]
#print("set rawData of '{}' to this section".format(thisSection.rawData))
logging.debug("set rawData of '{}' to this section".format(thisSection.rawData))
parseData = parseData[delimiterIndex:]
# Finally parse section data
thisSection.initSections()
if thisSection.isParsed:
self.sections.append(thisSection)
else:
print("Aborting parse due to section not being parseable.")
logging.error("Aborting parse due to section not being parseable.")
return
self.isParsed = True

View File

@ -17,6 +17,7 @@
import lib.dataStructures
import lib.config
import os
import logging
"""!@brief Creates and inits a Song object
This function creates a new Song object and sets the internal variables correctly
@ -32,7 +33,7 @@ def initSong(filePath):
thisSong.outputLocation = filePath[:filePath.rfind('.')]
# title is just the name of the .txt file
thisSong.title = thisSong.outputLocation[filePath.rfind('/')+1:]
#print("Finished init for input file '{}'.\nBase output folder is '{}'\nSong title is '{}'\n".format(thisSong.inputFile, thisSong.outputLocation, thisSong.title))
logging.debug("Finished init for input file '{}'.\nBase output folder is '{}'\nSong title is '{}'\n".format(thisSong.inputFile, thisSong.outputLocation, thisSong.title))
return thisSong
"""!@brief Creates a list of files found in a directory and its subdirectories
@ -43,7 +44,7 @@ def initSong(filePath):
"""
def walkDirectory(root, depth):
pathList = []
#print("Walking directory '{}'".format(root))
logging.debug("Walking directory '{}'".format(root))
def do_scan(start_dir,output,depth=2):
for f in os.listdir(start_dir):
ff = os.path.join(start_dir,f)
@ -73,10 +74,10 @@ def getSongObjects():
for inputFolder in configObj['inputfolders'].split(','):
for filePath in walkDirectory(inputFolder, recursionDepth):
if ((filePath[filePath.find('.'):] == ".txt" ) and configObj['readtxt'] == '1') or ((filePath[filePath.find('.'):] == ".rawtxt" ) and configObj['readraw'] == '1'):
#print("Found supported file '{}'".format(filePath))
logging.debug("Found supported file '{}'".format(filePath))
txtFileLocations.append(filePath)
#else:
#print("Skipping file '{}' for it is not a supported file".format(filePath))
else:
logging.debug("Skipping file '{}' for it is not a supported file".format(filePath))
# create list of Song objects
while(txtFileLocations):
filePath = txtFileLocations.pop()

53
main.py
View File

@ -14,11 +14,7 @@
# as best as it can, shrinking or growing sections to fit the remaining space
#
# @section notes Notes
# - Splitting raw text into lyric and tablature info is very basic at the moment.
# We need a better way to classify & split the various channels (raw tab, lyrics, chords, more?) that can be expected in tablature
#
# @section todo TODO
# - Various prints should be printed at specific log levels, to easily switch between debug, info or warnings only
# -
import lib.chordFinder
import lib.dataStructures
@ -27,6 +23,7 @@ import lib.transpose
import lib.config
import output2img
import output2txt
import logging
def main():
# Init config file
@ -39,34 +36,53 @@ def main():
exportToTxt = configObj['exporttotxt'] == '1'
exportToRaw = configObj['exporttoraw'] == '1'
logLevel = int(configObj['loglevel'])
if logLevel == 1:
logLevel = logging.CRITICAL
elif logLevel == 2:
logLevel = logging.ERROR
elif logLevel == 3:
logLevel = logging.WARNING
elif logLevel == 4:
logLevel = logging.INFO
else:
logLevel = logging.DEBUG
logging.basicConfig()
logging.root.setLevel(logLevel)
logging.debug('Starting')
for song in songs:
logging.info("Found song '{}' at '{}'".format(song.title, song.inputFile))
# Convert all songs into sections
for song in songs:
print("Start parsing of file '{}'...".format(song.inputFile))
logging.info("Start parsing song '{}'...".format(song.title))
# Initialise internal data structures
print("song file extension {}".format(song.fileExtension))
logging.debug("song file extension {}".format(song.fileExtension))
if song.fileExtension == 'txt':
song.initSections()
elif song.fileExtension == 'rawtxt':
song.initPreprocessed()
else:
print("File extension '{}' not supported. Skipping...".format(song.fileExtension))
logging.warning("File extension '{}' not supported. Skipping...".format(song.fileExtension))
continue
# 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...")
logging.error("Song was not initialized correctly. Skipping...")
continue
if exportToTxt:
# Create subdirectory where we will output our images
targetDirectory = song.outputLocation + "-txt"
print("Successfully parsed file. Writing output to '{}'\n".format(targetDirectory))
logging.info("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))
logging.info("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:
@ -75,18 +91,23 @@ def main():
# 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")
while len(song.pages) > song.maxPages:
logging.debug("Resizing down since we have {} pages and want {} pages".format(len(song.pages), song.maxPages))
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()
while song.canFillWhitespace():
logging.debug("Resizing down to fill remaining vertical whitespace")
song.resizeAllSections(-1)
song.sectionsToPages()
# Optimalisation: increase font size to fit target page amount
song.increaseToMinPages()
# 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))
logging.info("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()
logging.debug('Finished')

View File

@ -11,11 +11,10 @@
# @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
from PIL import Image, ImageDraw
import logging
"""!@brief Exports the song object to images
This function renders the metadata and sections
@ -30,17 +29,23 @@ def outputToImage(folderLocation, 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")
logging.info("Directory {} Created ".format(folderLocation))
else:
logging.debug("Directory {} already exists".format(folderLocation))
# Init image info
imageNumber = 1
currentHeight = songObj.topMargin
currentHeight = songObj.verticalMargin
# New Image
a4image = Image.new('RGB',(songObj.imageWidth, songObj.imageHeight),(songObj.backgroundColour))
draw = ImageDraw.Draw(a4image)
# Add extra whitespace on the left if this is an even page
# The whitespace on the right for uneven pages is handled elsewhere, by limiting the maximum horizontal size
horizontalMargin = songObj.horizontalMargin
if (imageNumber % 2) == 0:
horizontalMargin += songObj.extraHorizontalMargin
# Write metadata
for line in songObj.metadata.split('\n'):
@ -48,51 +53,56 @@ def outputToImage(folderLocation, songObj):
line = line.rstrip()
if not line and not songObj.keepEmptyLines:
continue
#print("meta line '{}'".format(line))
logging.debug("Metadata '{}'".format(line))
metadataTextWidth, metadataTextHeight = songObj.fontMetadata.getsize(line)
draw.text((songObj.leftMargin,currentHeight), line, fill=songObj.metadataColour, font=songObj.fontMetadata)
draw.text((horizontalMargin, currentHeight), line, fill=songObj.metadataColour, font=songObj.fontMetadata)
currentHeight += metadataTextHeight
# Draw all pages
for page in songObj.pages:
# Margin between metadata and the first section / section and top of page
currentHeight += songObj.topMargin
currentHeight += songObj.verticalMargin
for section in page.sections:
# Reset section specific variables
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))
logging.critical("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...")
logging.critical("Cannot write this section to file, since it was not processed correctly. The expected dimensions are not set. Aborting...")
return
# write section title
headerWidth, headerHeight = songObj.fontTablature.getsize(section.header)
draw.text((songObj.leftMargin,currentHeight), section.header, fill=songObj.fontColour, font=songObj.fontTablature)
draw.text((horizontalMargin ,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]))
logging.debug("Printing tablatures line {} and lyrics line {}".format(section.tablatures[lineIterator], section.lyrics[lineIterator]))
# Get tablatures&lyric line
lyricTextWidth, lyricTextHeight = songObj.fontLyrics.getsize(section.lyrics[lineIterator])
tablatureTextWidth, tablatureTextHeight = songObj.fontTablature.getsize(section.tablatures[lineIterator])
# add to image file
draw.text((songObj.leftMargin,currentHeight), section.tablatures[lineIterator], fill=songObj.fontColour, font=songObj.fontTablature)
draw.text((horizontalMargin ,currentHeight), section.tablatures[lineIterator], fill=songObj.fontColour, font=songObj.fontTablature)
currentHeight += tablatureTextHeight
draw.text((songObj.leftMargin,currentHeight), section.lyrics[lineIterator], fill=songObj.fontColour, font=songObj.fontLyrics)
draw.text((horizontalMargin ,currentHeight), section.lyrics[lineIterator], fill=songObj.fontColour, font=songObj.fontLyrics)
currentHeight += lyricTextHeight
lineIterator += 1
#print("currentheight={}".format(currentHeight))
logging.debug("currentheight={}".format(currentHeight))
# If we stripped al whitespace, we need to add whitespace between sections
if not songObj.keepEmptyLines:
currentHeight += songObj.topMargin
currentHeight += songObj.verticalMargin
# Got all sections in the page, so write it
outputLocation = folderLocation + "/" + songObj.title + '-' + str(imageNumber) + ".png"
a4image.save(outputLocation)
a4image = Image.new('RGB',(songObj.imageWidth, songObj.imageHeight),(songObj.backgroundColour))
draw = ImageDraw.Draw(a4image)
currentHeight = songObj.topMargin
currentHeight = songObj.verticalMargin
imageNumber += 1
# Add extra whitespace on the left if this is an even page
# The whitespace on the right for uneven pages is handled elsewhere, by limiting the maximum horizontal size
horizontalMargin = songObj.horizontalMargin
if (imageNumber % 2) == 0:
horizontalMargin += songObj.extraHorizontalMargin

View File

@ -8,13 +8,9 @@
# 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
import logging
"""!@brief Exports the song object to a txt file
Perfect to use as source file for any program which requires
@ -34,9 +30,9 @@ 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")
logging.info("Directory {} Created ".format(folderLocation))
else:
logging.debug("Directory {} already exists".format(folderLocation))
output = ""
emptyLines = []
@ -51,7 +47,7 @@ def outputToTxt(folderLocation, printRaw, songObj):
# remove any unwanted characters from metadata
if not songObj.keepEmptyLines and not line:
continue
#print("meta line '{}'".format(line))
logging.debug("Metadata '{}'".format(line))
output += line
metadataLines.append(lineCounter)
lineCounter += 1
@ -67,7 +63,7 @@ def outputToTxt(folderLocation, printRaw, songObj):
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))
logging.critical("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'