mirror of
https://github.com/stronk-dev/Guitar-Sheet-Parser.git
synced 2025-07-05 00:25:08 +02:00
184 lines
6.3 KiB
Python
184 lines
6.3 KiB
Python
# !/usr/bin/python
|
|
# This file hosts the classes for storing song data
|
|
import re
|
|
|
|
# TODO: move to separate file with helper functions like this
|
|
def stripEmptyLines(inputString):
|
|
nonEmptyLines = ""
|
|
lines = inputString.split("\n")
|
|
for line in lines:
|
|
if line.strip() != "":
|
|
nonEmptyLines += line + "\r\n"
|
|
return nonEmptyLines
|
|
# read .txt input TODO: move to separate input functions if we want to support multiple types of inputs some day, like web or PDF
|
|
def readSourceFile(inputFile):
|
|
with open(inputFile, 'r') as file:
|
|
return file.read()
|
|
|
|
def isChordType(inputString):
|
|
if not inputString:
|
|
return
|
|
#print("Checking '{}' for line type".format(inputString))
|
|
# Assume CHORD line if any NUMBER character
|
|
if any(char.isdigit() for char in inputString):
|
|
#print("'{}' is a CHORD line, since it contains a number".format(inputString))
|
|
return True
|
|
# Assume CHORD 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))
|
|
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"
|
|
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))
|
|
return False
|
|
# 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))
|
|
return False
|
|
# Else warn and assume chord line
|
|
#print("Unable to identify if '{}' is a lyric or chord line. Assuming it is a chord line. Please improve the isChordType function".format(inputString))
|
|
return True
|
|
|
|
|
|
class Section:
|
|
def __init__(self):
|
|
# List of lines of lyrics strings
|
|
self.lyrics = []
|
|
# List of lines of chord strings
|
|
self.chords = []
|
|
# section type string
|
|
self.header = ""
|
|
# string of chord and lyric data
|
|
self.rawData = ""
|
|
# Flag for succesfully parsed
|
|
self.isParsed = False
|
|
|
|
# Parses self.rawData into lyrics and chord 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
|
|
lines = self.rawData.split('\r\n')
|
|
for line in lines:
|
|
# Determine lyric or chord line
|
|
currentIsChord = isChordType(line)
|
|
# Initially just fill in the first line correctly
|
|
if isFirstLine:
|
|
isFirstLine = False
|
|
if currentIsChord:
|
|
self.chords.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:
|
|
#print("Inserting empty Lyric line")
|
|
self.chords.append(line)
|
|
self.lyrics.append("")
|
|
else:
|
|
#print("Inserting empty Chord line")
|
|
self.lyrics.append(line)
|
|
self.chords.append("")
|
|
# also insert the current line
|
|
elif currentIsChord:
|
|
#print("Inserting empty Lyric line")
|
|
self.chords.append(line)
|
|
else:
|
|
self.lyrics.append(line)
|
|
|
|
prevWasChord = currentIsChord
|
|
# Simple check to see if it worked
|
|
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.")
|
|
return
|
|
# Add a final empty line if necessary
|
|
elif len(self.lyrics) > len(self.chords):
|
|
self.chords.append("")
|
|
elif len(self.lyrics) < len(self.chords):
|
|
self.lyrics.append("")
|
|
self.isParsed = True
|
|
|
|
|
|
class Song:
|
|
def __init__(self):
|
|
# Src file
|
|
self.inputFile = ""
|
|
# Path to folder
|
|
self.outputLocation = ""
|
|
# Title - based on input file
|
|
self.title = ""
|
|
# List of Section objects
|
|
self.sections = []
|
|
# Meta info: the text before the first section
|
|
self.metadata = ""
|
|
# String of entire input
|
|
self.rawData = ""
|
|
# Flag for succesfully parsed
|
|
self.isParsed = False
|
|
|
|
# Parses self.rawData into Section objects and metadata
|
|
def parseMe(self):
|
|
# Fill raw data
|
|
self.rawData = readSourceFile(self.inputFile)
|
|
# Clean up input
|
|
parseData = stripEmptyLines(self.rawData)
|
|
#print("Clean data='{}'\n".format(parseData))
|
|
# While !EOF: build sections (untill []).
|
|
delimiterIndex = parseData.find("[")
|
|
if delimiterIndex == -1:
|
|
print("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))
|
|
parseData = parseData[delimiterIndex:]
|
|
# We are now at the start of the first section, at the '[' character
|
|
while parseData:
|
|
thisSection = Section()
|
|
# Get first line
|
|
delimiterIndex = parseData.find("]\r\n")
|
|
if delimiterIndex == -1:
|
|
print("Cannot parse input file, delimitor did not match '[<sectionName>]'")
|
|
return
|
|
# Set header to first line
|
|
thisSection.header = parseData[:delimiterIndex+3]
|
|
parseData = parseData[delimiterIndex+3:]
|
|
# Find next section
|
|
delimiterIndex = parseData.find("[")
|
|
# If EOF, current buffer is final section
|
|
if delimiterIndex == -1:
|
|
# Set current section data to remaining buffer
|
|
thisSection.rawData = parseData
|
|
parseData = ""
|
|
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))
|
|
parseData = parseData[delimiterIndex:]
|
|
# Finally parse section data
|
|
thisSection.parseMe()
|
|
if thisSection.isParsed:
|
|
self.sections.append(thisSection)
|
|
else:
|
|
print("Aborting parse due to section not being parseable.")
|
|
return
|
|
self.isParsed = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|