Added support for writing to txt and rawtxt (which is quick to parse as input)

Added config options for choosing which extensions to read and which to write to
This commit is contained in:
Marco van Dijk 2021-07-09 20:31:18 +02:00
parent 4eef1ff044
commit 8d03f6d665
6 changed files with 164 additions and 27 deletions

View File

@ -30,8 +30,13 @@ def initConfig():
# Else load defaults # Else load defaults
else: else:
config['input'] = {'inputFolders': os.getcwd(), config['input'] = {'inputFolders': os.getcwd(),
'supportedExtensions': ".txt", 'maxDepth': 2,
'maxDepth': 3 'readtxt': 1,
'readraw': 1
}
config['options'] = {'exporttoimg': 1,
'exporttotxt': 0,
'exporttoraw': 0
} }
config['output'] = {'metafontfamily': 'fonts/CourierPrime-Regular.ttf', config['output'] = {'metafontfamily': 'fonts/CourierPrime-Regular.ttf',
'metaFontWeight': 16, 'metaFontWeight': 16,

View File

@ -184,6 +184,7 @@ class Song:
self.inputFile = "" self.inputFile = ""
# Path to folder # Path to folder
self.outputLocation = "" self.outputLocation = ""
self.fileExtension = ""
# Title - based on input file # Title - based on input file
self.title = "" self.title = ""
# List of Section objects # List of Section objects
@ -216,7 +217,9 @@ class Song:
self.rightMargin = int(configObj['rightMargin']) self.rightMargin = int(configObj['rightMargin'])
self.fontLyrics = ImageFont.truetype(configObj['lyricfontfamily'], self.fontSize) self.fontLyrics = ImageFont.truetype(configObj['lyricfontfamily'], self.fontSize)
self.fontTablature = ImageFont.truetype(configObj['tablaturefontfamliy'], self.fontSize) self.fontTablature = ImageFont.truetype(configObj['tablaturefontfamliy'], self.fontSize)
self.configObj = configObj self.fontFamilyLyrics = configObj['lyricfontfamily']
self.fontFamilyTablature = configObj['tablaturefontfamliy']
"""!@brief Calculates dimensions of metadata """!@brief Calculates dimensions of metadata
@ -247,8 +250,8 @@ class Song:
def resizeAllSections(self, mutator): def resizeAllSections(self, mutator):
#print("Resizing font by {} to {}".format(mutator, self.fontSize)) #print("Resizing font by {} to {}".format(mutator, self.fontSize))
self.fontSize += mutator self.fontSize += mutator
self.fontLyrics = ImageFont.truetype(self.configObj['lyricfontfamily'], self.fontSize) self.fontLyrics = ImageFont.truetype(self.fontFamilyLyrics, self.fontSize)
self.fontTablature = ImageFont.truetype(self.configObj['tablaturefontfamliy'], self.fontSize) self.fontTablature = ImageFont.truetype(self.fontFamilyTablature, self.fontSize)
self.prerenderSections() self.prerenderSections()
"""!@brief Calculates the expected dimensions of all sections """!@brief Calculates the expected dimensions of all sections
@ -379,6 +382,13 @@ class Song:
curPage.totalHeight = currentHeight curPage.totalHeight = currentHeight
self.pages.append(curPage) self.pages.append(curPage)
"""!@brief Parses self.rawData into Section objects and metadata
Assumes the raw data is preprocessed, so it parses it using set rules instead of guessing line attributes
@return None
"""
def initPreprocessed(self):
pass
"""!@brief Parses self.rawData into Section objects and metadata """!@brief Parses self.rawData into Section objects and metadata
@return None @return None
""" """
@ -404,7 +414,7 @@ class Song:
# Get header on the first line # Get header on the first line
delimiterIndex = parseData.find("]\r\n") delimiterIndex = parseData.find("]\r\n")
if delimiterIndex == -1: if delimiterIndex == -1:
print("Cannot parse input file, delimitor did not match '[<sectionName>]'") print("Cannot parse input file, delimiter did not match '[<sectionName>]'")
return return
# Skip the ']\r\n' characters # Skip the ']\r\n' characters
thisSection.header = parseData[:delimiterIndex+3] thisSection.header = parseData[:delimiterIndex+3]

View File

@ -27,6 +27,7 @@ import os
def initSong(filePath): def initSong(filePath):
thisSong = lib.dataStructures.Song() thisSong = lib.dataStructures.Song()
thisSong.inputFile = filePath thisSong.inputFile = filePath
thisSong.fileExtension = filePath[filePath.rfind('.')+1:]
# set base folder name - depending on selected outputs the output folder name changes # set base folder name - depending on selected outputs the output folder name changes
thisSong.outputLocation = filePath[:filePath.rfind('.')] thisSong.outputLocation = filePath[:filePath.rfind('.')]
# title is just the name of the .txt file # title is just the name of the .txt file
@ -71,11 +72,11 @@ def getSongObjects():
# get all files we can find, then filter on supported extensions # get all files we can find, then filter on supported extensions
for inputFolder in configObj['inputfolders'].split(','): for inputFolder in configObj['inputfolders'].split(','):
for filePath in walkDirectory(inputFolder, recursionDepth): for filePath in walkDirectory(inputFolder, recursionDepth):
if(filePath[filePath.rfind('.'):] in configObj['supportedextensions']): if ((filePath[filePath.find('.'):] == ".txt" ) and configObj['readtxt']) or ((filePath[filePath.find('.'):] == ".rawtxt" ) and configObj['readraw']):
#print("Found .txt file '{}'".format(filePath)) #print("Found supported file '{}'".format(filePath))
txtFileLocations.append(filePath) txtFileLocations.append(filePath)
#else: #else:
#print("Skipping file '{}' for it is not a .txt file".format(filePath)) #print("Skipping file '{}' for it is not a supported file".format(filePath))
# create list of Song objects # create list of Song objects
while(txtFileLocations): while(txtFileLocations):
filePath = txtFileLocations.pop() filePath = txtFileLocations.pop()

26
main.py
View File

@ -26,20 +26,46 @@ import lib.initSongs
import lib.transpose import lib.transpose
import lib.config import lib.config
import output2img import output2img
import output2txt
def main(): def main():
# Init config file # Init config file
lib.config.initConfig() lib.config.initConfig()
# Init Song objects for all songs with compatible inputs # Init Song objects for all songs with compatible inputs
songs = lib.initSongs.getSongObjects() songs = lib.initSongs.getSongObjects()
# Get what programs we are going to run
configObj = lib.config.config['options']
exportToImg = configObj['exporttoimg']
exportToTxt = configObj['exporttotxt']
exportToRaw = configObj['exporttoraw']
# Convert all songs into sections # Convert all songs into sections
for song in songs: for song in songs:
print("Start parsing of file '{}'...".format(song.inputFile)) print("Start parsing of file '{}'...".format(song.inputFile))
# Initialise internal data structures # Initialise internal data structures
if song.fileExtension == 'txt':
song.initSections() song.initSections()
elif song.fileExtension == 'raw':
pass#song.initPreprocessed()
# If input is .raw output. If output to raw is set, overwrite itself
# ready quickly using rules
if not song.isParsed: if not song.isParsed:
print("Song was not initialized correctly. Skipping...") print("Song was not initialized correctly. Skipping...")
continue 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))
# 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))
# Write out metadata and sections, as many as can fit on one page
output2txt.outputToTxt(targetDirectory, True, song)
if exportToImg:
# Fit all sections on each page, resizes down if it does not fit on width # Fit all sections on each page, resizes down if it does not fit on width
song.fitSectionsByWidth() song.fitSectionsByWidth()
# Prerender: calculate Pages, and move sections into Pages # Prerender: calculate Pages, and move sections into Pages

View File

@ -86,7 +86,7 @@ def outputToImage(folderLocation, songObj):
# Margin between each section # Margin between each section
currentHeight += songObj.topMargin currentHeight += songObj.topMargin
# Got all sections in the page, so write it # Got all sections in the page, so write it
outputLocation = folderLocation + "/" + str(imageNumber) + ".png" outputLocation = folderLocation + "/" + songObj.title + '-' + str(imageNumber) + ".png"
a4image.save(outputLocation) a4image.save(outputLocation)
a4image = Image.new('RGB',(songObj.imageWidth, songObj.imageHeight),(songObj.backgroundColour)) a4image = Image.new('RGB',(songObj.imageWidth, songObj.imageHeight),(songObj.backgroundColour))
draw = ImageDraw.Draw(a4image) draw = ImageDraw.Draw(a4image)

95
output2txt.py Normal file
View File

@ -0,0 +1,95 @@
#!/usr/bin/env python3
##
# @file output2img.py
#
# @brief This program converts the internal data structure to an image file
#
# @section description Description
# 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
"""!@brief Exports the song object to a txt file
Perfect to use as source file for any program which requires
tabs as input, due to the predictable layout of
metadata
[section title]
<non-lyric line>
<lyric line>
...
@param folderLocation path to where we want the text file
@param printRaw if set, prints empty lines as well and saves as .raw
if false, will print in a more readable format
@param songObj lib.dataStructures.Song object
@return None
"""
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")
output = ""
# Write metadata
for line in songObj.metadata.split('\n'):
# remove any unwanted characters from metadata
line = line.rstrip()
if not line:
continue
#print("meta line '{}'".format(line))
output += line + '\r\n'
# If exporting raw, do not include the whitespace between metadata and sections
if not printRaw:
output += '\r\n'
# Draw all pages
for section in songObj.sections:
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))
return
# write section title
output += section.header.rstrip() + '\r\n'
# Write each line tablature&lyric data
while lineIterator < amountOfLines:
tabline = section.tablatures[lineIterator].rstrip()
lyricline = section.lyrics[lineIterator].rstrip()
if printRaw or len(tabline):
output += tabline + '\r\n'
if printRaw or len(lyricline):
output += lyricline + '\r\n'
lineIterator += 1
# If exporting raw, do not include the whitespace between sections
if not printRaw:
output += '\r\n'
# Finished, so print some trailing endlines
outputLocation = ""
if not printRaw:
output += '\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n'
outputLocation = folderLocation + "/" + songObj.title + ".txt"
else:
outputLocation = folderLocation + "/" + songObj.title + ".rawtxt"
with open(outputLocation, "w") as fileOut:
fileOut.write(output)