Added support for importing chords and verseorder from songbeamer.

This commit is contained in:
Tomas Groth 2016-08-18 21:55:22 +02:00
parent 25c268145a
commit b35d300708
2 changed files with 130 additions and 32 deletions

View File

@ -23,13 +23,15 @@
The :mod:`songbeamer` module provides the functionality for importing SongBeamer songs into the OpenLP database.
"""
import chardet
import codecs
import logging
import os
import re
import base64
import math
from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib.importers.songimport import SongImport
from openlp.core.common import Settings, is_win, is_macosx, get_file_encoding
log = logging.getLogger(__name__)
@ -110,27 +112,31 @@ class SongBeamerImport(SongImport):
self.set_defaults()
self.current_verse = ''
self.current_verse_type = VerseType.tags[VerseType.Verse]
read_verses = False
self.chord_table = None
file_name = os.path.split(import_file)[1]
if os.path.isfile(import_file):
# First open in binary mode to detect the encoding
detect_file = open(import_file, 'rb')
details = chardet.detect(detect_file.read())
detect_file.close()
infile = codecs.open(import_file, 'r', details['encoding'])
# Detect the encoding
self.input_file_encoding = get_file_encoding(file_name)['encoding']
# The encoding should only be ANSI (cp1251), UTF-8, Unicode, Big-Endian-Unicode.
# So if it doesn't start with 'u' we default to cp1251. See:
# https://forum.songbeamer.com/viewtopic.php?p=419&sid=ca4814924e37c11e4438b7272a98b6f2
if self.input_file_encoding.lower().startswith('u'):
self.input_file_encoding = 'cp1251'
infile = open(import_file, 'rt', encoding=self.input_file_encoding)
song_data = infile.readlines()
infile.close()
else:
continue
self.title = file_name.split('.sng')[0]
read_verses = False
# The first verse separator doesn't count, but the others does, so line count starts at -1
line_number = -1
for line in song_data:
# Just make sure that the line is of the type 'Unicode'.
line = str(line).strip()
line = line.rstrip()
if line.startswith('#') and not read_verses:
self.parseTags(line)
elif line.startswith('--'):
# --- and -- allowed for page-breaks (difference in Songbeamer only in printout)
self.parse_tags(line)
elif line.startswith('---'):
# '---' is a verse breaker
if self.current_verse:
self.replace_html_tags()
self.add_verse(self.current_verse, self.current_verse_type)
@ -138,19 +144,51 @@ class SongBeamerImport(SongImport):
self.current_verse_type = VerseType.tags[VerseType.Verse]
read_verses = True
verse_start = True
# Songbeamer allows chord on line "-1"
if line_number == -1:
first_line = self.insert_chords(line_number, '')
if first_line:
self.current_verse = first_line.strip() + '\n'
line_number += 1
elif line.startswith('--'):
# '--' is a page breaker, we convert to optional page break
self.current_verse += '[---]\n'
line_number += 1
elif read_verses:
line = self.insert_chords(line_number, line)
if verse_start:
verse_start = False
if not self.check_verse_marks(line):
self.current_verse = line + '\n'
self.current_verse += line.strip() + '\n'
else:
self.current_verse += line + '\n'
self.current_verse += line.strip() + '\n'
line_number += 1
if self.current_verse:
self.replace_html_tags()
self.add_verse(self.current_verse, self.current_verse_type)
if not self.finish():
self.log_error(import_file)
def insert_chords(self, line_number, line):
"""
Insert chords into text if any exists and chords import is enabled
:param linenumber: Number of the current line
:param line: The line of lyrics to insert chords
"""
if self.chord_table and not Settings().value('songs/disable chords import') and line_number in self.chord_table:
line_idx = sorted(self.chord_table[line_number].keys(), reverse=True)
for idx in line_idx:
# In SongBeamer the column position of the chord can be a decimal, we just round it up.
int_idx = int(math.ceil(idx))
if int_idx < 0:
int_idx = 0
elif int_idx > len(line):
# If a chord is placed beyond the current end of the line, extend the line with spaces.
line += ' ' * (int_idx - len(line))
line = line[:int_idx] + '[' + self.chord_table[line_number][idx] + ']' + line[int_idx:]
return line
def replace_html_tags(self):
"""
This can be called to replace SongBeamer's specific (html) tags with OpenLP's specific (html) tags.
@ -158,7 +196,7 @@ class SongBeamerImport(SongImport):
for pair in SongBeamerImport.HTML_TAG_PAIRS:
self.current_verse = pair[0].sub(pair[1], self.current_verse)
def parseTags(self, line):
def parse_tags(self, line):
"""
Parses a meta data line.
@ -175,6 +213,8 @@ class SongBeamerImport(SongImport):
self.add_copyright(tag_val[1])
elif tag_val[0] == '#AddCopyrightInfo':
pass
elif tag_val[0] == '#AudioFile':
parse_audio_file(tag_val[1])
elif tag_val[0] == '#Author':
self.parse_author(tag_val[1])
elif tag_val[0] == '#BackgroundImage':
@ -186,12 +226,15 @@ class SongBeamerImport(SongImport):
elif tag_val[0] == '#CCLI':
self.ccli_number = tag_val[1]
elif tag_val[0] == '#Chords':
pass
self.chord_table = self.parse_chords(tag_val[1])
elif tag_val[0] == '#ChurchSongID':
pass
elif tag_val[0] == '#ColorChords':
pass
elif tag_val[0] == '#Comments':
try:
self.comments = base64.b64decode(tag_val[1]).decode(self.input_file_encoding)
except ValueError:
self.comments = tag_val[1]
elif tag_val[0] == '#Editor':
pass
@ -242,7 +285,7 @@ class SongBeamerImport(SongImport):
elif tag_val[0] == '#TextAlign':
pass
elif tag_val[0] == '#Title':
self.title = str(tag_val[1]).strip()
self.title = tag_val[1].strip()
elif tag_val[0] == '#TitleAlign':
pass
elif tag_val[0] == '#TitleFontSize':
@ -262,25 +305,80 @@ class SongBeamerImport(SongImport):
elif tag_val[0] == '#Version':
pass
elif tag_val[0] == '#VerseOrder':
# TODO: add the verse order.
pass
verse_order = tag_val[1].strip()
for verse_mark in verse_order.split(','):
new_verse_mark = self.convert_verse_marks(verse_mark)
if new_verse_mark:
self.verse_order_list.append(new_verse_mark)
def check_verse_marks(self, line):
"""
Check and add the verse's MarkType. Returns ``True`` if the given line contains a correct verse mark otherwise
``False``.
:param line: The line to check for marks (unicode).
:param line: The line to check for marks.
"""
marks = line.split(' ')
if len(marks) <= 2 and marks[0].lower() in SongBeamerTypes.MarkTypes:
self.current_verse_type = SongBeamerTypes.MarkTypes[marks[0].lower()]
if len(marks) == 2:
# If we have a digit, we append it to current_verse_type.
if marks[1].isdigit():
self.current_verse_type += marks[1]
return True
elif marks[0].lower().startswith('$$m='): # this verse-mark cannot be numbered
self.current_verse_type = SongBeamerTypes.MarkTypes['$$m=']
new_verse_mark = self.convert_verse_marks(line)
if new_verse_mark:
self.current_verse_type = new_verse_mark
return True
return False
def convert_verse_marks(self, line):
"""
Convert the verse's MarkType. Returns the OpenLP versemark if the given line contains a correct SongBeamer verse
mark otherwise ``None``.
:param line: The line to check for marks.
"""
new_verse_mark = None
marks = line.split(' ')
if len(marks) <= 2 and marks[0].lower() in SongBeamerTypes.MarkTypes:
new_verse_mark = SongBeamerTypes.MarkTypes[marks[0].lower()]
if len(marks) == 2:
# If we have a digit, we append it to the converted verse mark
if marks[1].isdigit():
new_verse_mark += marks[1]
elif marks[0].lower().startswith('$$m='): # this verse-mark cannot be numbered
new_verse_mark = SongBeamerTypes.MarkTypes['$$m=']
return new_verse_mark
def parse_chords(self, chords):
"""
Parse chords. The chords are in a base64 encode string. The decoded string is an index of chord placement
separated by "\r", like this: "<linecolumn>,<linenumber>,<chord>\r"
:param chords: Chords in a base64 encoded string
"""
chord_list = base64.b64decode(chords).decode(self.input_file_encoding).split('\r')
chord_table = {}
for chord_index in chord_list:
if not chord_index:
continue
[col_str, line_str, chord] = chord_index.split(',')
col = float(col_str)
line = int(line_str)
if line not in chord_table:
chord_table[line] = {}
chord_table[line][col] = chord
return chord_table
def parse_audio_file(self, audio_file_path):
"""
Parse audio file. The path is relative to the SongsBeamer Songs folder.
:param audio_file_path: Path to the audio file
"""
# The path is relative to SongBeamers Song folder
if is_win():
user_doc_folder = os.path.expandvars('$DOCUMENTS')
elif is_macosx():
user_doc_folder = os.path.join(os.path.expanduser('~'), 'Documents')
else:
# SongBeamer only runs on mac and win...
return
audio_file_path = os.path.normpath(os.path.join(user_doc_folder, 'SongBeamer', 'Songs', audio_file_path))
if os.path.isfile(audio_file_path):
self.add_media_file(audio_file_path)
else:
log.debug('Could not import mediafile "%s" since it does not exists!' % audio_file_path)

View File

@ -124,7 +124,7 @@ class VideoPsalmImport(SongImport):
for verse in song['Verses']:
verse_text = verse['Text']
# Strip out chords if set up to
if not Settings().value('songs/disable chords import'):
if Settings().value('songs/disable chords import'):
verse_text = re.sub(r'\[\w.*?\]', '', verse_text)
self.add_verse(verse_text, 'v')
if not self.finish():