2008-12-07 08:09:59 +00:00
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
2019-04-13 13:00:22 +00:00
|
|
|
|
##########################################################################
|
|
|
|
|
# OpenLP - Open Source Lyrics Projection #
|
|
|
|
|
# ---------------------------------------------------------------------- #
|
2022-12-31 15:54:46 +00:00
|
|
|
|
# Copyright (c) 2008-2023 OpenLP Developers #
|
2019-04-13 13:00:22 +00:00
|
|
|
|
# ---------------------------------------------------------------------- #
|
|
|
|
|
# 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, either version 3 of the License, or #
|
|
|
|
|
# (at your option) any later version. #
|
|
|
|
|
# #
|
|
|
|
|
# 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, see <https://www.gnu.org/licenses/>. #
|
|
|
|
|
##########################################################################
|
2013-02-11 19:44:04 +00:00
|
|
|
|
"""
|
|
|
|
|
The :mod:`~openlp.plugins.songs.lib` module contains a number of library functions and classes used in the Songs plugin.
|
|
|
|
|
"""
|
2013-06-14 20:20:26 +00:00
|
|
|
|
|
|
|
|
|
import logging
|
2011-03-14 18:59:59 +00:00
|
|
|
|
import re
|
2008-12-07 08:09:59 +00:00
|
|
|
|
|
2015-11-07 00:49:40 +00:00
|
|
|
|
from PyQt5 import QtWidgets
|
2011-02-23 20:22:29 +00:00
|
|
|
|
|
2017-10-07 07:05:07 +00:00
|
|
|
|
from openlp.core.common import CONTROL_CHARS
|
|
|
|
|
from openlp.core.common.applocation import AppLocation
|
|
|
|
|
from openlp.core.common.i18n import translate
|
2020-01-26 17:12:45 +00:00
|
|
|
|
from openlp.core.common.registry import Registry
|
2018-11-03 05:48:43 +00:00
|
|
|
|
from openlp.core.display.render import remove_tags
|
2018-10-26 23:15:31 +00:00
|
|
|
|
from openlp.plugins.songs.lib.db import Author, MediaFile, Song
|
2016-08-13 14:29:12 +00:00
|
|
|
|
from openlp.plugins.songs.lib.ui import SongStrings
|
2010-06-12 12:33:04 +00:00
|
|
|
|
|
2013-06-14 20:20:26 +00:00
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
2017-10-28 10:04:09 +00:00
|
|
|
|
WHITESPACE = re.compile(r'[\W_]+')
|
|
|
|
|
APOSTROPHE = re.compile(r'[\'`’ʻ′]')
|
2013-09-10 20:36:46 +00:00
|
|
|
|
# PATTERN will look for the next occurence of one of these symbols:
|
|
|
|
|
# \controlword - optionally preceded by \*, optionally followed by a number
|
|
|
|
|
# \'## - where ## is a pair of hex digits, representing a single character
|
|
|
|
|
# \# - where # is a single non-alpha character, representing a special symbol
|
|
|
|
|
# { or } - marking the beginning/end of a group
|
|
|
|
|
# a run of characters without any \ { } or end-of-line
|
2013-12-31 22:56:02 +00:00
|
|
|
|
PATTERN = re.compile(
|
|
|
|
|
r"(\\\*)?\\([a-z]{1,32})(-?\d{1,10})?[ ]?|\\'([0-9a-f]{2})|\\([^a-z*])|([{}])|[\r\n]+|([^\\{}\r\n]+)", re.I)
|
2012-07-03 22:26:54 +00:00
|
|
|
|
# RTF control words which specify a "destination" to be ignored.
|
|
|
|
|
DESTINATIONS = frozenset((
|
2013-12-31 22:56:02 +00:00
|
|
|
|
'aftncn', 'aftnsep', 'aftnsepc', 'annotation', 'atnauthor', 'atndate', 'atnicn', 'atnid', 'atnparent', 'atnref',
|
|
|
|
|
'atntime', 'atrfend', 'atrfstart', 'author', 'background', 'bkmkend', 'bkmkstart', 'blipuid', 'buptim', 'category',
|
|
|
|
|
'colorschememapping', 'colortbl', 'comment', 'company', 'creatim', 'datafield', 'datastore', 'defchp', 'defpap',
|
|
|
|
|
'do', 'doccomm', 'docvar', 'dptxbxtext', 'ebcend', 'ebcstart', 'factoidname', 'falt', 'fchars', 'ffdeftext',
|
|
|
|
|
'ffentrymcr', 'ffexitmcr', 'ffformat', 'ffhelptext', 'ffl', 'ffname', 'ffstattext', 'file', 'filetbl', 'fldinst',
|
|
|
|
|
'fldtype', 'fname', 'fontemb', 'fontfile', 'footer', 'footerf', 'footerl', 'footerr', 'footnote', 'formfield',
|
|
|
|
|
'ftncn', 'ftnsep', 'ftnsepc', 'g', 'generator', 'gridtbl', 'header', 'headerf', 'headerl', 'headerr', 'hl', 'hlfr',
|
|
|
|
|
'hlinkbase', 'hlloc', 'hlsrc', 'hsv', 'htmltag', 'info', 'keycode', 'keywords', 'latentstyles', 'lchars',
|
|
|
|
|
'levelnumbers', 'leveltext', 'lfolevel', 'linkval', 'list', 'listlevel', 'listname', 'listoverride',
|
|
|
|
|
'listoverridetable', 'listpicture', 'liststylename', 'listtable', 'listtext', 'lsdlockedexcept', 'macc', 'maccPr',
|
|
|
|
|
'mailmerge', 'maln', 'malnScr', 'manager', 'margPr', 'mbar', 'mbarPr', 'mbaseJc', 'mbegChr', 'mborderBox',
|
|
|
|
|
'mborderBoxPr', 'mbox', 'mboxPr', 'mchr', 'mcount', 'mctrlPr', 'md', 'mdeg', 'mdegHide', 'mden', 'mdiff', 'mdPr',
|
|
|
|
|
'me', 'mendChr', 'meqArr', 'meqArrPr', 'mf', 'mfName', 'mfPr', 'mfunc', 'mfuncPr', 'mgroupChr', 'mgroupChrPr',
|
|
|
|
|
'mgrow', 'mhideBot', 'mhideLeft', 'mhideRight', 'mhideTop', 'mhtmltag', 'mlim', 'mlimloc', 'mlimlow', 'mlimlowPr',
|
|
|
|
|
'mlimupp', 'mlimuppPr', 'mm', 'mmaddfieldname', 'mmath', 'mmathPict', 'mmathPr', 'mmaxdist', 'mmc', 'mmcJc',
|
|
|
|
|
'mmconnectstr', 'mmconnectstrdata', 'mmcPr', 'mmcs', 'mmdatasource', 'mmheadersource', 'mmmailsubject', 'mmodso',
|
|
|
|
|
'mmodsofilter', 'mmodsofldmpdata', 'mmodsomappedname', 'mmodsoname', 'mmodsorecipdata', 'mmodsosort', 'mmodsosrc',
|
|
|
|
|
'mmodsotable', 'mmodsoudl', 'mmodsoudldata', 'mmodsouniquetag', 'mmPr', 'mmquery', 'mmr', 'mnary', 'mnaryPr',
|
|
|
|
|
'mnoBreak', 'mnum', 'mobjDist', 'moMath', 'moMathPara', 'moMathParaPr', 'mopEmu', 'mphant', 'mphantPr', 'mplcHide',
|
|
|
|
|
'mpos', 'mr', 'mrad', 'mradPr', 'mrPr', 'msepChr', 'mshow', 'mshp', 'msPre', 'msPrePr', 'msSub', 'msSubPr',
|
|
|
|
|
'msSubSup', 'msSubSupPr', 'msSup', 'msSupPr', 'mstrikeBLTR', 'mstrikeH', 'mstrikeTLBR', 'mstrikeV', 'msub',
|
|
|
|
|
'msubHide', 'msup', 'msupHide', 'mtransp', 'mtype', 'mvertJc', 'mvfmf', 'mvfml', 'mvtof', 'mvtol', 'mzeroAsc',
|
|
|
|
|
'mzFrodesc', 'mzeroWid', 'nesttableprops', 'nextfile', 'nonesttables', 'objalias', 'objclass', 'objdata', 'object',
|
|
|
|
|
'objname', 'objsect', 'objtime', 'oldcprops', 'oldpprops', 'oldsprops', 'oldtprops', 'oleclsid', 'operator',
|
|
|
|
|
'panose', 'password', 'passwordhash', 'pgp', 'pgptbl', 'picprop', 'pict', 'pn', 'pnseclvl', 'pntext', 'pntxta',
|
|
|
|
|
'pntxtb', 'printim', 'private', 'propname', 'protend', 'protstart', 'protusertbl', 'pxe', 'result', 'revtbl',
|
|
|
|
|
'revtim', 'rsidtbl', 'rxe', 'shp', 'shpgrp', 'shpinst', 'shppict', 'shprslt', 'shptxt', 'sn', 'sp', 'staticval',
|
|
|
|
|
'stylesheet', 'subject', 'sv', 'svb', 'tc', 'template', 'themedata', 'title', 'txe', 'ud', 'upr', 'userprops',
|
|
|
|
|
'wgrffmtfilter', 'windowcaption', 'writereservation', 'writereservhash', 'xe', 'xform', 'xmlattrname',
|
|
|
|
|
'xmlattrvalue', 'xmlclose', 'xmlname', 'xmlnstbl', 'xmlopen'
|
|
|
|
|
))
|
2012-07-03 22:26:54 +00:00
|
|
|
|
# Translation of some special characters.
|
|
|
|
|
SPECIAL_CHARS = {
|
2013-09-08 03:50:51 +00:00
|
|
|
|
'\n': '\n',
|
|
|
|
|
'\r': '\n',
|
|
|
|
|
'~': '\u00A0',
|
|
|
|
|
'-': '\u00AD',
|
|
|
|
|
'_': '\u2011',
|
2013-08-31 18:17:38 +00:00
|
|
|
|
'par': '\n',
|
|
|
|
|
'sect': '\n\n',
|
2012-07-03 22:26:54 +00:00
|
|
|
|
# Required page and column break.
|
|
|
|
|
# Would be good if we could split verse into subverses here.
|
2013-08-31 18:17:38 +00:00
|
|
|
|
'page': '\n\n',
|
|
|
|
|
'column': '\n\n',
|
2012-07-03 22:26:54 +00:00
|
|
|
|
# Soft breaks.
|
2013-08-31 18:17:38 +00:00
|
|
|
|
'softpage': '[---]',
|
|
|
|
|
'softcol': '[---]',
|
|
|
|
|
'line': '\n',
|
|
|
|
|
'tab': '\t',
|
|
|
|
|
'emdash': '\u2014',
|
2013-12-24 09:24:32 +00:00
|
|
|
|
'endash': '\u2013',
|
2013-08-31 18:17:38 +00:00
|
|
|
|
'emspace': '\u2003',
|
|
|
|
|
'enspace': '\u2002',
|
|
|
|
|
'qmspace': '\u2005',
|
|
|
|
|
'bullet': '\u2022',
|
|
|
|
|
'lquote': '\u2018',
|
|
|
|
|
'rquote': '\u2019',
|
|
|
|
|
'ldblquote': '\u201C',
|
|
|
|
|
'rdblquote': '\u201D',
|
|
|
|
|
'ltrmark': '\u200E',
|
|
|
|
|
'rtlmark': '\u200F',
|
|
|
|
|
'zwj': '\u200D',
|
2013-12-31 22:56:02 +00:00
|
|
|
|
'zwnj': '\u200C'
|
|
|
|
|
}
|
2012-07-03 22:26:54 +00:00
|
|
|
|
CHARSET_MAPPING = {
|
2013-09-08 03:50:51 +00:00
|
|
|
|
'0': 'cp1252',
|
|
|
|
|
'128': 'cp932',
|
|
|
|
|
'129': 'cp949',
|
|
|
|
|
'134': 'cp936',
|
|
|
|
|
'161': 'cp1253',
|
|
|
|
|
'162': 'cp1254',
|
|
|
|
|
'163': 'cp1258',
|
|
|
|
|
'177': 'cp1255',
|
|
|
|
|
'178': 'cp1256',
|
|
|
|
|
'186': 'cp1257',
|
|
|
|
|
'204': 'cp1251',
|
|
|
|
|
'222': 'cp874',
|
2013-12-31 22:56:02 +00:00
|
|
|
|
'238': 'cp1250'
|
|
|
|
|
}
|
2012-07-03 22:26:54 +00:00
|
|
|
|
|
2011-06-01 22:47:42 +00:00
|
|
|
|
|
2010-06-12 12:33:04 +00:00
|
|
|
|
class VerseType(object):
|
|
|
|
|
"""
|
2013-02-24 18:13:50 +00:00
|
|
|
|
VerseType provides an enumeration for the tags that may be associated with verses in songs.
|
2010-06-12 12:33:04 +00:00
|
|
|
|
"""
|
|
|
|
|
Verse = 0
|
|
|
|
|
Chorus = 1
|
|
|
|
|
Bridge = 2
|
|
|
|
|
PreChorus = 3
|
|
|
|
|
Intro = 4
|
|
|
|
|
Ending = 5
|
|
|
|
|
Other = 6
|
2011-02-17 19:46:01 +00:00
|
|
|
|
|
2013-12-31 22:56:02 +00:00
|
|
|
|
names = ['Verse', 'Chorus', 'Bridge', 'Pre-Chorus', 'Intro', 'Ending', 'Other']
|
2013-02-24 18:13:50 +00:00
|
|
|
|
tags = [name[0].lower() for name in names]
|
2011-02-17 19:46:01 +00:00
|
|
|
|
|
2013-02-24 18:13:50 +00:00
|
|
|
|
translated_names = [
|
2012-05-17 18:57:01 +00:00
|
|
|
|
translate('SongsPlugin.VerseType', 'Verse'),
|
|
|
|
|
translate('SongsPlugin.VerseType', 'Chorus'),
|
|
|
|
|
translate('SongsPlugin.VerseType', 'Bridge'),
|
|
|
|
|
translate('SongsPlugin.VerseType', 'Pre-Chorus'),
|
|
|
|
|
translate('SongsPlugin.VerseType', 'Intro'),
|
|
|
|
|
translate('SongsPlugin.VerseType', 'Ending'),
|
|
|
|
|
translate('SongsPlugin.VerseType', 'Other')]
|
2013-03-21 19:05:40 +00:00
|
|
|
|
|
2013-02-27 12:41:04 +00:00
|
|
|
|
translated_tags = [name[0].lower() for name in translated_names]
|
2011-02-16 18:37:51 +00:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
2011-11-24 22:34:27 +00:00
|
|
|
|
def translated_tag(verse_tag, default=Other):
|
2011-02-18 00:48:58 +00:00
|
|
|
|
"""
|
2013-02-24 18:13:50 +00:00
|
|
|
|
Return the translated UPPERCASE tag for a given tag, used to show translated verse tags in UI
|
2011-02-18 00:48:58 +00:00
|
|
|
|
|
2014-03-06 20:40:08 +00:00
|
|
|
|
:param verse_tag: The string to return a VerseType for
|
|
|
|
|
:param default: Default return value if no matching tag is found
|
2013-12-31 22:56:02 +00:00
|
|
|
|
:return: A translated UPPERCASE tag
|
2011-02-18 00:48:58 +00:00
|
|
|
|
"""
|
|
|
|
|
verse_tag = verse_tag[0].lower()
|
2013-02-24 18:13:50 +00:00
|
|
|
|
for num, tag in enumerate(VerseType.tags):
|
2011-02-18 00:48:58 +00:00
|
|
|
|
if verse_tag == tag:
|
2013-02-27 12:41:04 +00:00
|
|
|
|
return VerseType.translated_tags[num].upper()
|
2013-03-07 12:34:35 +00:00
|
|
|
|
if len(VerseType.names) > default:
|
2013-02-27 12:41:04 +00:00
|
|
|
|
return VerseType.translated_tags[default].upper()
|
2013-02-19 14:07:34 +00:00
|
|
|
|
else:
|
2013-03-04 08:53:43 +00:00
|
|
|
|
return VerseType.translated_tags[VerseType.Other].upper()
|
2011-02-18 00:48:58 +00:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
2011-11-24 22:34:27 +00:00
|
|
|
|
def translated_name(verse_tag, default=Other):
|
2011-02-18 00:48:58 +00:00
|
|
|
|
"""
|
|
|
|
|
Return the translated name for a given tag
|
|
|
|
|
|
2014-03-06 20:40:08 +00:00
|
|
|
|
:param verse_tag: The string to return a VerseType for
|
|
|
|
|
:param default: Default return value if no matching tag is found
|
2013-12-31 22:56:02 +00:00
|
|
|
|
:return: Translated name for the given tag
|
2011-02-18 00:48:58 +00:00
|
|
|
|
"""
|
|
|
|
|
verse_tag = verse_tag[0].lower()
|
2013-02-24 18:13:50 +00:00
|
|
|
|
for num, tag in enumerate(VerseType.tags):
|
2011-02-18 00:48:58 +00:00
|
|
|
|
if verse_tag == tag:
|
2013-03-07 12:34:35 +00:00
|
|
|
|
return VerseType.translated_names[num]
|
|
|
|
|
if len(VerseType.names) > default:
|
2013-02-24 18:13:50 +00:00
|
|
|
|
return VerseType.translated_names[default]
|
2013-02-19 14:07:34 +00:00
|
|
|
|
else:
|
2013-03-04 08:53:43 +00:00
|
|
|
|
return VerseType.translated_names[VerseType.Other]
|
2011-02-18 00:48:58 +00:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
2011-11-24 22:34:27 +00:00
|
|
|
|
def from_tag(verse_tag, default=Other):
|
2010-06-12 12:33:04 +00:00
|
|
|
|
"""
|
2011-02-17 15:05:58 +00:00
|
|
|
|
Return the VerseType for a given tag
|
2010-06-12 12:33:04 +00:00
|
|
|
|
|
2014-03-06 20:40:08 +00:00
|
|
|
|
:param verse_tag: The string to return a VerseType for
|
2014-04-25 20:04:43 +00:00
|
|
|
|
:param default: Default return value if no matching tag is found (a valid VerseType or None)
|
2013-12-31 22:56:02 +00:00
|
|
|
|
:return: A VerseType of the tag
|
2011-02-17 15:05:58 +00:00
|
|
|
|
"""
|
2011-02-18 00:48:58 +00:00
|
|
|
|
verse_tag = verse_tag[0].lower()
|
2013-02-24 18:13:50 +00:00
|
|
|
|
for num, tag in enumerate(VerseType.tags):
|
2011-02-18 00:48:58 +00:00
|
|
|
|
if verse_tag == tag:
|
2011-02-17 15:05:58 +00:00
|
|
|
|
return num
|
2014-04-26 05:07:08 +00:00
|
|
|
|
if default in range(0, len(VerseType.names)) or default is None:
|
2013-02-19 14:07:34 +00:00
|
|
|
|
return default
|
|
|
|
|
else:
|
|
|
|
|
return VerseType.Other
|
2011-02-16 18:37:51 +00:00
|
|
|
|
|
2011-02-17 15:05:58 +00:00
|
|
|
|
@staticmethod
|
2011-11-24 22:34:27 +00:00
|
|
|
|
def from_translated_tag(verse_tag, default=Other):
|
2011-02-17 15:05:58 +00:00
|
|
|
|
"""
|
|
|
|
|
Return the VerseType for a given tag
|
|
|
|
|
|
2014-03-06 20:40:08 +00:00
|
|
|
|
:param verse_tag: The string to return a VerseType for
|
|
|
|
|
:param default: Default return value if no matching tag is found
|
2013-12-31 22:56:02 +00:00
|
|
|
|
:return: The VerseType of a translated tag
|
2011-02-17 15:05:58 +00:00
|
|
|
|
"""
|
2011-02-18 00:48:58 +00:00
|
|
|
|
verse_tag = verse_tag[0].lower()
|
2013-02-24 18:13:50 +00:00
|
|
|
|
for num, tag in enumerate(VerseType.translated_tags):
|
2011-02-18 00:48:58 +00:00
|
|
|
|
if verse_tag == tag:
|
|
|
|
|
return num
|
2014-04-26 05:07:08 +00:00
|
|
|
|
if default in range(0, len(VerseType.names)) or default is None:
|
2013-02-19 14:07:34 +00:00
|
|
|
|
return default
|
|
|
|
|
else:
|
|
|
|
|
return VerseType.Other
|
2011-02-17 15:05:58 +00:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
2011-11-24 22:34:27 +00:00
|
|
|
|
def from_string(verse_name, default=Other):
|
2011-02-17 15:05:58 +00:00
|
|
|
|
"""
|
|
|
|
|
Return the VerseType for a given string
|
|
|
|
|
|
2014-03-06 20:40:08 +00:00
|
|
|
|
:param verse_name: The string to return a VerseType for
|
2013-12-31 22:56:02 +00:00
|
|
|
|
:param default: Default return value if no matching tag is found
|
|
|
|
|
:return: The VerseType determined from the string
|
2010-06-12 12:33:04 +00:00
|
|
|
|
"""
|
2011-02-18 00:48:58 +00:00
|
|
|
|
verse_name = verse_name.lower()
|
2013-02-24 18:13:50 +00:00
|
|
|
|
for num, name in enumerate(VerseType.names):
|
2011-02-18 00:48:58 +00:00
|
|
|
|
if verse_name == name.lower():
|
|
|
|
|
return num
|
2011-11-24 22:34:27 +00:00
|
|
|
|
return default
|
2010-06-12 12:33:04 +00:00
|
|
|
|
|
2011-02-16 18:37:51 +00:00
|
|
|
|
@staticmethod
|
2011-02-18 00:48:58 +00:00
|
|
|
|
def from_translated_string(verse_name):
|
2011-02-16 18:37:51 +00:00
|
|
|
|
"""
|
|
|
|
|
Return the VerseType for a given string
|
|
|
|
|
|
2014-03-06 20:40:08 +00:00
|
|
|
|
:param verse_name: The string to return a VerseType for
|
2013-12-31 22:56:02 +00:00
|
|
|
|
:return: A VerseType
|
2011-02-16 18:37:51 +00:00
|
|
|
|
"""
|
2011-02-18 00:48:58 +00:00
|
|
|
|
verse_name = verse_name.lower()
|
2013-02-24 18:13:50 +00:00
|
|
|
|
for num, translation in enumerate(VerseType.translated_names):
|
2011-02-18 00:48:58 +00:00
|
|
|
|
if verse_name == translation.lower():
|
2011-02-16 18:37:51 +00:00
|
|
|
|
return num
|
2016-03-22 21:08:56 +00:00
|
|
|
|
return None
|
2011-02-16 18:37:51 +00:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
2011-11-24 22:34:27 +00:00
|
|
|
|
def from_loose_input(verse_name, default=Other):
|
2011-02-17 15:05:58 +00:00
|
|
|
|
"""
|
2011-11-24 22:49:21 +00:00
|
|
|
|
Return the VerseType for a given string
|
2011-02-17 15:05:58 +00:00
|
|
|
|
|
2014-03-06 20:40:08 +00:00
|
|
|
|
:param verse_name: The string to return a VerseType for
|
|
|
|
|
:param default: Default return value if no matching tag is found
|
2013-12-31 22:56:02 +00:00
|
|
|
|
:return: A VerseType
|
2011-02-17 15:05:58 +00:00
|
|
|
|
"""
|
2011-02-18 00:48:58 +00:00
|
|
|
|
if len(verse_name) > 1:
|
|
|
|
|
verse_index = VerseType.from_translated_string(verse_name)
|
|
|
|
|
if verse_index is None:
|
2011-11-24 22:34:27 +00:00
|
|
|
|
verse_index = VerseType.from_string(verse_name, default)
|
2011-08-16 00:08:16 +00:00
|
|
|
|
elif len(verse_name) == 1:
|
2016-03-22 21:08:56 +00:00
|
|
|
|
verse_index = VerseType.from_translated_tag(verse_name, None)
|
2011-08-16 00:08:16 +00:00
|
|
|
|
if verse_index is None:
|
2011-11-24 22:34:27 +00:00
|
|
|
|
verse_index = VerseType.from_tag(verse_name, default)
|
|
|
|
|
else:
|
|
|
|
|
return default
|
2011-02-18 00:48:58 +00:00
|
|
|
|
return verse_index
|
2011-01-04 10:13:41 +00:00
|
|
|
|
|
2012-06-16 15:51:04 +00:00
|
|
|
|
|
2011-01-04 10:13:41 +00:00
|
|
|
|
def retrieve_windows_encoding(recommendation=None):
|
2011-02-07 16:29:06 +00:00
|
|
|
|
"""
|
2013-02-24 18:13:50 +00:00
|
|
|
|
Determines which encoding to use on an information source. The process uses both automated detection, which is
|
|
|
|
|
passed to this method as a recommendation, and user confirmation to return an encoding.
|
2011-02-07 16:29:06 +00:00
|
|
|
|
|
2014-03-06 20:40:08 +00:00
|
|
|
|
:param recommendation: A recommended encoding discovered programmatically for the user to confirm.
|
2013-12-31 22:56:02 +00:00
|
|
|
|
:return: A list of recommended encodings, or None
|
2011-02-07 16:29:06 +00:00
|
|
|
|
"""
|
2011-01-04 10:13:41 +00:00
|
|
|
|
# map chardet result to compatible windows standard code page
|
2013-12-31 22:56:02 +00:00
|
|
|
|
codepage_mapping = {'IBM866': 'cp866', 'TIS-620': 'cp874', 'SHIFT_JIS': 'cp932', 'GB2312': 'cp936',
|
|
|
|
|
'HZ-GB-2312': 'cp936', 'EUC-KR': 'cp949', 'Big5': 'cp950', 'ISO-8859-2': 'cp1250',
|
|
|
|
|
'windows-1250': 'cp1250', 'windows-1251': 'cp1251', 'windows-1252': 'cp1252',
|
|
|
|
|
'ISO-8859-7': 'cp1253', 'windows-1253': 'cp1253', 'ISO-8859-8': 'cp1255',
|
|
|
|
|
'windows-1255': 'cp1255'}
|
2011-01-04 10:13:41 +00:00
|
|
|
|
if recommendation in codepage_mapping:
|
|
|
|
|
recommendation = codepage_mapping[recommendation]
|
|
|
|
|
|
|
|
|
|
# Show dialog for encoding selection
|
2014-03-06 20:40:08 +00:00
|
|
|
|
encodings = [
|
|
|
|
|
('cp1256', translate('SongsPlugin', 'Arabic (CP-1256)')),
|
2013-08-31 18:17:38 +00:00
|
|
|
|
('cp1257', translate('SongsPlugin', 'Baltic (CP-1257)')),
|
|
|
|
|
('cp1250', translate('SongsPlugin', 'Central European (CP-1250)')),
|
|
|
|
|
('cp1251', translate('SongsPlugin', 'Cyrillic (CP-1251)')),
|
|
|
|
|
('cp1253', translate('SongsPlugin', 'Greek (CP-1253)')),
|
|
|
|
|
('cp1255', translate('SongsPlugin', 'Hebrew (CP-1255)')),
|
|
|
|
|
('cp932', translate('SongsPlugin', 'Japanese (CP-932)')),
|
|
|
|
|
('cp949', translate('SongsPlugin', 'Korean (CP-949)')),
|
|
|
|
|
('cp936', translate('SongsPlugin', 'Simplified Chinese (CP-936)')),
|
|
|
|
|
('cp874', translate('SongsPlugin', 'Thai (CP-874)')),
|
|
|
|
|
('cp950', translate('SongsPlugin', 'Traditional Chinese (CP-950)')),
|
|
|
|
|
('cp1254', translate('SongsPlugin', 'Turkish (CP-1254)')),
|
|
|
|
|
('cp1258', translate('SongsPlugin', 'Vietnam (CP-1258)')),
|
2013-12-31 22:56:02 +00:00
|
|
|
|
('cp1252', translate('SongsPlugin', 'Western European (CP-1252)'))
|
|
|
|
|
]
|
2011-01-04 10:13:41 +00:00
|
|
|
|
recommended_index = -1
|
|
|
|
|
if recommendation:
|
2016-08-13 14:29:12 +00:00
|
|
|
|
for index, encoding in enumerate(encodings):
|
|
|
|
|
if recommendation == encoding[0]:
|
2011-01-04 10:13:41 +00:00
|
|
|
|
recommended_index = index
|
|
|
|
|
break
|
2013-09-08 03:50:51 +00:00
|
|
|
|
if recommended_index > -1:
|
2015-11-07 00:49:40 +00:00
|
|
|
|
choice = QtWidgets.QInputDialog.getItem(
|
2013-12-31 22:56:02 +00:00
|
|
|
|
None,
|
2011-01-04 10:13:41 +00:00
|
|
|
|
translate('SongsPlugin', 'Character Encoding'),
|
|
|
|
|
translate('SongsPlugin', 'The codepage setting is responsible\n'
|
2013-12-31 22:56:02 +00:00
|
|
|
|
'for the correct character representation.\n'
|
|
|
|
|
'Usually you are fine with the preselected choice.'),
|
2011-01-04 10:13:41 +00:00
|
|
|
|
[pair[1] for pair in encodings], recommended_index, False)
|
|
|
|
|
else:
|
2015-11-07 00:49:40 +00:00
|
|
|
|
choice = QtWidgets.QInputDialog.getItem(
|
2013-12-31 22:56:02 +00:00
|
|
|
|
None,
|
2011-01-04 10:13:41 +00:00
|
|
|
|
translate('SongsPlugin', 'Character Encoding'),
|
|
|
|
|
translate('SongsPlugin', 'Please choose the character encoding.\n'
|
2013-12-31 22:56:02 +00:00
|
|
|
|
'The encoding is responsible for the correct character representation.'),
|
2014-03-06 20:40:08 +00:00
|
|
|
|
[pair[1] for pair in encodings], 0, False)
|
2011-01-04 10:13:41 +00:00
|
|
|
|
if not choice[1]:
|
|
|
|
|
return None
|
2013-09-08 03:50:51 +00:00
|
|
|
|
return next(filter(lambda item: item[1] == choice[0], encodings))[0]
|
2011-01-04 10:13:41 +00:00
|
|
|
|
|
2012-06-16 15:51:04 +00:00
|
|
|
|
|
2011-06-01 22:47:42 +00:00
|
|
|
|
def clean_string(string):
|
|
|
|
|
"""
|
2013-02-24 18:13:50 +00:00
|
|
|
|
Strips punctuation from the passed string to assist searching.
|
2013-12-31 22:56:02 +00:00
|
|
|
|
|
|
|
|
|
:param string: The string to clean
|
|
|
|
|
:return: A clean string
|
2011-06-01 22:47:42 +00:00
|
|
|
|
"""
|
2013-08-31 18:17:38 +00:00
|
|
|
|
return WHITESPACE.sub(' ', APOSTROPHE.sub('', string)).lower()
|
2012-04-04 07:26:51 +00:00
|
|
|
|
|
2012-06-16 15:51:04 +00:00
|
|
|
|
|
2012-03-22 06:50:54 +00:00
|
|
|
|
def clean_title(title):
|
|
|
|
|
"""
|
2013-02-24 18:13:50 +00:00
|
|
|
|
Cleans the song title by removing Unicode control chars groups C0 & C1, as well as any trailing spaces.
|
2013-12-31 22:56:02 +00:00
|
|
|
|
|
|
|
|
|
:param title: The song title to clean
|
|
|
|
|
:return: A clean title
|
2012-03-22 06:50:54 +00:00
|
|
|
|
"""
|
2013-08-31 18:17:38 +00:00
|
|
|
|
return CONTROL_CHARS.sub('', title).rstrip()
|
2011-06-01 22:47:42 +00:00
|
|
|
|
|
2012-06-16 15:51:04 +00:00
|
|
|
|
|
2011-03-14 18:59:59 +00:00
|
|
|
|
def clean_song(manager, song):
|
2011-02-23 20:22:29 +00:00
|
|
|
|
"""
|
2013-02-24 18:13:50 +00:00
|
|
|
|
Cleans the search title, rebuilds the search lyrics, adds a default author if the song does not have one and other
|
|
|
|
|
clean ups. This should always called when a new song is added or changed.
|
2011-02-23 20:22:29 +00:00
|
|
|
|
|
2013-12-31 22:56:02 +00:00
|
|
|
|
:param manager: The song database manager object.
|
2014-03-06 20:40:08 +00:00
|
|
|
|
:param song: The song object.
|
2011-02-23 20:22:29 +00:00
|
|
|
|
"""
|
2014-07-14 11:36:33 +00:00
|
|
|
|
from .openlyricsxml import SongXML
|
2013-03-11 21:10:29 +00:00
|
|
|
|
|
2012-03-22 06:50:54 +00:00
|
|
|
|
if song.title:
|
|
|
|
|
song.title = clean_title(song.title)
|
|
|
|
|
else:
|
2013-08-31 18:17:38 +00:00
|
|
|
|
song.title = ''
|
2012-03-22 06:50:54 +00:00
|
|
|
|
if song.alternate_title:
|
|
|
|
|
song.alternate_title = clean_title(song.alternate_title)
|
|
|
|
|
else:
|
2013-08-31 18:17:38 +00:00
|
|
|
|
song.alternate_title = ''
|
|
|
|
|
song.search_title = clean_string(song.title) + '@' + clean_string(song.alternate_title)
|
2013-12-31 22:56:02 +00:00
|
|
|
|
if isinstance(song.lyrics, bytes):
|
|
|
|
|
song.lyrics = str(song.lyrics, encoding='utf8')
|
|
|
|
|
verses = SongXML().get_verses(song.lyrics)
|
2017-10-04 07:44:08 +00:00
|
|
|
|
song.search_lyrics = ' '.join([clean_string(remove_tags(verse[1], True)) for verse in verses])
|
2014-04-02 07:03:53 +00:00
|
|
|
|
# The song does not have any author, add one.
|
2014-05-07 09:51:59 +00:00
|
|
|
|
if not song.authors_songs:
|
2014-04-02 07:03:53 +00:00
|
|
|
|
name = SongStrings.AuthorUnknown
|
|
|
|
|
author = manager.get_object_filtered(Author, Author.display_name == name)
|
|
|
|
|
if author is None:
|
2023-03-08 03:27:49 +00:00
|
|
|
|
author = Author(display_name=name, last_name='', first_name='')
|
2014-05-07 09:51:59 +00:00
|
|
|
|
song.add_author(author)
|
2012-04-02 11:38:22 +00:00
|
|
|
|
if song.copyright:
|
2013-08-31 18:17:38 +00:00
|
|
|
|
song.copyright = CONTROL_CHARS.sub('', song.copyright).strip()
|
2011-02-23 20:22:29 +00:00
|
|
|
|
|
2012-07-03 22:26:54 +00:00
|
|
|
|
|
|
|
|
|
def get_encoding(font, font_table, default_encoding, failed=False):
|
|
|
|
|
"""
|
|
|
|
|
Finds an encoding to use. Asks user, if necessary.
|
|
|
|
|
|
2014-03-06 20:40:08 +00:00
|
|
|
|
:param font: The number of currently active font.
|
|
|
|
|
:param font_table: Dictionary of fonts and respective encodings.
|
|
|
|
|
:param default_encoding: The default encoding to use when font_table is empty or no font is used.
|
|
|
|
|
:param failed: A boolean indicating whether the previous encoding didn't work.
|
2012-07-03 22:26:54 +00:00
|
|
|
|
"""
|
|
|
|
|
encoding = None
|
|
|
|
|
if font in font_table:
|
|
|
|
|
encoding = font_table[font]
|
|
|
|
|
if not encoding and default_encoding:
|
|
|
|
|
encoding = default_encoding
|
|
|
|
|
if not encoding or failed:
|
|
|
|
|
encoding = retrieve_windows_encoding()
|
|
|
|
|
default_encoding = encoding
|
|
|
|
|
font_table[font] = encoding
|
|
|
|
|
return encoding, default_encoding
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def strip_rtf(text, default_encoding=None):
|
2012-06-24 18:08:20 +00:00
|
|
|
|
"""
|
2012-07-03 22:26:54 +00:00
|
|
|
|
This function strips RTF control structures and returns an unicode string.
|
2012-06-24 18:08:20 +00:00
|
|
|
|
|
2013-12-31 22:56:02 +00:00
|
|
|
|
Thanks to Markus Jarderot (MizardX) for this code, used by permission. http://stackoverflow.com/questions/188545
|
2012-07-03 22:26:54 +00:00
|
|
|
|
|
2014-03-06 20:40:08 +00:00
|
|
|
|
:param text: RTF-encoded text, a string.
|
|
|
|
|
:param default_encoding: Default encoding to use when no encoding is specified.
|
2013-12-31 22:56:02 +00:00
|
|
|
|
:return: A tuple ``(text, encoding)`` where ``text`` is the clean text and ``encoding`` is the detected encoding
|
2012-06-24 18:08:20 +00:00
|
|
|
|
"""
|
2012-07-03 22:26:54 +00:00
|
|
|
|
# Current font is the font tag we last met.
|
2013-08-31 18:17:38 +00:00
|
|
|
|
font = ''
|
2012-07-03 22:26:54 +00:00
|
|
|
|
# Character encoding is defined inside fonttable.
|
2014-05-02 06:42:17 +00:00
|
|
|
|
# font_table could contain eg '0': u'cp1252'
|
2013-08-31 18:17:38 +00:00
|
|
|
|
font_table = {'': ''}
|
2012-07-03 22:26:54 +00:00
|
|
|
|
# Stack of things to keep track of when entering/leaving groups.
|
|
|
|
|
stack = []
|
|
|
|
|
# Whether this group (and all inside it) are "ignorable".
|
|
|
|
|
ignorable = False
|
|
|
|
|
# Number of ASCII characters to skip after an unicode character.
|
|
|
|
|
ucskip = 1
|
|
|
|
|
# Number of ASCII characters left to skip.
|
|
|
|
|
curskip = 0
|
|
|
|
|
# Output buffer.
|
|
|
|
|
out = []
|
2013-09-08 03:50:51 +00:00
|
|
|
|
# Encoded buffer.
|
|
|
|
|
ebytes = bytearray()
|
2012-07-03 22:26:54 +00:00
|
|
|
|
for match in PATTERN.finditer(text):
|
2016-08-13 14:29:12 +00:00
|
|
|
|
iinu, word, arg, hex_, char, brace, tchar = match.groups()
|
2013-09-08 03:50:51 +00:00
|
|
|
|
# \x (non-alpha character)
|
|
|
|
|
if char:
|
|
|
|
|
if char in '\\{}':
|
|
|
|
|
tchar = char
|
|
|
|
|
else:
|
|
|
|
|
word = char
|
|
|
|
|
# Flush encoded buffer to output buffer
|
2016-08-13 14:29:12 +00:00
|
|
|
|
if ebytes and not hex_ and not tchar:
|
2013-09-08 03:50:51 +00:00
|
|
|
|
failed = False
|
|
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
encoding, default_encoding = get_encoding(font, font_table, default_encoding, failed=failed)
|
|
|
|
|
if not encoding:
|
|
|
|
|
return None
|
|
|
|
|
dbytes = ebytes.decode(encoding)
|
|
|
|
|
# Code 5C is a peculiar case with Windows Codepage 932
|
|
|
|
|
if encoding == 'cp932' and '\\' in dbytes:
|
|
|
|
|
dbytes = dbytes.replace('\\', '\u00A5')
|
|
|
|
|
out.append(dbytes)
|
|
|
|
|
ebytes.clear()
|
|
|
|
|
except UnicodeDecodeError:
|
|
|
|
|
failed = True
|
|
|
|
|
else:
|
|
|
|
|
break
|
|
|
|
|
# {}
|
2012-07-03 22:26:54 +00:00
|
|
|
|
if brace:
|
|
|
|
|
curskip = 0
|
2013-08-31 18:17:38 +00:00
|
|
|
|
if brace == '{':
|
2012-07-03 22:26:54 +00:00
|
|
|
|
# Push state
|
|
|
|
|
stack.append((ucskip, ignorable, font))
|
2013-09-08 03:50:51 +00:00
|
|
|
|
elif brace == '}' and len(stack) > 0:
|
2012-07-03 22:26:54 +00:00
|
|
|
|
# Pop state
|
|
|
|
|
ucskip, ignorable, font = stack.pop()
|
|
|
|
|
# \command
|
|
|
|
|
elif word:
|
|
|
|
|
curskip = 0
|
|
|
|
|
if word in DESTINATIONS:
|
|
|
|
|
ignorable = True
|
|
|
|
|
elif word in SPECIAL_CHARS:
|
2013-09-08 03:50:51 +00:00
|
|
|
|
if not ignorable:
|
|
|
|
|
out.append(SPECIAL_CHARS[word])
|
2013-08-31 18:17:38 +00:00
|
|
|
|
elif word == 'uc':
|
2012-07-03 22:26:54 +00:00
|
|
|
|
ucskip = int(arg)
|
2013-09-08 03:50:51 +00:00
|
|
|
|
elif word == 'u':
|
2012-07-03 22:26:54 +00:00
|
|
|
|
c = int(arg)
|
|
|
|
|
if c < 0:
|
|
|
|
|
c += 0x10000
|
2013-09-08 03:50:51 +00:00
|
|
|
|
if not ignorable:
|
|
|
|
|
out.append(chr(c))
|
2012-07-03 22:26:54 +00:00
|
|
|
|
curskip = ucskip
|
2013-08-31 18:17:38 +00:00
|
|
|
|
elif word == 'fonttbl':
|
2012-07-03 22:26:54 +00:00
|
|
|
|
ignorable = True
|
2013-08-31 18:17:38 +00:00
|
|
|
|
elif word == 'f':
|
2012-07-03 22:26:54 +00:00
|
|
|
|
font = arg
|
2013-08-31 18:17:38 +00:00
|
|
|
|
elif word == 'ansicpg':
|
2012-07-03 22:26:54 +00:00
|
|
|
|
font_table[font] = 'cp' + arg
|
2013-09-08 03:50:51 +00:00
|
|
|
|
elif word == 'fcharset' and font not in font_table and arg in CHARSET_MAPPING:
|
|
|
|
|
font_table[font] = CHARSET_MAPPING[arg]
|
|
|
|
|
elif word == 'fldrslt':
|
|
|
|
|
pass
|
|
|
|
|
# \* 'Ignore if not understood' marker
|
|
|
|
|
elif iinu:
|
|
|
|
|
ignorable = True
|
2012-07-03 22:26:54 +00:00
|
|
|
|
# \'xx
|
2016-08-13 14:29:12 +00:00
|
|
|
|
elif hex_:
|
2012-07-03 22:26:54 +00:00
|
|
|
|
if curskip > 0:
|
|
|
|
|
curskip -= 1
|
|
|
|
|
elif not ignorable:
|
2016-08-13 14:29:12 +00:00
|
|
|
|
ebytes.append(int(hex_, 16))
|
2012-07-03 22:26:54 +00:00
|
|
|
|
elif tchar:
|
2016-09-21 03:10:28 +00:00
|
|
|
|
if not ignorable:
|
2013-09-08 03:50:51 +00:00
|
|
|
|
ebytes += tchar.encode()
|
2016-09-21 03:10:28 +00:00
|
|
|
|
if len(ebytes) >= curskip:
|
|
|
|
|
ebytes = ebytes[curskip:]
|
|
|
|
|
else:
|
|
|
|
|
curskip -= len(ebytes)
|
|
|
|
|
ebytes = ""
|
2013-08-31 18:17:38 +00:00
|
|
|
|
text = ''.join(out)
|
2012-07-03 22:26:54 +00:00
|
|
|
|
return text, default_encoding
|
|
|
|
|
|
2013-06-14 20:20:26 +00:00
|
|
|
|
|
|
|
|
|
def delete_song(song_id, song_plugin):
|
|
|
|
|
"""
|
2013-12-31 22:56:02 +00:00
|
|
|
|
Deletes a song from the database. Media files associated to the song are removed prior to the deletion of the song.
|
2013-06-14 20:20:26 +00:00
|
|
|
|
|
2014-03-06 20:40:08 +00:00
|
|
|
|
:param song_id: The ID of the song to delete.
|
|
|
|
|
:param song_plugin: The song plugin instance.
|
2013-06-14 20:20:26 +00:00
|
|
|
|
"""
|
2013-12-31 22:56:02 +00:00
|
|
|
|
save_path = ''
|
2013-06-14 20:20:26 +00:00
|
|
|
|
media_files = song_plugin.manager.get_all_objects(MediaFile, MediaFile.song_id == song_id)
|
|
|
|
|
for media_file in media_files:
|
|
|
|
|
try:
|
2017-09-30 20:16:30 +00:00
|
|
|
|
media_file.file_path.unlink()
|
2013-12-31 22:56:02 +00:00
|
|
|
|
except OSError:
|
2017-09-30 20:16:30 +00:00
|
|
|
|
log.exception('Could not remove file: {name}'.format(name=media_file.file_path))
|
2013-06-14 20:20:26 +00:00
|
|
|
|
try:
|
2017-09-30 20:16:30 +00:00
|
|
|
|
save_path = AppLocation.get_section_data_path(song_plugin.name) / 'audio' / str(song_id)
|
|
|
|
|
if save_path.exists():
|
|
|
|
|
save_path.rmdir()
|
2013-06-14 20:20:26 +00:00
|
|
|
|
except OSError:
|
2016-05-27 08:13:14 +00:00
|
|
|
|
log.exception('Could not remove directory: {path}'.format(path=save_path))
|
2013-06-14 20:20:26 +00:00
|
|
|
|
song_plugin.manager.delete_object(Song, song_id)
|
2016-06-07 20:21:21 +00:00
|
|
|
|
|
|
|
|
|
|
2017-08-09 05:02:42 +00:00
|
|
|
|
def transpose_lyrics(lyrics, transpose_value):
|
2016-06-07 20:21:21 +00:00
|
|
|
|
"""
|
2017-08-09 05:02:42 +00:00
|
|
|
|
Transpose lyrics
|
2016-06-07 20:21:21 +00:00
|
|
|
|
|
2017-08-09 05:02:42 +00:00
|
|
|
|
:param lyrics: The lyrics to be transposed
|
|
|
|
|
:param transpose_value: The value to transpose the lyrics with
|
2016-06-07 20:21:21 +00:00
|
|
|
|
:return: The transposed lyrics
|
|
|
|
|
"""
|
2016-06-07 20:53:56 +00:00
|
|
|
|
# Split text by verse delimiter - both normal and optional
|
2018-07-02 20:38:47 +00:00
|
|
|
|
verse_list = re.split(r'(---\[.+?:.+?\]---|\[---\])', lyrics)
|
2016-06-07 20:53:56 +00:00
|
|
|
|
transposed_lyrics = ''
|
2020-01-26 17:12:45 +00:00
|
|
|
|
notation = Registry().get('settings').value('songs/chord notation')
|
2021-07-22 20:03:55 +00:00
|
|
|
|
key = None
|
2016-06-07 20:53:56 +00:00
|
|
|
|
for verse in verse_list:
|
|
|
|
|
if verse.startswith('---[') or verse == '[---]':
|
|
|
|
|
transposed_lyrics += verse
|
|
|
|
|
else:
|
2021-07-22 20:03:55 +00:00
|
|
|
|
transposed_lyric, key = transpose_verse(verse, transpose_value, notation, key)
|
|
|
|
|
transposed_lyrics += transposed_lyric
|
2016-06-07 20:53:56 +00:00
|
|
|
|
return transposed_lyrics
|
|
|
|
|
|
|
|
|
|
|
2021-07-22 20:03:55 +00:00
|
|
|
|
def transpose_verse(verse_text, transpose_value, notation, key):
|
2016-06-07 20:53:56 +00:00
|
|
|
|
"""
|
2017-08-09 05:02:42 +00:00
|
|
|
|
Transpose Verse
|
2016-06-07 20:53:56 +00:00
|
|
|
|
|
2017-08-09 05:02:42 +00:00
|
|
|
|
:param verse_text: The lyrics to be transposed
|
|
|
|
|
:param transpose_value: The value to transpose the lyrics with
|
|
|
|
|
:param notation: which notation to use
|
2016-06-07 20:53:56 +00:00
|
|
|
|
:return: The transposed lyrics
|
|
|
|
|
"""
|
|
|
|
|
if '[' not in verse_text:
|
2021-07-22 20:03:55 +00:00
|
|
|
|
return verse_text, key
|
|
|
|
|
# Split the lyrics based on chord tags, based on this, chords and bass will be treated equally and separately,
|
|
|
|
|
# 6/9 chords should be noted 6-9 or 69 or 6add9
|
2018-07-02 20:38:47 +00:00
|
|
|
|
lyric_list = re.split(r'(\[|\]|/)', verse_text)
|
2016-06-07 20:21:21 +00:00
|
|
|
|
transposed_lyrics = ''
|
2021-08-01 18:18:16 +00:00
|
|
|
|
is_bass = False
|
|
|
|
|
last_chord = None
|
2016-06-07 20:21:21 +00:00
|
|
|
|
in_tag = False
|
|
|
|
|
for word in lyric_list:
|
|
|
|
|
if not in_tag:
|
|
|
|
|
transposed_lyrics += word
|
|
|
|
|
if word == '[':
|
|
|
|
|
in_tag = True
|
|
|
|
|
else:
|
|
|
|
|
if word == ']':
|
|
|
|
|
in_tag = False
|
|
|
|
|
transposed_lyrics += word
|
2021-07-22 20:03:55 +00:00
|
|
|
|
elif word == '/':
|
2021-08-01 18:18:16 +00:00
|
|
|
|
is_bass = True
|
2021-07-22 20:03:55 +00:00
|
|
|
|
transposed_lyrics += word
|
|
|
|
|
elif word == '--}{--':
|
2016-06-07 20:21:21 +00:00
|
|
|
|
transposed_lyrics += word
|
|
|
|
|
else:
|
|
|
|
|
# This MUST be a chord
|
2021-08-01 18:18:16 +00:00
|
|
|
|
transposed_chord, key, last_chord = transpose_chord(word, transpose_value, notation, key, last_chord,
|
2021-08-01 18:54:25 +00:00
|
|
|
|
is_bass)
|
|
|
|
|
is_bass = False
|
2021-07-22 20:03:55 +00:00
|
|
|
|
transposed_lyrics += transposed_chord
|
2016-06-07 20:21:21 +00:00
|
|
|
|
# If still inside a chord tag something is wrong!
|
|
|
|
|
if in_tag:
|
2021-07-22 20:03:55 +00:00
|
|
|
|
return verse_text, key
|
2016-06-07 20:21:21 +00:00
|
|
|
|
else:
|
2021-07-22 20:03:55 +00:00
|
|
|
|
return transposed_lyrics, key
|
2016-06-07 20:21:21 +00:00
|
|
|
|
|
2016-06-08 19:58:30 +00:00
|
|
|
|
|
2021-08-01 18:18:16 +00:00
|
|
|
|
def transpose_chord(chord, transpose_value, notation, key, last_chord, is_bass):
|
2016-06-07 20:21:21 +00:00
|
|
|
|
"""
|
|
|
|
|
Transpose chord according to the notation used.
|
|
|
|
|
NOTE: This function has a javascript equivalent in chords.js - make sure to update both!
|
|
|
|
|
|
|
|
|
|
:param chord: The chord to transpose.
|
|
|
|
|
:param transpose_value: The value the chord should be transposed.
|
|
|
|
|
:param notation: The notation to use when transposing.
|
|
|
|
|
:return: The transposed chord.
|
|
|
|
|
"""
|
|
|
|
|
# See https://en.wikipedia.org/wiki/Musical_note#12-tone_chromatic_scale
|
2019-07-03 13:23:23 +00:00
|
|
|
|
notes_sharp_notation = {
|
|
|
|
|
'german': ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'H'],
|
|
|
|
|
'english': ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'],
|
|
|
|
|
'neo-latin': ['Do', 'Do#', 'Re', 'Re#', 'Mi', 'Fa', 'Fa#', 'Sol', 'Sol#', 'La', 'La#', 'Si']
|
|
|
|
|
}
|
|
|
|
|
notes_flat_notation = {
|
|
|
|
|
'german': ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'A', 'B', 'H'],
|
|
|
|
|
'english': ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'],
|
|
|
|
|
'neo-latin': ['Do', 'Reb', 'Re', 'Mib', 'Fab', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Si']
|
|
|
|
|
}
|
2021-07-22 20:03:55 +00:00
|
|
|
|
scales = {
|
|
|
|
|
'german': {
|
2021-07-22 20:48:10 +00:00
|
|
|
|
'C': ['C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'B', 'H'],
|
|
|
|
|
'Am': ['C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'B', 'H'],
|
|
|
|
|
'C#': ['H#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'G', 'G#', 'A', 'A#', 'H'],
|
|
|
|
|
'A#m': ['H#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'G', 'G#', 'A', 'A#', 'H'],
|
|
|
|
|
'Db': ['C', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'Abb', 'Ab', 'Bb', 'B', 'Cb'],
|
|
|
|
|
'Bm': ['C', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'Abb', 'Ab', 'Bb', 'B', 'Cb'],
|
|
|
|
|
'D': ['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'B', 'H'],
|
|
|
|
|
'Hm': ['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'B', 'H'],
|
|
|
|
|
'D#': ['H#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'A', 'A#', 'H'],
|
|
|
|
|
'H#m': ['H#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'A', 'A#', 'H'],
|
|
|
|
|
'Eb': ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'Bb', 'B', 'Cb'],
|
|
|
|
|
'Cm': ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'Bb', 'B', 'Cb'],
|
|
|
|
|
'E': ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'B', 'H'],
|
|
|
|
|
'C#m': ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'B', 'H'],
|
|
|
|
|
'F': ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'B', 'Cb'],
|
|
|
|
|
'Dm': ['C', 'C#', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'B', 'H'],
|
|
|
|
|
'F#': ['C', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'G', 'G#', 'A', 'A#', 'H'],
|
|
|
|
|
'D#m': ['C', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'G', 'G#', 'A', 'A#', 'H'],
|
|
|
|
|
'Gb': ['Dbb', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'Abb', 'Ab', 'Bb', 'B', 'Cb'],
|
|
|
|
|
'Ebm': ['Dbb', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'Abb', 'Ab', 'Bb', 'B', 'Cb'],
|
|
|
|
|
'G': ['C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'B', 'H'],
|
|
|
|
|
'Em': ['C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'B', 'H'],
|
|
|
|
|
'G#': ['H#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'A', 'A#', 'H'],
|
|
|
|
|
'E#m': ['H#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'A', 'A#', 'H'],
|
|
|
|
|
'Ab': ['C', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'Bb', 'B', 'Cb'],
|
|
|
|
|
'Fm': ['C', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'Bb', 'B', 'Cb'],
|
|
|
|
|
'A': ['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'G#', 'A', 'B', 'H'],
|
|
|
|
|
'F#m': ['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'G#', 'A', 'B', 'H'],
|
|
|
|
|
'A#': ['H#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'Gx', 'A#', 'H'],
|
|
|
|
|
'F##m': ['H#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'Gx', 'A#', 'H'],
|
|
|
|
|
'Fxm': ['H#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'Gx', 'A#', 'H'],
|
|
|
|
|
'B': ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'A', 'B', 'Cb'],
|
|
|
|
|
'Gm': ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'A', 'B', 'Cb'],
|
|
|
|
|
'H': ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'H'],
|
|
|
|
|
'G#m': ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'H'],
|
|
|
|
|
'Cb': ['Dbb', 'Db', 'Ebb', 'Eb', 'Fb', 'Gbb', 'Gb', 'Abb', 'Ab', 'Bb', 'B', 'Cb'],
|
|
|
|
|
'Abm': ['Dbb', 'Db', 'Ebb', 'Eb', 'Fb', 'Gbb', 'Gb', 'Abb', 'Ab', 'Bb', 'B', 'Cb']
|
2021-07-22 20:03:55 +00:00
|
|
|
|
},
|
|
|
|
|
'english': {
|
2021-07-22 20:48:10 +00:00
|
|
|
|
'C': ['C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'Bb', 'B'],
|
|
|
|
|
'Am': ['C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'Bb', 'B'],
|
2021-07-22 22:44:29 +00:00
|
|
|
|
'C#': ['B#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'G', 'G#', 'A', 'A#', 'B'],
|
|
|
|
|
'A#m': ['B#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'G', 'G#', 'A', 'A#', 'B'],
|
2021-07-22 20:48:10 +00:00
|
|
|
|
'Db': ['C', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'Abb', 'Ab', 'Bbb', 'Bb', 'Cb'],
|
2021-07-22 22:52:26 +00:00
|
|
|
|
'Bbm': ['C', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'Abb', 'Ab', 'Bbb', 'Bb', 'Cb'],
|
2021-07-22 20:48:10 +00:00
|
|
|
|
'D': ['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'Bb', 'B'],
|
2021-07-22 22:44:29 +00:00
|
|
|
|
'Bm': ['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'Bb', 'B'],
|
|
|
|
|
'D#': ['B#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'A', 'A#', 'B'],
|
|
|
|
|
'B#m': ['B#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'A', 'A#', 'B'],
|
2021-07-22 20:48:10 +00:00
|
|
|
|
'Eb': ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'Bbb', 'Bb', 'Cb'],
|
|
|
|
|
'Cm': ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'Bbb', 'Bb', 'Cb'],
|
|
|
|
|
'E': ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'Bb', 'B'],
|
|
|
|
|
'C#m': ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'Bb', 'B'],
|
|
|
|
|
'F': ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'Cb'],
|
|
|
|
|
'Dm': ['C', 'C#', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'],
|
|
|
|
|
'F#': ['C', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'G', 'G#', 'A', 'A#', 'B'],
|
|
|
|
|
'D#m': ['C', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'G', 'G#', 'A', 'A#', 'B'],
|
|
|
|
|
'Gb': ['Dbb', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'Abb', 'Ab', 'Bbb', 'Bb', 'Cb'],
|
|
|
|
|
'Ebm': ['Dbb', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'Abb', 'Ab', 'Bbb', 'Bb', 'Cb'],
|
|
|
|
|
'G': ['C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'Bb', 'B'],
|
|
|
|
|
'Em': ['C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'Bb', 'B'],
|
2021-07-22 22:44:29 +00:00
|
|
|
|
'G#': ['B#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'A', 'A#', 'B'],
|
|
|
|
|
'E#m': ['B#', 'C#', 'D', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'A', 'A#', 'B'],
|
2021-07-22 20:48:10 +00:00
|
|
|
|
'Ab': ['C', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'Bbb', 'Bb', 'Cb'],
|
|
|
|
|
'Fm': ['C', 'Db', 'Ebb', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'Bbb', 'Bb', 'Cb'],
|
|
|
|
|
'A': ['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'G#', 'A', 'Bb', 'B'],
|
|
|
|
|
'F#m': ['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'G#', 'A', 'Bb', 'B'],
|
2021-07-22 22:44:29 +00:00
|
|
|
|
'A#': ['B#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'Gx', 'A#', 'B'],
|
|
|
|
|
'F##m': ['B#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'Gx', 'A#', 'B'],
|
|
|
|
|
'Fxm': ['B#', 'C#', 'Cx', 'D#', 'E', 'E#', 'F#', 'Fx', 'G#', 'Gx', 'A#', 'B'],
|
|
|
|
|
'Bb': ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'Cb'],
|
2021-07-22 20:48:10 +00:00
|
|
|
|
'Gm': ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'Cb'],
|
2021-07-22 22:44:29 +00:00
|
|
|
|
'B': ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'],
|
2021-07-22 20:48:10 +00:00
|
|
|
|
'G#m': ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'],
|
|
|
|
|
'Cb': ['Dbb', 'Db', 'Ebb', 'Eb', 'Fb', 'Gbb', 'Gb', 'Abb', 'Ab', 'Bbb', 'Bb', 'Cb'],
|
|
|
|
|
'Abm': ['Dbb', 'Db', 'Ebb', 'Eb', 'Fb', 'Gbb', 'Gb', 'Abb', 'Ab', 'Bbb', 'Bb', 'Cb']
|
2021-07-22 22:44:29 +00:00
|
|
|
|
},
|
|
|
|
|
'neo-latin': {
|
|
|
|
|
'Do': ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Fa#', 'Sol', 'Lab', 'La', 'Sib', 'Si'],
|
|
|
|
|
'Lam': ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Fa#', 'Sol', 'Lab', 'La', 'Sib', 'Si'],
|
|
|
|
|
'Do#': ['Si#', 'Do#', 'Re', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Sol', 'Sol#', 'La', 'La#', 'Si'],
|
|
|
|
|
'La#m': ['Si#', 'Do#', 'Re', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Sol', 'Sol#', 'La', 'La#', 'Si'],
|
|
|
|
|
'Reb': ['Do', 'Reb', 'Mibb', 'Mib', 'Fab', 'Fa', 'Solb', 'Labb', 'Lab', 'Sibb', 'Sib', 'Dob'],
|
2021-07-22 22:52:26 +00:00
|
|
|
|
'Sibm': ['Do', 'Reb', 'Mibb', 'Mib', 'Fab', 'Fa', 'Solb', 'Labb', 'Lab', 'Sibb', 'Sib', 'Dob'],
|
2021-07-22 22:44:29 +00:00
|
|
|
|
'Re': ['Do', 'Do#', 'Re', 'Mib', 'Mi', 'Fa', 'Fa#', 'Sol', 'Lab', 'La', 'Sib', 'Si'],
|
|
|
|
|
'Sim': ['Do', 'Do#', 'Re', 'Mib', 'Mi', 'Fa', 'Fa#', 'Sol', 'Lab', 'La', 'Sib', 'Si'],
|
|
|
|
|
'Re#': ['Si#', 'Do#', 'Dox', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Fax', 'Sol#', 'La', 'La#', 'Si'],
|
|
|
|
|
'Si#m': ['Si#', 'Do#', 'Dox', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Fax', 'Sol#', 'La', 'La#', 'Si'],
|
|
|
|
|
'Mib': ['Do', 'Reb', 'Re', 'Mib', 'Fab', 'Fa', 'Solb', 'Sol', 'Lab', 'Sibb', 'Sib', 'Dob'],
|
|
|
|
|
'Dom': ['Do', 'Reb', 'Re', 'Mib', 'Fab', 'Fa', 'Solb', 'Sol', 'Lab', 'Sibb', 'Sib', 'Dob'],
|
|
|
|
|
'Mi': ['Do', 'Do#', 'Re', 'Re#', 'Mi', 'Fa', 'Fa#', 'Sol', 'Sol#', 'La', 'Sib', 'Si'],
|
|
|
|
|
'Do#m': ['Do', 'Do#', 'Re', 'Re#', 'Mi', 'Fa', 'Fa#', 'Sol', 'Sol#', 'La', 'Sib', 'Si'],
|
|
|
|
|
'Fa': ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Dob'],
|
|
|
|
|
'Rem': ['Do', 'Do#', 'Re', 'Mib', 'Mi', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Si'],
|
|
|
|
|
'Fa#': ['Do', 'Do#', 'Re', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Sol', 'Sol#', 'La', 'La#', 'Si'],
|
|
|
|
|
'Re#m': ['Do', 'Do#', 'Re', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Sol', 'Sol#', 'La', 'La#', 'Si'],
|
|
|
|
|
'Solb': ['Rebb', 'Reb', 'Mibb', 'Mib', 'Fab', 'Fa', 'Solb', 'Labb', 'Lab', 'Sibb', 'Sib', 'Dob'],
|
|
|
|
|
'Mibm': ['Rebb', 'Reb', 'Mibb', 'Mib', 'Fab', 'Fa', 'Solb', 'Labb', 'Lab', 'Sibb', 'Sib', 'Dob'],
|
|
|
|
|
'Sol': ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Fa#', 'Sol', 'Lab', 'La', 'Sib', 'Si'],
|
|
|
|
|
'Mim': ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Fa#', 'Sol', 'Lab', 'La', 'Sib', 'Si'],
|
|
|
|
|
'Sol#': ['Si#', 'Do#', 'Re', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Fax', 'Sol#', 'La', 'La#', 'Si'],
|
|
|
|
|
'Mi#m': ['Si#', 'Do#', 'Re', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Fax', 'Sol#', 'La', 'La#', 'Si'],
|
|
|
|
|
'Lab': ['Do', 'Reb', 'Mibb', 'Mib', 'Fab', 'Fa', 'Solb', 'Sol', 'Lab', 'Sibb', 'Sib', 'Dob'],
|
|
|
|
|
'Fam': ['Do', 'Reb', 'Mibb', 'Mib', 'Fab', 'Fa', 'Solb', 'Sol', 'Lab', 'Sibb', 'Sib', 'Dob'],
|
|
|
|
|
'La': ['Do', 'Do#', 'Re', 'Mib', 'Mi', 'Fa', 'Fa#', 'Sol', 'Sol#', 'La', 'Sib', 'Si'],
|
|
|
|
|
'Fa#m': ['Do', 'Do#', 'Re', 'Mib', 'Mi', 'Fa', 'Fa#', 'Sol', 'Sol#', 'La', 'Sib', 'Si'],
|
|
|
|
|
'La#': ['Si#', 'Do#', 'Dox', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Fax', 'Sol#', 'Solx', 'La#', 'Si'],
|
|
|
|
|
'Fa##m': ['Si#', 'Do#', 'Dox', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Fax', 'Sol#', 'Solx', 'La#', 'Si'],
|
|
|
|
|
'Faxm': ['Si#', 'Do#', 'Dox', 'Re#', 'Mi', 'Mi#', 'Fa#', 'Fax', 'Sol#', 'Solx', 'La#', 'Si'],
|
|
|
|
|
'Sib': ['Do', 'Reb', 'Re', 'Mib', 'Fab', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Dob'],
|
|
|
|
|
'Solm': ['Do', 'Reb', 'Re', 'Mib', 'Fab', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Dob'],
|
|
|
|
|
'Si': ['Do', 'Do#', 'Re', 'Re#', 'Mi', 'Fa', 'Fa#', 'Sol', 'Sol#', 'La', 'La#', 'Si'],
|
|
|
|
|
'Sol#m': ['Do', 'Do#', 'Re', 'Re#', 'Mi', 'Fa', 'Fa#', 'Sol', 'Sol#', 'La', 'La#', 'Si'],
|
|
|
|
|
'Dob': ['Rebb', 'Reb', 'Mibb', 'Mib', 'Fab', 'Solbb', 'Solb', 'Labb', 'Lab', 'Sibb', 'Sib', 'Dob'],
|
|
|
|
|
'Labm': ['Rebb', 'Reb', 'Mibb', 'Mib', 'Fab', 'Solbb', 'Solb', 'Labb', 'Lab', 'Sibb', 'Sib', 'Dob']
|
2021-07-22 20:03:55 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-08-01 18:18:16 +00:00
|
|
|
|
note_numbers = {
|
2021-07-22 20:03:55 +00:00
|
|
|
|
'german': {
|
2021-07-22 20:48:10 +00:00
|
|
|
|
'C': 0, 'H#': 0, 'B##': 0, 'Bx': 0, 'Dbb': 0,
|
|
|
|
|
'C#': 1, 'Db': 1,
|
|
|
|
|
'D': 2, 'C##': 2, 'Cx': 2, 'Ebb': 2,
|
|
|
|
|
'D#': 3, 'Eb': 3,
|
|
|
|
|
'E': 4, 'D##': 4, 'Dx': 4, 'Fb': 4,
|
|
|
|
|
'F': 5, 'E#': 5, 'Gbb': 5,
|
|
|
|
|
'F#': 6, 'Gb': 6,
|
|
|
|
|
'G': 7, 'F##': 7, 'Fx': 7, 'Abb': 7,
|
|
|
|
|
'G#': 8, 'Ab': 8,
|
|
|
|
|
'A': 9, 'G##': 9, 'Gx': 9, 'Bb': 9, 'Hbb': 9,
|
|
|
|
|
'B': 10, 'A#': 10, 'Hb': 10,
|
|
|
|
|
'H': 11, 'B#': 11, 'A##': 11, 'Ax': 11, 'Cb': 11
|
2021-07-22 20:03:55 +00:00
|
|
|
|
},
|
|
|
|
|
'english': {
|
2021-07-22 20:48:10 +00:00
|
|
|
|
'C': 0, 'B#': 0, 'Dbb': 0,
|
|
|
|
|
'C#': 1, 'Db': 1, 'B##': 1, 'Bx': 1,
|
|
|
|
|
'D': 2, 'C##': 2, 'Cx': 2, 'Ebb': 2,
|
2021-07-22 22:44:29 +00:00
|
|
|
|
'D#': 3, 'Eb': 3, 'Fbb': 3,
|
2021-07-22 20:48:10 +00:00
|
|
|
|
'E': 4, 'D##': 4, 'Dx': 4, 'Fb': 4,
|
|
|
|
|
'F': 5, 'E#': 5, 'Gbb': 5,
|
2021-07-22 22:44:29 +00:00
|
|
|
|
'F#': 6, 'Gb': 6, 'E##': 6, 'Ex': 6,
|
2021-07-22 20:48:10 +00:00
|
|
|
|
'G': 7, 'F##': 7, 'Fx': 7, 'Abb': 7,
|
|
|
|
|
'G#': 8, 'Ab': 8,
|
|
|
|
|
'A': 9, 'G##': 9, 'Gx': 9, 'Bbb': 9,
|
2021-07-22 22:44:29 +00:00
|
|
|
|
'Bb': 10, 'A#': 10, 'Cbb': 10,
|
2021-07-22 20:48:10 +00:00
|
|
|
|
'B': 11, 'A##': 11, 'Ax': 11, 'Cb': 11
|
2021-07-22 22:44:29 +00:00
|
|
|
|
},
|
|
|
|
|
'neo-latin': {
|
|
|
|
|
'Do': 0, 'Si#': 0, 'Rebb': 0,
|
|
|
|
|
'Do#': 1, 'Reb': 1, 'Si##': 1, 'Six': 1,
|
|
|
|
|
'Re': 2, 'Do##': 2, 'Dox': 2, 'Mibb': 2,
|
|
|
|
|
'Re#': 3, 'Mib': 3, 'Fabb': 3,
|
|
|
|
|
'Mi': 4, 'Re##': 4, 'Rex': 4, 'Fab': 4,
|
|
|
|
|
'Fa': 5, 'Mi#': 5, 'Solbb': 5,
|
|
|
|
|
'Fa#': 6, 'Solb': 6, 'Mi##': 6, 'Mix': 6,
|
|
|
|
|
'Sol': 7, 'Fa##': 7, 'Fax': 7, 'Labb': 7,
|
|
|
|
|
'Sol#': 8, 'Lab': 8,
|
|
|
|
|
'La': 9, 'Sol##': 9, 'Solx': 9, 'Sibb': 9,
|
|
|
|
|
'Sib': 10, 'La#': 10, 'Dobb': 10,
|
|
|
|
|
'Si': 11, 'La##': 11, 'Lax': 11, 'Dob': 11
|
2021-07-22 20:03:55 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
chord = chord.replace('♭', 'b').replace('♯', '#')
|
2016-06-07 20:21:21 +00:00
|
|
|
|
transposed_chord = ''
|
2021-07-22 20:03:55 +00:00
|
|
|
|
minor = ''
|
2021-08-01 18:18:16 +00:00
|
|
|
|
is_key_change_chord = False
|
2016-06-07 20:21:21 +00:00
|
|
|
|
notes_sharp = notes_sharp_notation[notation]
|
|
|
|
|
notes_flat = notes_flat_notation[notation]
|
2021-07-22 20:03:55 +00:00
|
|
|
|
notes_preferred = ['b', '#', '#', 'b', '#', 'b', '#', '#', 'b', '#', 'b', '#']
|
|
|
|
|
if chord and chord[0] == '(':
|
|
|
|
|
transposed_chord += '('
|
|
|
|
|
if len(chord) > 1:
|
|
|
|
|
chord = chord[1:]
|
|
|
|
|
else:
|
|
|
|
|
chord = ''
|
|
|
|
|
if chord and chord[0] == '=':
|
|
|
|
|
transposed_chord += '='
|
|
|
|
|
if len(chord) > 1:
|
|
|
|
|
chord = chord[1:]
|
2021-08-01 18:18:16 +00:00
|
|
|
|
is_key_change_chord = True
|
2021-07-22 20:03:55 +00:00
|
|
|
|
else:
|
|
|
|
|
chord = ''
|
|
|
|
|
if chord and chord[0] == '|':
|
|
|
|
|
transposed_chord += '|'
|
|
|
|
|
if len(chord) > 1:
|
|
|
|
|
chord = chord[1:]
|
|
|
|
|
else:
|
|
|
|
|
chord = ''
|
|
|
|
|
if len(chord) > 0:
|
|
|
|
|
if notation == 'neo-latin':
|
|
|
|
|
if len(chord) > 2 and chord[0:3].lower() == 'sol':
|
|
|
|
|
note = chord[0:3]
|
|
|
|
|
chord = chord[3:] if len(chord) > 3 else ''
|
|
|
|
|
elif len(chord) > 1:
|
|
|
|
|
note = chord[0:2]
|
|
|
|
|
chord = chord[2:] if len(chord) > 2 else ''
|
|
|
|
|
else:
|
|
|
|
|
note = chord[0]
|
|
|
|
|
chord = chord[1:] if len(chord) > 1 else ''
|
|
|
|
|
while len(chord) > 0 and '#bx'.find(chord[0]) > -1:
|
|
|
|
|
note += chord[0]
|
|
|
|
|
chord = chord[1:] if len(chord) > 0 else ''
|
|
|
|
|
if len(chord) > 0:
|
|
|
|
|
if 'm-'.find(chord[0]) > -1 or (len(chord) > 1 and chord[0:2].lower() == 'mi'):
|
|
|
|
|
minor = chord[0]
|
|
|
|
|
chord = chord[1:] if len(chord) > 1 else ''
|
2016-06-07 20:21:21 +00:00
|
|
|
|
else:
|
2021-07-22 20:03:55 +00:00
|
|
|
|
minor = ''
|
2021-08-01 18:18:16 +00:00
|
|
|
|
note_number = note_numbers[notation][note]
|
2021-07-22 22:14:53 +00:00
|
|
|
|
note_number += transpose_value
|
|
|
|
|
while note_number > 11:
|
|
|
|
|
note_number -= 12
|
|
|
|
|
while note_number < 0:
|
|
|
|
|
note_number += 12
|
2021-08-01 18:18:16 +00:00
|
|
|
|
if is_bass:
|
|
|
|
|
if last_chord:
|
|
|
|
|
note = scales[notation][last_chord][note_number]
|
2021-07-22 22:14:53 +00:00
|
|
|
|
elif key:
|
|
|
|
|
note = scales[notation][key][note_number]
|
2016-06-07 20:21:21 +00:00
|
|
|
|
else:
|
2021-07-22 22:14:53 +00:00
|
|
|
|
note = notes_sharp[note_number] if notes_preferred[note_number] == '#' else notes_flat[note_number]
|
2021-07-22 20:03:55 +00:00
|
|
|
|
else:
|
2021-08-01 18:18:16 +00:00
|
|
|
|
if not key or is_key_change_chord:
|
2021-07-22 22:14:53 +00:00
|
|
|
|
note = notes_sharp[note_number] if notes_preferred[note_number] == '#' else notes_flat[note_number]
|
|
|
|
|
else:
|
|
|
|
|
note = scales[notation][key][note_number]
|
|
|
|
|
transposed_chord += note + minor + chord
|
2021-08-01 18:18:16 +00:00
|
|
|
|
if is_key_change_chord:
|
2021-07-22 20:03:55 +00:00
|
|
|
|
key = note + minor
|
|
|
|
|
else:
|
2021-08-01 18:18:16 +00:00
|
|
|
|
if not is_bass:
|
|
|
|
|
last_chord = note + minor
|
|
|
|
|
return transposed_chord, key, last_chord
|