openlp/openlp/plugins/songs/lib/importers/cclifile.py

340 lines
15 KiB
Python

# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
import codecs
import logging
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2018 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import chardet
from openlp.core.common.i18n import translate
from openlp.plugins.songs.lib import VerseType
from .songimport import SongImport
log = logging.getLogger(__name__)
class CCLIFileImport(SongImport):
"""
The :class:`CCLIFileImport` class provides OpenLP with the ability to import CCLI SongSelect song files in
TEXT and USR formats. See `<http://www.ccli.com/>`_ for more details.
NOTE: Sometime before 2015, CCLI/SongSelect has changed the USR filename to a .bin extension; however,
the file format remained the same as the .usr file format.
"""
def __init__(self, manager, **kwargs):
"""
Initialise the import.
:param manager: The song manager for the running OpenLP installation.
:param kwargs: The files to be imported.
"""
super().__init__(manager, **kwargs)
def do_import(self):
"""
Import either a USR or TEXT SongSelect file.
"""
log.debug('Starting CCLI File Import')
self.import_wizard.progress_bar.setMaximum(len(self.import_source))
for file_path in self.import_source:
log.debug('Importing CCLI File: {name}'.format(name=file_path))
if file_path.is_file():
with file_path.open('rb') as detect_file:
detect_content = detect_file.read(2048)
try:
str(detect_content, 'utf-8')
details = {'confidence': 1, 'encoding': 'utf-8'}
except UnicodeDecodeError:
details = chardet.detect(detect_content)
in_file = codecs.open(str(file_path), 'r', details['encoding'])
if not in_file.read(1) == '\ufeff':
# not UTF or no BOM was found
in_file.seek(0)
lines = in_file.readlines()
in_file.close()
ext = file_path.suffix.lower()
if ext == '.usr' or ext == '.bin':
log.info('SongSelect USR format file found: {name}'.format(name=file_path))
if not self.do_import_usr_file(lines):
self.log_error(file_path)
elif ext == '.txt':
log.info('SongSelect TEXT format file found: {name}'.format(name=file_path))
if not self.do_import_txt_file(lines):
self.log_error(file_path)
else:
self.log_error(file_path, translate('SongsPlugin.CCLIFileImport',
'The file does not have a valid extension.'))
log.info('Extension {name} is not valid'.format(name=file_path))
if self.stop_import_flag:
return
def do_import_usr_file(self, text_list):
"""
The :func:`doImport_usr_file` method provides OpenLP with the ability
to import CCLI SongSelect songs in *USR* file format.
**SongSelect USR file format**
``[File]``
USR file format first line
``Type=``
Indicates the file type
e.g. *Type=SongSelect Import File*
``Version=3.0``
File format version
``[S A2672885]``
Contains the CCLI Song number e.g. *2672885*
``Title=``
Contains the song title (e.g. *Title=Above All*)
``Author=``
Contains a | delimited list of the song authors
e.g. *Author=LeBlanc, Lenny | Baloche, Paul*
``Copyright=``
Contains a | delimited list of the song copyrights
e.g. Copyright=1999 Integrity's Hosanna! Music |
LenSongs Publishing (Verwaltet von Gerth Medien
Musikverlag)
``Admin=``
Contains the song administrator
e.g. *Admin=Gerth Medien Musikverlag*
``Themes=``
Contains a /t delimited list of the song themes
e.g. *Themes=Cross/tKingship/tMajesty/tRedeemer*
``Keys=``
Contains the keys in which the music is played??
e.g. *Keys=A*
``Fields=``
Contains a list of the songs fields in order /t delimited
e.g. *Fields=Vers 1/tVers 2/tChorus 1/tAndere 1*
``Words=``
Contains the songs various lyrics in order as shown by the
*Fields* description
e.g. *Words=Above all powers....* [/n = CR, /n/t = CRLF]
:param text_list: An array of strings containing the usr file content.
"""
log.debug('USR file text: {text}'.format(text=text_list))
song_author = ''
song_topics = ''
for line in text_list:
if line.startswith('[S '):
ccli, line = line.split(']', 1)
if ccli.startswith('[S A'):
self.ccli_number = ccli[4:].strip()
else:
self.ccli_number = ccli[3:].strip()
if line.startswith('Title='):
self.title = line[6:].strip()
elif line.startswith('Author='):
song_author = line[7:].strip()
elif line.startswith('Copyright='):
self.add_copyright(line[10:].strip())
elif line.startswith('Themes='):
song_topics = line[7:].strip().replace(' | ', '/t')
elif line.startswith('Fields='):
# Fields contain single line indicating verse, chorus, etc,
# /t delimited, same as with words field. store separately
# and process at end.
song_fields = line[7:].strip()
elif line.startswith('Words='):
song_words = line[6:].strip()
# Unhandled usr keywords: Type, Version, Admin, Keys
# Process Fields and words sections.
check_first_verse_line = False
field_list = song_fields.split('/t')
words_list = song_words.split('/t')
for counter in range(len(field_list)):
if field_list[counter].startswith('Ver'):
verse_type = VerseType.tags[VerseType.Verse]
elif field_list[counter].startswith('Ch'):
verse_type = VerseType.tags[VerseType.Chorus]
elif field_list[counter].startswith('Br'):
verse_type = VerseType.tags[VerseType.Bridge]
else:
verse_type = VerseType.tags[VerseType.Other]
check_first_verse_line = True
verse_text = str(words_list[counter])
verse_text = verse_text.replace('/n', '\n')
verse_text = verse_text.replace(' | ', '\n')
verse_lines = verse_text.split('\n', 1)
if check_first_verse_line:
if verse_lines[0].startswith('(PRE-CHORUS'):
verse_type = VerseType.tags[VerseType.PreChorus]
log.debug('USR verse PRE-CHORUS: {lines}'.format(lines=verse_lines[0]))
verse_text = verse_lines[1]
elif verse_lines[0].startswith('(BRIDGE'):
verse_type = VerseType.tags[VerseType.Bridge]
log.debug('USR verse BRIDGE')
verse_text = verse_lines[1]
elif verse_lines[0].startswith('('):
verse_type = VerseType.tags[VerseType.Other]
verse_text = verse_lines[1]
if verse_text:
self.add_verse(verse_text, verse_type)
check_first_verse_line = False
# Handle multiple authors
author_list = song_author.split('/')
if len(author_list) < 2:
author_list = song_author.split('|')
for author in author_list:
separated = author.split(',')
if len(separated) > 1:
author = ' '.join(map(str.strip, reversed(separated)))
self.add_author(author.strip())
self.topics = [topic.strip() for topic in song_topics.split('/t')]
return self.finish()
def do_import_txt_file(self, text_list):
"""
The :func:`doImport_txt_file` method provides OpenLP with the ability
to import CCLI SongSelect songs in *TXT* file format.
:param text_list: An array of strings containing the txt file content.
SongSelect .txt file format::
Song Title # Contains the song title
<Empty line>
Verse type and number # e.g. Verse 1, Chorus 1
Verse lyrics
<Empty line>
<Empty line>
Verse type and number (repeats)
Verse lyrics
<Empty line>
<Empty line>
Song CCLI number
# e.g. CCLI Number (e.g.CCLI-Liednummer: 2672885)
Song copyright (if it begins ©, otherwise after authors)
# e.g. © 1999 Integrity's Hosanna! Music | LenSongs Publishing
Song authors # e.g. Lenny LeBlanc | Paul Baloche
Licencing info
# e.g. For use solely with the SongSelect Terms of Use.
All rights Reserved. www.ccli.com
CCLI Licence number of user
# e.g. CCLI-Liedlizenznummer: 14 / CCLI License No. 14
"""
log.debug('TXT file text: {text}'.format(text=text_list))
line_number = 0
check_first_verse_line = False
verse_text = ''
song_author = ''
verse_start = False
for line in text_list:
verse_type = 'v'
clean_line = line.strip()
if not clean_line:
if line_number == 0:
continue
elif verse_start:
if verse_text:
self.add_verse(verse_text, verse_type)
verse_text = ''
verse_start = False
else:
# line_number=0, song title
if line_number == 0:
self.title = clean_line
line_number += 1
# line_number=1, verses
elif line_number == 1:
# line_number=1, ccli number, first line after verses
if clean_line.startswith('CCLI'):
line_number += 1
ccli_parts = clean_line.split(' ')
self.ccli_number = ccli_parts[len(ccli_parts) - 1]
elif not verse_start:
# We have the verse descriptor
verse_desc_parts = clean_line.split(' ')
if len(verse_desc_parts) == 2:
if verse_desc_parts[0].startswith('Ver'):
verse_type = VerseType.tags[VerseType.Verse]
elif verse_desc_parts[0].startswith('Ch'):
verse_type = VerseType.tags[VerseType.Chorus]
elif verse_desc_parts[0].startswith('Br'):
verse_type = VerseType.tags[VerseType.Bridge]
else:
# we need to analyse the next line for
# verse type, so set flag
verse_type = VerseType.tags[VerseType.Other]
check_first_verse_line = True
else:
verse_type = VerseType.tags[VerseType.Other]
verse_start = True
else:
# check first line for verse type
if check_first_verse_line:
if line.startswith('(PRE-CHORUS'):
verse_type = VerseType.tags[VerseType.PreChorus]
elif line.startswith('(BRIDGE'):
verse_type = VerseType.tags[VerseType.Bridge]
# Handle all other misc types
elif line.startswith('('):
verse_type = VerseType.tags[VerseType.Other]
else:
verse_text = verse_text + line
check_first_verse_line = False
else:
# We have verse content or the start of the
# last part. Add l so as to keep the CRLF
verse_text = verse_text + line
else:
# line_number=2, copyright
if line_number == 2:
line_number += 1
if clean_line.startswith('©'):
self.add_copyright(clean_line)
else:
song_author = clean_line
# n=3, authors
elif line_number == 3:
line_number += 1
if song_author:
self.add_copyright(clean_line)
else:
song_author = clean_line
# line_number=4, comments lines before last line
elif line_number == 4 and not clean_line.startswith('CCL'):
self.comments += clean_line
# split on known separators
author_list = song_author.split('/')
if len(author_list) < 2:
author_list = song_author.split('|')
# Clean spaces before and after author names.
for author_name in author_list:
self.add_author(author_name.strip())
return self.finish()