Support additional markers in the chordpro file, and move to div-based HTML
This commit is contained in:
parent
454c29152d
commit
d3f68f109b
@ -1,7 +1,7 @@
|
|||||||
from hyphen import Hyphenator
|
from hyphen import Hyphenator
|
||||||
|
|
||||||
from chordpro.constants import DIRECTIVE, CHORD_WORD, CHORUS_MARKER, KNOWN_DIRECTIVES, \
|
from chordpro.constants import BRIDGE_MARKER, CHORD_WORD, CHORUS_MARKER, DIRECTIVE, END_OF, \
|
||||||
START_OF, END_OF
|
KNOWN_DIRECTIVES, START_OF, VERSE_MARKER
|
||||||
|
|
||||||
HYPHEN_CACHE = {}
|
HYPHEN_CACHE = {}
|
||||||
SYLLABLE_EXCEPTIONS = {
|
SYLLABLE_EXCEPTIONS = {
|
||||||
@ -159,6 +159,18 @@ class Verse(object):
|
|||||||
return match is not None
|
return match is not None
|
||||||
return match.group(1) == type_
|
return match.group(1) == type_
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_verse_marker(line):
|
||||||
|
return line.strip().startswith('{verse')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_verse_from_marker(line):
|
||||||
|
match = VERSE_MARKER.match(line)
|
||||||
|
if not match:
|
||||||
|
return None
|
||||||
|
if len(match.groups()) > 1:
|
||||||
|
return match.group(2)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_chorus_marker(line):
|
def is_chorus_marker(line):
|
||||||
return line.strip().startswith('{chorus')
|
return line.strip().startswith('{chorus')
|
||||||
@ -171,6 +183,18 @@ class Verse(object):
|
|||||||
if len(match.groups()) > 1:
|
if len(match.groups()) > 1:
|
||||||
return match.group(2)
|
return match.group(2)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_bridge_marker(line):
|
||||||
|
return line.strip().startswith('{bridge')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_bridge_from_marker(line):
|
||||||
|
match = BRIDGE_MARKER.match(line)
|
||||||
|
if not match:
|
||||||
|
return None
|
||||||
|
if len(match.groups()) > 1:
|
||||||
|
return match.group(2)
|
||||||
|
|
||||||
|
|
||||||
class Metadata(object):
|
class Metadata(object):
|
||||||
|
|
||||||
@ -214,9 +238,21 @@ class Song(object):
|
|||||||
current_verse = None
|
current_verse = None
|
||||||
elif is_verse:
|
elif is_verse:
|
||||||
current_verse.add_line(line.strip())
|
current_verse.add_line(line.strip())
|
||||||
|
elif Verse.is_verse_marker(line):
|
||||||
|
verse_name = Verse.get_verse_from_marker(line)
|
||||||
|
for verse in self.verses[::-1]:
|
||||||
|
if verse.title == verse_name or verse.type_ == "verse":
|
||||||
|
self.verse_order.append(verse)
|
||||||
|
break
|
||||||
elif Verse.is_chorus_marker(line):
|
elif Verse.is_chorus_marker(line):
|
||||||
chorus_name = Verse.get_chorus_from_marker(line)
|
chorus_name = Verse.get_chorus_from_marker(line)
|
||||||
for verse in self.verses[::-1]:
|
for verse in self.verses[::-1]:
|
||||||
if verse.title == chorus_name or verse.type_ == "chorus":
|
if verse.title == chorus_name or verse.type_ == "chorus":
|
||||||
self.verse_order.append(verse)
|
self.verse_order.append(verse)
|
||||||
break
|
break
|
||||||
|
elif Verse.is_bridge_marker(line):
|
||||||
|
bridge_name = Verse.get_bridge_from_marker(line)
|
||||||
|
for verse in self.verses[::-1]:
|
||||||
|
if verse.title == bridge_name or verse.type_ == "bridge":
|
||||||
|
self.verse_order.append(verse)
|
||||||
|
break
|
||||||
|
@ -35,5 +35,6 @@ START_OF = re.compile(r'\{start_of_(' + '|'.join(KNOWN_VERSE_TYPES) + r')(: *(.*
|
|||||||
END_OF = re.compile(r'\{end_of_(' + '|'.join(KNOWN_VERSE_TYPES) + r')\}')
|
END_OF = re.compile(r'\{end_of_(' + '|'.join(KNOWN_VERSE_TYPES) + r')\}')
|
||||||
CHORUS_MARKER = re.compile(r'\{chorus(: *(.*?))?\}')
|
CHORUS_MARKER = re.compile(r'\{chorus(: *(.*?))?\}')
|
||||||
VERSE_MARKER = re.compile(r'\{verse: *(.*?)\}')
|
VERSE_MARKER = re.compile(r'\{verse: *(.*?)\}')
|
||||||
|
BRIDGE_MARKER = re.compile(r'\{bridge: *(.*?)\}')
|
||||||
CHORD_WORD = re.compile(r'(.*?)\[(.*?)\]')
|
CHORD_WORD = re.compile(r'(.*?)\[(.*?)\]')
|
||||||
CHORD = re.compile(r'\[(.*?)\]')
|
CHORD = re.compile(r'\[(.*?)\]')
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
CHORD = '<td class="chord">{}</td>'
|
# Tables -- don't work when rendered to PDF
|
||||||
SYLLB = '<td class="syllable">{}</td>'
|
# CHORD = '<td class="chord">{}</td>'
|
||||||
LINE = '<table class="line" border="0" cellpadding="0" cellspacing="0"><tr class="chords-line">{}</tr><tr ' \
|
# SYLLB = '<td class="syllable">{}</td>'
|
||||||
'class="lyrics-line">{}</tr></table>'
|
# LINE = '<table class="line" border="0" cellpadding="0" cellspacing="0"><tr class="chords-line">{}</tr><tr ' \
|
||||||
|
# 'class="lyrics-line">{}</tr></table>'
|
||||||
|
|
||||||
|
# Divs
|
||||||
|
CHORD = '<div class="chord">{}</div>'
|
||||||
|
SYLLB = '<div class="syllable">{}</div>'
|
||||||
|
WRAPP = '<div class="wrapper">{}</div>'
|
||||||
|
LINE = '<div class="line">{}</div>'
|
||||||
STANZA = '''<section class="stanza-section {verse_type}-section">
|
STANZA = '''<section class="stanza-section {verse_type}-section">
|
||||||
<h4 class="stanza-heading {verse_type}-heading">{verse_name}</h4>
|
<h4 class="stanza-heading {verse_type}-heading">{verse_name}</h4>
|
||||||
<div class="stanza {verse_type}">
|
<div class="stanza {verse_type}">
|
||||||
@ -16,6 +23,9 @@ HTML = '''<html>
|
|||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<style>
|
<style>
|
||||||
h1.title, h3.composer, h4.stanza-heading {{ font-weight: normal; }}
|
h1.title, h3.composer, h4.stanza-heading {{ font-weight: normal; }}
|
||||||
|
h4.stanza-heading {{ margin-bottom: 0; }}
|
||||||
|
.stanza-section {{ margin-bottom: 2rem; page-break-inside: avoid; }}
|
||||||
|
.wrapper {{ display: inline-block !important; }}
|
||||||
{styles}
|
{styles}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@ -403,26 +413,30 @@ def generate_option_styles(options):
|
|||||||
return styles
|
return styles
|
||||||
|
|
||||||
|
|
||||||
def render(song, options=None):
|
def render(song, options=None, extra_styles=None):
|
||||||
"""Render a song to HTML"""
|
"""Render a song to HTML"""
|
||||||
styles = options and generate_option_styles(options) or []
|
styles = options and generate_option_styles(options) or []
|
||||||
|
if extra_styles:
|
||||||
|
styles.append(extra_styles)
|
||||||
rendered_verses = []
|
rendered_verses = []
|
||||||
for verse in song.verse_order:
|
for verse in song.verse_order:
|
||||||
rendered_lines = []
|
rendered_lines = []
|
||||||
for line in verse.lines:
|
for line in verse.lines:
|
||||||
rendered_chords = []
|
|
||||||
rendered_syllables = []
|
rendered_syllables = []
|
||||||
for word_counter, word in enumerate(line.words):
|
for word_counter, word in enumerate(line.words):
|
||||||
is_last_word = (word_counter + 1) == len(line.words)
|
is_last_word = (word_counter + 1) == len(line.words)
|
||||||
for syll_counter, syllable in enumerate(word.syllables):
|
for syll_counter, syllable in enumerate(word.syllables):
|
||||||
is_last_syllable = (syll_counter + 1) == len(word.syllables)
|
is_last_syllable = (syll_counter + 1) == len(word.syllables)
|
||||||
rendered_chords.append(CHORD.format(syllable.chord or ' '))
|
rendered_chord = CHORD.format(syllable.chord or ' ')
|
||||||
rendered_syllables.append(SYLLB.format(
|
rendered_syllable = SYLLB.format(
|
||||||
syllable.syllable + (' ' if is_last_syllable and not is_last_word else '')))
|
(syllable.syllable or ' ') +
|
||||||
rendered_lines.append(LINE.format(''.join(rendered_chords), ''.join(rendered_syllables)))
|
(' ' if is_last_syllable and not is_last_word else ''))
|
||||||
|
rendered_syllables.append(WRAPP.format(rendered_chord + rendered_syllable))
|
||||||
|
rendered_lines.append(LINE.format(''.join(rendered_syllables)))
|
||||||
rendered_verses.append(STANZA.format(verse_type=verse.type_ or '', verse_name=verse.title,
|
rendered_verses.append(STANZA.format(verse_type=verse.type_ or '', verse_name=verse.title,
|
||||||
verse_body='\n'.join(rendered_lines)))
|
verse_body='\n'.join(rendered_lines)))
|
||||||
title = song.metadata.get('title') or 'Song'
|
title = song.metadata.get('title') or 'Song'
|
||||||
metadata = TITLE.format(title=title, composer=song.metadata.get('artist') or song.metadata.get('composer') or '')
|
metadata = TITLE.format(title=title, composer=song.metadata.get('artist') or
|
||||||
|
song.metadata.get('composer') or '')
|
||||||
body = metadata + '\n' + '\n'.join(rendered_verses)
|
body = metadata + '\n' + '\n'.join(rendered_verses)
|
||||||
return HTML.format(title=title, body=body, styles=os.linesep.join(styles))
|
return HTML.format(title=title, body=body, styles=os.linesep.join(styles))
|
||||||
|
Loading…
Reference in New Issue
Block a user