├── po ├── LINGUAS ├── meson.build └── POTFILES ├── src ├── __init__.py ├── bible.gresource.xml ├── bible.gresource.xml.in ├── tts.py ├── config.py.in ├── Bible_Parser.py ├── Bible_Parser_Base.py ├── path_order.py ├── bible.in ├── Bible_Parser_tsv.py ├── meson.build ├── settings.ui ├── Bible_Parser_xml.py ├── Bible_Parser_spb.py ├── Bible_Parser_sqlite.py ├── mybible_to_markdown_new.py ├── window.ui ├── text_rendering.ui ├── settings.py ├── window.py ├── main.py ├── text_rendering.py ├── Bible.py ├── mybible_to_markdown.py └── Audio_Player.py ├── .gitignore ├── meson_options.txt ├── data ├── net.lugsole.bible_gui.desktop.in.in ├── icons │ ├── meson.build │ ├── net.lugsole.bible_gui-symbolic.svg │ └── net.lugsole.bible_gui.svg ├── net.lugsole.bible_gui.gschema.xml ├── net.lugsole.bible_gui.gschema.xml.in ├── meson.build └── net.lugsole.bible_gui.appdata.xml.in ├── LICENSE ├── meson.build ├── net.lugsole.bible_gui.Devel.json ├── net.lugsole.bible_gui.json └── README.md /po/LINGUAS: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | __pycache__ 3 | *.pyc 4 | -------------------------------------------------------------------------------- /po/meson.build: -------------------------------------------------------------------------------- 1 | i18n.gettext(application_id, preset: 'glib') 2 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option ( 2 | 'profile', 3 | type: 'combo', 4 | choices: [ 5 | 'default', 6 | 'development' 7 | ], 8 | value: 'default' 9 | ) 10 | -------------------------------------------------------------------------------- /po/POTFILES: -------------------------------------------------------------------------------- 1 | data/net.lugsole.bible_gui.desktop.in 2 | data/net.lugsole.bible_gui.appdata.xml.in 3 | data/net.lugsole.bible_gui.gschema.xml 4 | src/window.ui 5 | src/main.py 6 | src/window.py 7 | 8 | -------------------------------------------------------------------------------- /src/bible.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | window.ui 5 | settings.ui 6 | text_rendering.ui 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/bible.gresource.xml.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | window.ui 5 | settings.ui 6 | text_rendering.ui 7 | 8 | 9 | -------------------------------------------------------------------------------- /data/net.lugsole.bible_gui.desktop.in.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Bible@application_name_suffix@ 3 | Icon=@APPLICATION_ID@ 4 | Exec=net.lugsole.bible_gui@PROFILE@ %U 5 | Terminal=false 6 | Type=Application 7 | MimeType=x-scheme-handler/bible; 8 | Categories=Education 9 | StartupNotify=true 10 | X-Purism-FormFactor=Workstation;Mobile; 11 | -------------------------------------------------------------------------------- /src/tts.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from .config import espeak_path 3 | 4 | from gi.repository import GLib 5 | import os 6 | 7 | 8 | def readText(text, lang): 9 | file = os.path.join(GLib.get_tmp_dir(), "out.wav") 10 | # print("tmp file:",file) 11 | subprocess.check_call( 12 | ["espeak", "-v", lang, text, "-w", file]) 13 | return file 14 | -------------------------------------------------------------------------------- /src/config.py.in: -------------------------------------------------------------------------------- 1 | VERSION = '@VERSION@' 2 | pkgdatadir = '@pkgdatadir@' 3 | localedir = '@localedir@' 4 | espeak_path = '@ESPEAK@' 5 | application_id= '@APPLICATION_ID@' 6 | import os 7 | from gi.repository import GLib 8 | user_data_dir = os.path.join(GLib.get_user_data_dir(), application_id, "translations") 9 | 10 | translationdir = '@translationdir@' 11 | default_translation = 'kjv.tsv' -------------------------------------------------------------------------------- /data/icons/meson.build: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | install_data('net.lugsole.bible_gui.svg', 5 | rename: application_id+'.svg', 6 | install_dir: join_paths(get_option('datadir'), 'icons', 'hicolor', 'scalable', 'apps')) 7 | 8 | 9 | install_data('net.lugsole.bible_gui-symbolic.svg', 10 | rename: application_id+'-symbolic.svg', 11 | install_dir: join_paths(get_option('datadir'), 'icons', 'hicolor', 'symbolic', 'apps')) 12 | 13 | -------------------------------------------------------------------------------- /data/net.lugsole.bible_gui.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | "" 6 | Bible file location 7 | 8 | the file to read the bible from 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /data/net.lugsole.bible_gui.gschema.xml.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | "" 6 | Bible file location 7 | 8 | the file to read the bible from 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Bible_Parser.py: -------------------------------------------------------------------------------- 1 | from .Bible_Parser_spb import BibleParserSPB 2 | from .Bible_Parser_sqlite import BibleParserSqLit3 3 | from .Bible_Parser_tsv import BibleParserTSV 4 | from .Bible_Parser_xml import BibleParserXML 5 | from .Bible_Parser_Base import check_extention 6 | 7 | allParsers = [ 8 | BibleParserSPB, 9 | BibleParserSqLit3, 10 | BibleParserTSV, 11 | BibleParserXML] 12 | 13 | 14 | def BibleParser(filename): 15 | for file_type in allParsers: 16 | if check_extention(file_type, filename): 17 | return file_type(filename) 18 | -------------------------------------------------------------------------------- /data/icons/net.lugsole.bible_gui-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Bible_Parser_Base.py: -------------------------------------------------------------------------------- 1 | from .Bible import * 2 | 3 | 4 | class BibleParserBase: 5 | name = "Base" 6 | fileEndings = [] 7 | 8 | def __init__(self, file_name): 9 | self.file_name = file_name 10 | self.bible = Bible() 11 | 12 | def isValidFileEnding(self, file_name): 13 | for ending in self.fileEndings: 14 | if '.' + ending in file_name: 15 | return True 16 | return False 17 | 18 | def getParserName(self): 19 | return self.name 20 | 21 | def getParserEndings(self): 22 | return self.fileEndings 23 | 24 | def loadAll(self): 25 | pass 26 | 27 | def loadInfo(self): 28 | pass 29 | 30 | 31 | def check_extention(type_class, file_name): 32 | return type_class.isValidFileEnding(type_class, file_name) 33 | -------------------------------------------------------------------------------- /src/path_order.py: -------------------------------------------------------------------------------- 1 | from .config import pkgdatadir, application_id, user_data_dir, translationdir 2 | import os 3 | 4 | 5 | def find_file_on_path(find_file): 6 | path = [user_data_dir, translationdir] 7 | for i in path: 8 | if os.path.isfile(os.path.join(i, find_file)): 9 | return i 10 | 11 | 12 | def walk_files_on_path(): 13 | path = [user_data_dir, translationdir] 14 | all_files_hash = {} 15 | for i in path: 16 | 17 | for root, dirs, files in os.walk(i): 18 | for filename in files: 19 | full_path = os.path.join(root, filename) 20 | rel_path = os.path.relpath(full_path, i) 21 | if not os.path.relpath(rel_path, i) in all_files_hash: 22 | all_files_hash[rel_path] = i 23 | 24 | return all_files_hash 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Lugsole 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('bible_gui', 2 | version: '0.1.5', 3 | meson_version: '>= 0.50.0', 4 | default_options: [ 'warning_level=2', 5 | ], 6 | ) 7 | 8 | i18n = import('i18n') 9 | gnome = import('gnome') 10 | 11 | version_date='2020-09-26' 12 | 13 | 14 | if get_option('profile') == 'development' 15 | profile = '.Devel' 16 | name_suffix = ' Development' 17 | file_name_suffix = '-devel' 18 | path_suffix = '/devel' 19 | vcs_tag = run_command('git', 'rev-parse', '--short', 'HEAD').stdout().strip() 20 | if vcs_tag == '' 21 | version_suffix = '-devel' 22 | else 23 | version_suffix = '-@0@'.format (vcs_tag) 24 | endif 25 | else 26 | profile = '' 27 | name_suffix = '' 28 | file_name_suffix = '' 29 | path_suffix = '' 30 | version_suffix = '' 31 | endif 32 | 33 | application_name_suffix = name_suffix 34 | application_id = 'net.lugsole.bible_gui@0@'.format(profile) 35 | application_path = '/net/lugsole/bible_gui@0@'.format(path_suffix) 36 | 37 | translationdir = join_paths(get_option('prefix'), get_option('datadir'), application_id, 'translations') 38 | 39 | subdir('data') 40 | subdir('src') 41 | subdir('po') 42 | 43 | meson.add_install_script('build-aux/meson/postinstall.py') 44 | -------------------------------------------------------------------------------- /src/bible.in: -------------------------------------------------------------------------------- 1 | #!@PYTHON@ 2 | 3 | 4 | import os 5 | import sys 6 | import signal 7 | import gettext 8 | import locale 9 | 10 | VERSION = '@VERSION@' 11 | pkgdatadir = '@pkgdatadir@' 12 | LOCALE_DIR = '@localedir@' 13 | 14 | sys.path.insert(1, pkgdatadir) 15 | signal.signal(signal.SIGINT, signal.SIG_DFL) 16 | 17 | def set_internationalization(): 18 | """Sets application internationalization.""" 19 | try: 20 | locale.bindtextdomain('@APPLICATION_ID@', LOCALE_DIR) 21 | locale.textdomain('@APPLICATION_ID@') 22 | except AttributeError as e: 23 | # Python built without gettext support does not have 24 | # bindtextdomain() and textdomain(). 25 | print( 26 | "Could not bind the gettext translation domain. Some" 27 | " translations will not work. Error:\n{}".format(e)) 28 | 29 | gettext.bindtextdomain('@APPLICATION_ID@', LOCALE_DIR) 30 | gettext.textdomain('@APPLICATION_ID@') 31 | 32 | if __name__ == '__main__': 33 | import gi 34 | 35 | from gi.repository import Gio 36 | set_internationalization() 37 | resource = Gio.Resource.load(os.path.join(pkgdatadir, 'bible.gresource')) 38 | resource._register() 39 | from bible import main 40 | sys.exit(main.main(VERSION)) 41 | -------------------------------------------------------------------------------- /src/Bible_Parser_tsv.py: -------------------------------------------------------------------------------- 1 | from .Bible import Book, Verse 2 | from .Bible_Parser_Base import BibleParserBase 3 | import os 4 | 5 | 6 | class BibleParserTSV(BibleParserBase): 7 | name = "TSV" 8 | fileEndings = ["tsv"] 9 | 10 | def __init__(self, file_name): 11 | BibleParserBase.__init__(self, file_name) 12 | 13 | def loadInfo(self): 14 | 15 | with open(self.file_name) as myFile: 16 | lines = myFile.readlines() 17 | i = 0 18 | while i < len(lines): 19 | i += self.prosessVerseData(lines, i) 20 | self.bible.translationAbbreviation = os.path.basename(self.file_name) 21 | self.bible.translationName = os.path.basename(self.file_name)[:-4] 22 | 23 | def loadAll(self): 24 | 25 | with open(self.file_name) as myFile: 26 | lines = myFile.readlines() 27 | i = 0 28 | while i < len(lines): 29 | i += self.prosessVerseData(lines, i) 30 | self.bible.translationAbbreviation = os.path.basename(self.file_name) 31 | 32 | def prosessVerseData(self, lines, index): 33 | data = lines[index].replace("\n", "").split('\t') 34 | if len(self.bible.getBooksChapterNames(data[0])) == 0: 35 | self.bible.append(Book(data[0], int(data[2]), data[1])) 36 | self.bible.addVerse( 37 | Verse(int(data[2]), int(data[3]), int(data[4]), data[5])) 38 | return 1 39 | -------------------------------------------------------------------------------- /net.lugsole.bible_gui.Devel.json: -------------------------------------------------------------------------------- 1 | { 2 | "app-id" : "net.lugsole.bible_gui.Devel", 3 | "runtime" : "org.gnome.Platform", 4 | "runtime-version" : "46", 5 | "sdk" : "org.gnome.Sdk", 6 | "command" : "net.lugsole.bible_gui.Devel", 7 | "finish-args" : [ 8 | "--device=dri", 9 | "--share=ipc", 10 | "--socket=fallback-x11", 11 | "--socket=wayland", 12 | "--socket=pulseaudio", 13 | "--own-name=org.mpris.MediaPlayer2.Bible" 14 | ], 15 | "cleanup" : [ 16 | "/include", 17 | "/lib/pkgconfig", 18 | "/man", 19 | "/share/doc", 20 | "/share/gtk-doc", 21 | "/share/man", 22 | "/share/pkgconfig", 23 | "*.la", 24 | "*.a" 25 | ], 26 | "modules" : [ 27 | { 28 | "name" : "espeak", 29 | "no-parallel-make" : true, 30 | "sources" : [ 31 | { 32 | "type" : "git", 33 | "url" : "https://github.com/espeak-ng/espeak-ng", 34 | "commit" : "a25849e4d54a23ae1294b129d5696ca7e144ec8b" 35 | } 36 | ] 37 | }, 38 | { 39 | "name" : "bible", 40 | "builddir" : true, 41 | "buildsystem" : "meson", 42 | "config-opts":["-Dprofile=development"], 43 | "sources" : [ 44 | { 45 | "type" : "git", 46 | "url" : "https://github.com/Lugsole/net.lugsole.bible_gui" 47 | } 48 | ] 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), application_id) 2 | moduledir = join_paths(pkgdatadir, 'bible') 3 | 4 | gnome.compile_resources('bible', 5 | 'bible.gresource.xml', 6 | gresource_bundle: true, 7 | install: true, 8 | install_dir: pkgdatadir, 9 | ) 10 | 11 | python = import('python') 12 | 13 | conf = configuration_data() 14 | conf.set('PYTHON', python.find_installation('python3').path()) 15 | conf.set('ESPEAK', find_program('espeak').path()) 16 | conf.set('VERSION', meson.project_version() + version_suffix) 17 | conf.set('localedir', join_paths(get_option('prefix'), get_option('localedir'))) 18 | conf.set('pkgdatadir', pkgdatadir) 19 | conf.set('APPLICATION_ID', application_id) 20 | conf.set('translationdir', translationdir) 21 | 22 | configure_file( 23 | input: 'bible.in', 24 | output: 'net.lugsole.bible_gui'+profile, 25 | configuration: conf, 26 | install: true, 27 | install_dir: get_option('bindir') 28 | ) 29 | 30 | configure_file( 31 | input: 'config.py.in', 32 | output: 'config.py', 33 | configuration: conf, 34 | install: true, 35 | install_dir: moduledir 36 | ) 37 | 38 | bible_sources = [ 39 | '__init__.py', 40 | 'main.py', 41 | 'window.py', 42 | 'tts.py', 43 | 'Bible_Parser.py', 44 | 'Bible_Parser_Base.py', 45 | 'Bible_Parser_sqlite.py', 46 | 'Bible_Parser_spb.py', 47 | 'Bible_Parser_xml.py', 48 | 'Bible.py', 49 | 'Audio_Player.py', 50 | 'Bible_Parser_tsv.py', 51 | 'settings.py', 52 | 'mybible_to_markdown.py', 53 | 'mybible_to_markdown_new.py', 54 | 'path_order.py', 55 | 'text_rendering.py', 56 | ] 57 | 58 | libhandy_dep = dependency('libadwaita-1', 59 | version: '>=0.84.0' 60 | ) 61 | 62 | 63 | install_data(bible_sources, install_dir: moduledir) 64 | -------------------------------------------------------------------------------- /net.lugsole.bible_gui.json: -------------------------------------------------------------------------------- 1 | { 2 | "app-id" : "net.lugsole.bible_gui", 3 | "runtime" : "org.gnome.Platform", 4 | "runtime-version" : "45", 5 | "sdk" : "org.gnome.Sdk", 6 | "command" : "net.lugsole.bible_gui", 7 | "finish-args" : [ 8 | "--device=dri", 9 | "--share=ipc", 10 | "--socket=fallback-x11", 11 | "--socket=wayland", 12 | "--socket=pulseaudio", 13 | "--own-name=org.mpris.MediaPlayer2.Bible" 14 | ], 15 | "cleanup" : [ 16 | "/include", 17 | "/lib/pkgconfig", 18 | "/man", 19 | "/share/doc", 20 | "/share/gtk-doc", 21 | "/share/man", 22 | "/share/pkgconfig", 23 | "*.la", 24 | "*.a" 25 | ], 26 | "modules" : [ 27 | { 28 | "name" : "espeak", 29 | "no-parallel-make" : true, 30 | "sources" : [ 31 | { 32 | "type" : "git", 33 | "url" : "https://github.com/espeak-ng/espeak-ng", 34 | "commit" : "a25849e4d54a23ae1294b129d5696ca7e144ec8b" 35 | } 36 | ] 37 | }, 38 | { 39 | "name" : "gb", 40 | "buildsystem": "simple", 41 | "build-commands": ["install -D GB.SQLite3 /app/share/net.lugsole.bible_gui/translations/GB.SQLite3"], 42 | "sources" : [ 43 | { 44 | "type" : "file", 45 | "path" : "GB.SQLite3" 46 | } 47 | ] 48 | }, 49 | { 50 | "name" : "bible", 51 | "builddir" : true, 52 | "buildsystem" : "meson", 53 | "sources" : [ 54 | { 55 | "type" : "git", 56 | "url" : "https://github.com/Lugsole/net.lugsole.bible_gui" 57 | } 58 | ] 59 | } 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /src/settings.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 44 | 45 | -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | 2 | 3 | conf = configuration_data() 4 | conf.set('version', meson.project_version()) 5 | conf.set('version_date', version_date) 6 | conf.set('APPLICATION_ID', application_id) 7 | conf.set('application_path', application_path) 8 | conf.set('file_name_suffix', file_name_suffix) 9 | conf.set('application_name_suffix', application_name_suffix) 10 | conf.set('PROFILE', profile) 11 | 12 | desktop_file = i18n.merge_file( 13 | input: configure_file( 14 | output: application_id+ '.desktop.in', 15 | input: 'net.lugsole.bible_gui.desktop.in.in', 16 | configuration: conf), 17 | output: application_id+'.desktop', 18 | type: 'desktop', 19 | po_dir: '../po', 20 | install: true, 21 | install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'applications') 22 | ) 23 | 24 | desktop_utils = find_program('desktop-file-validate', required: false) 25 | if desktop_utils.found() 26 | test('Validate desktop file', desktop_utils, 27 | args: [desktop_file] 28 | ) 29 | endif 30 | appstream_file = configure_file( 31 | input: 'net.lugsole.bible_gui.appdata.xml.in', 32 | output: application_id+'.appdata.xml', 33 | configuration: conf, 34 | install: true, 35 | install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'metainfo') 36 | ) 37 | 38 | appstream_util = find_program('appstream-util', required: false) 39 | if appstream_util.found() 40 | test('Validate appstream file', appstream_util, 41 | args: ['validate', appstream_file] 42 | ) 43 | endif 44 | 45 | schema_src = 'net.lugsole.bible_gui.gschema.xml' 46 | schema_src = configure_file( 47 | input: 'net.lugsole.bible_gui.gschema.xml.in', 48 | output: application_id+'.gschema.xml', 49 | configuration: conf, 50 | install: true, 51 | install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'glib-2.0/schemas') 52 | ) 53 | compiled = gnome.compile_schemas(build_by_default: true, 54 | depend_files: schema_src) 55 | 56 | install_data(schema_src, 57 | rename: application_id+'.gschema.xml', 58 | install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'glib-2.0/schemas') 59 | ) 60 | 61 | 62 | install_data('kjv.tsv', 63 | install_dir: translationdir 64 | ) 65 | 66 | 67 | subdir('icons') 68 | -------------------------------------------------------------------------------- /data/icons/net.lugsole.bible_gui.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # net.lugsole.bible_gui 2 | 3 | ## File formats 4 | This app supports multiple different bible formats to varying extents. 5 | 6 | * **SPB** -- These are soft projector bible files. 7 | * **SQLite3** -- as designed by MyBible 8 | * **tsv** -- These files have 6 columns with the data separated by 9 | * **xml** -- This file 10 | 11 | These files should be installed in `XDG_DATA_HOME` which would likely be at `$HOME/.var/app/net.lugsole.bible_gui/data`. 12 | In settings, there is an option to add a new translation file to the list. 13 | 14 | ToDo: 15 | 16 | * Different text rendering engines 17 | 18 | settings to add: 19 | 20 | * Select rendering engines 21 | 22 | ## About the file formats 23 | This app supports multiple different bible formats to varying extents. 24 | 25 | ### SPB 26 | These files have 4 main fields 27 | 28 | * **spDataVersion** This is the data type version number. This is 1 in most cases. 29 | * **Title** This it the title of the bible translation. 30 | * **Abbreviation** This is the bible's abbreviation 31 | * **Information** This usually contains information about copyright. 32 | * **RightToLeft** The lines that follow this field contains a list of the books of the Bible. 33 | 34 | 35 | Then after all the `----`, each line represents a bible verse. 36 | The first piece of data would be a string that represents where this verse belongs in the bible. 37 | The next piece of data is the boon number, the chapter number, then the verse number, then the text of that verse. 38 | All the pieces of data in the line are separated by a tab. 39 | 40 | 41 | ### SQLite3 42 | These files contain multiple tables of which the app only uses two of the tables. 43 | The data they have for each verse is in an HTML kind of format. 44 | This means that there is a lot of data that could be parsed. 45 | With that being said, not all of the data currently is being processed. 46 | 47 | ### tsv -- These files have 6 columns with the data separated by 48 | These files ate a giant table of bible verses. It thas 5 columns 49 | 50 | * Book name 51 | * Book name short 52 | * Book number 53 | * Chapter 54 | * Verse 55 | * Verse text 56 | 57 | ### xml 58 | 59 | These files contain a root node `XMLBIBLE`. 60 | This has many children named `BIBLEBOOK` 61 | These represent the different books in the Bible. 62 | These books have attributes `bname` and `bnumber`. 63 | These are the book mane and book number respectively. 64 | The books all have children called `CHAPTER`, which has attributes named `cnumber` 65 | The chapters all have children called `VERS`, which has attributes named `vnumber`. 66 | The data inside this would be the text of that verse. -------------------------------------------------------------------------------- /data/net.lugsole.bible_gui.appdata.xml.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | @APPLICATION_ID@.desktop 4 | FSFAP 5 | MIT 6 | 7 | Bible@application_name_suffix@ 8 | Bible app that supports multiple translations 9 | 10 | 11 | Bible app on the Chapter selection sccreen 12 | http://flatpak.lugsole.net/bible_chapter.png 13 | 14 | 15 | 16 |

17 | This is a basic bible app that supports many different translations 18 |

19 |

20 | This app supports multple different bible file formats. 21 |

22 |
    23 |
  • SoftProjector - spb
  • 24 |
  • MyBible - SQLite3
  • 25 |
  • tsv
  • 26 |
  • Zefania XML bible - xml
  • 27 |
28 |
29 | 30 | keyboard 31 | pointing 32 | touch 33 | 34 | 35 | Bible 36 | Bibles 37 | Linux 38 | Book 39 | 40 | net.lugsole.bible_gui.desktop 41 | 42 | 360 43 | 44 | 45 | #799dff 46 | #1757e2 47 | 48 | https://lugsole.net 49 | 50 | Lugsole 51 | 52 | 53 | 54 | 55 |
    56 |
  • Updated settings screen
  • 57 |
  • sqlite can now render like a normal Bible
  • 58 |
59 |
60 |
61 | 62 | 63 |
    64 |
  • Replaced GTK3 with GTK4, replaced libhandy with libadwaita
  • 65 |
  • Fixed search
  • 66 |
  • Added intagration to system media player.
  • 67 |
68 |
69 |
70 | 71 | 72 |
    73 |
  • Added supports Zefania XML based bible files.
  • 74 |
  • New Bible translation file importer.
  • 75 |
76 |
77 |
78 | 79 | 80 |
81 |
82 | -------------------------------------------------------------------------------- /src/Bible_Parser_xml.py: -------------------------------------------------------------------------------- 1 | from .Bible import Book, Verse 2 | from .Bible_Parser_Base import BibleParserBase 3 | import xml.etree.ElementTree as ET 4 | import os.path 5 | 6 | 7 | data = ["Genesis", 8 | "Exodus", 9 | "Leviticus", 10 | "Numbers", 11 | "Deuteronomy", 12 | "Joshua", 13 | "Judges", 14 | "Ruth", 15 | "1 Samuel", 16 | "2 Samuel", 17 | "1 Kings", 18 | "2 Kings", 19 | "1 Chronicles", 20 | "2 Chronicles", 21 | "Ezra", 22 | "Nehemiah", 23 | "Esther", 24 | "Job", 25 | "Psalms", 26 | "Proverbs", 27 | "Ecclesiastes", 28 | "Song of Songs", 29 | "Isaiah", 30 | "Jeremiah", 31 | "Lamentations", 32 | "Ezekiel", 33 | "Daniel", 34 | "Hosea", 35 | "Joel", 36 | "Amos", 37 | "Obadiah", 38 | "Jonah", 39 | "Micah", 40 | "Nahum", 41 | "Habakkuk", 42 | "Zephaniah", 43 | "Haggai", 44 | "Zechariah", 45 | "Malachi", 46 | "Matthew", 47 | "Mark", 48 | "Luke", 49 | "John", 50 | "Acts", 51 | "Romans", 52 | "1 Corinthians", 53 | "2 Corinthians", 54 | "Galatians", 55 | "Ephesians", 56 | "Philippians", 57 | "Colossians", 58 | "1 Thessalonians", 59 | "2 Thessalonians", 60 | "1 Timothy", 61 | "2 Timothy", 62 | "Titus", 63 | "Philemon", 64 | "Hebrews", 65 | "James", 66 | "1 Peter", 67 | "2 Peter", 68 | "1 John", 69 | "2 John", 70 | "3 John", 71 | "Jude", 72 | "Revelation"] 73 | 74 | class BibleParserXML(BibleParserBase): 75 | name = "XML" 76 | fileEndings = ["xml"] 77 | 78 | def __init__(self, file_name): 79 | BibleParserBase.__init__(self, file_name) 80 | 81 | def loadInfo(self): 82 | self.bible.translationAbbreviation = os.path.basename(self.file_name) 83 | self.bible.translationName = os.path.basename(self.file_name) 84 | tree = ET.ElementTree(file=self.file_name) 85 | root = tree.getroot() 86 | if "biblename" in root.attrib: 87 | self.bible.translationName = root.attrib['biblename'] 88 | for info_data in root.iter('INFORMATION'): 89 | #print("info_data", info_data) 90 | for info_data_sub in info_data: 91 | #print("info_data_sub", info_data_sub) 92 | #print("info_data_sub.tag", info_data_sub.tag) 93 | #print("info_data_sub.text", info_data_sub.text) 94 | if info_data_sub.tag == "identifier": 95 | self.bible.translationAbbreviation = info_data_sub.text 96 | elif info_data_sub.tag == "title": 97 | self.bible.translationName = info_data_sub.text 98 | elif info_data_sub.tag == "description": 99 | self.bible.translationInformation = info_data_sub.text 100 | 101 | def loadAll(self): 102 | tree = ET.ElementTree(file=self.file_name) 103 | books = tree.getroot() 104 | #print(len(data)) 105 | for book in books.iter('BIBLEBOOK'): 106 | b_number = int(book.attrib["bnumber"]) 107 | b_name = None 108 | if "bname" in book.attrib: 109 | b_name = book.attrib["bname"] 110 | if b_name is None: 111 | b_name = data[b_number-1] 112 | self.bible.append(Book(b_name, b_number)) 113 | 114 | for chapter in book: 115 | ch_number = int(chapter.attrib["cnumber"]) 116 | for verse in chapter: 117 | v_number = int(verse.attrib["vnumber"]) 118 | self.bible.addVerse( 119 | Verse(b_number, ch_number, v_number, str(ET.tostring(verse, encoding="unicode", method="text")).strip())) 120 | -------------------------------------------------------------------------------- /src/Bible_Parser_spb.py: -------------------------------------------------------------------------------- 1 | from .Bible import Book, Verse 2 | from .Bible_Parser_Base import BibleParserBase 3 | import os 4 | 5 | class BibleParserSPB(BibleParserBase): 6 | name = "spb" 7 | fileEndings = ["spb"] 8 | book = 0 9 | chapter = 0 10 | verse = 0 11 | 12 | def __init__(self, file_name): 13 | BibleParserBase.__init__(self, file_name) 14 | 15 | def loadInfo(self): 16 | self.bible.translationName = os.path.basename(self.file_name) 17 | with open(self.file_name) as myFile: 18 | lines = [] 19 | for line in myFile: 20 | if line.startswith("--"): 21 | break 22 | lines.append(line) 23 | i = 0 24 | while i < len(lines) and not lines[i].startswith("--"): 25 | i += self.prosessData(lines, i) 26 | 27 | def loadAll(self): 28 | 29 | with open(self.file_name) as myFile: 30 | lines = myFile.readlines() 31 | i = 0 32 | while i < len(lines) and not lines[i].startswith("--"): 33 | 34 | i += self.prosessData(lines, i) 35 | i += 1 36 | while i < len(lines): 37 | i += self.prosessVerseData(lines, i) 38 | 39 | def prosessData(self, lines, index): 40 | line = lines[index].replace("\n", '') 41 | parts = line.split('\t') 42 | if line.startswith("##Title:"): 43 | self.bible.translationName = parts[1] 44 | return 1 45 | elif line.startswith("##Abbreviation:"): 46 | self.bible.translationAbbreviation = parts[1] 47 | return 1 48 | elif line.startswith("##Information:"): 49 | self.bible.translationInformation = parts[1].replace("@%", '\n') 50 | return 1 51 | elif line.startswith("##RightToLeft:"): 52 | offset = 1 53 | while index + offset < len(lines) and not( 54 | lines[index + offset].startswith("##") or 55 | lines[index + offset].startswith("--") 56 | ): 57 | line = lines[index + offset] 58 | parts = line.split('\t') 59 | self.bible.append(Book(parts[1], int(parts[0]))) 60 | offset += 1 61 | return offset 62 | 63 | else: 64 | return 1 65 | 66 | def prosessVerseData(self, lines, index): 67 | data = lines[index].split('\t') 68 | if len(data) == 5: 69 | self.bible.addVerse( 70 | Verse(int(data[1]), 71 | int(data[2]), 72 | int(data[3]), 73 | data[4].rstrip())) 74 | if not( 75 | (self.verse + 76 | 1 == int( 77 | data[3]) and self.chapter == int( 78 | data[2]) and self.book == int( 79 | data[1])) or ( 80 | 1 == int( 81 | data[3]) and self.chapter + 82 | 1 == int( 83 | data[2]) and self.book == int( 84 | data[1])) or ( 85 | 1 == int( 86 | data[3]) and ( 87 | 1 == int( 88 | data[2]) or 0 == int( 89 | data[2])) and self.book + 90 | 1 == int( 91 | data[1]))): 92 | print( 93 | "Unusual transition:", 94 | self.book, 95 | self.chapter, 96 | self.verse) 97 | self.book = int(data[1]) 98 | self.chapter = int(data[2]) 99 | self.verse = int(data[3]) 100 | else: 101 | print("Bad line", data) 102 | return 1 103 | -------------------------------------------------------------------------------- /src/Bible_Parser_sqlite.py: -------------------------------------------------------------------------------- 1 | from .Bible import Book, Verse, Story 2 | import sqlite3 3 | import re 4 | from .Bible_Parser_Base import BibleParserBase 5 | 6 | 7 | class BibleParserSqLit3(BibleParserBase): 8 | name = "SQLite/My Bible" 9 | fileEndings = ["SQLite3", "sqlite"] 10 | 11 | def __init__(self, file_name): 12 | BibleParserBase.__init__(self, file_name) 13 | 14 | def isValidFileEnding(self, filename): 15 | return '.sqlite3' in filename.lower() and '.commentaries.sqlite3' not in filename.lower( 16 | ) and '.dictionary.sqlite3' not in filename.lower() and '.crossreferences.sqlite3' not in filename.lower() 17 | 18 | def loadInfo(self): 19 | conn = sqlite3.connect(self.file_name) 20 | c = conn.cursor() 21 | t = ('detailed_info',) 22 | c.execute('SELECT name, value FROM info where name=?', t) 23 | txt = c.fetchone() 24 | if txt is not None and txt[1] is not None: 25 | self.bible.translationInformation = txt[1] 26 | t = ('description',) 27 | c.execute('SELECT name, value FROM info where name=?', t) 28 | txt = c.fetchone() 29 | if txt is not None or txt[1] is not None: 30 | self.bible.translationName = txt[1] 31 | t = ('language',) 32 | c.execute('SELECT name, value FROM info where name=?', t) 33 | txt = c.fetchone() 34 | if txt is not None and txt[1] is not None: 35 | self.bible.language = txt[1] 36 | 37 | 38 | t = ('right_to_left',) 39 | c.execute('SELECT name, value FROM info where name=?', t) 40 | txt = c.fetchone() 41 | if txt is not None and txt[1] is not None: 42 | self.bible.right_to_left = txt[1].upper() == "TRUE" 43 | #print(self.bible.right_to_left) 44 | 45 | for row in c.execute( 46 | 'SELECT long_name,book_number,short_name FROM books'): 47 | self.bible.append( 48 | Book(row[0].strip(), number=int(row[1]), shortName=row[2])) 49 | 50 | conn.close() 51 | 52 | def loadAll(self): 53 | self.loadInfo() 54 | conn = sqlite3.connect(self.file_name) 55 | c = conn.cursor() 56 | for row in c.execute( 57 | 'SELECT book_number, chapter, verse, text FROM verses'): 58 | verseText = row[3].rstrip() 59 | verseText = verseText.replace('¶', '') 60 | self.bible.addVerse( 61 | Verse(int(row[0]), int(row[1]), int(row[2]), verseText)) 62 | c = conn.cursor() 63 | 64 | listOfTables = c.execute( 65 | """SELECT name FROM sqlite_master WHERE type='table' 66 | AND name='stories'; """).fetchall() 67 | 68 | if listOfTables != []: 69 | #print('stories table found!') 70 | c = conn.cursor() 71 | has_order_if_several = False 72 | for row in c.execute("PRAGMA table_info('stories')").fetchall(): 73 | if row[1] == 'order_if_several': 74 | has_order_if_several = True 75 | if has_order_if_several: 76 | for row in c.execute( 77 | 'select book_number, chapter, verse, order_if_several, title from stories'): 78 | verseText = row[4].rstrip() 79 | 80 | s = Story(int(row[0]), int(row[1]), int( 81 | row[2]), int(row[3]), verseText) 82 | self.bible.addVerse(s) 83 | else: 84 | for row in c.execute( 85 | 'select book_number, chapter, verse, title from stories'): 86 | verseText = row[3].rstrip() 87 | 88 | s = Story(int(row[0]), int(row[1]), int( 89 | row[2]), 0, verseText) 90 | self.bible.addVerse(s) 91 | else: 92 | print('stories table NOT found!') 93 | 94 | conn.close() 95 | 96 | -------------------------------------------------------------------------------- /src/mybible_to_markdown_new.py: -------------------------------------------------------------------------------- 1 | 2 | string = "In the [2]beginning God created the heaven and the earth." 3 | import xml.etree.ElementTree as ET 4 | from xml.etree.ElementTree import XMLParser, ParseError 5 | from xml.etree.ElementTree import Element,tostring 6 | 7 | def convert(element): 8 | new_element = None 9 | if element.tag == "i": 10 | new_element = Element("span") 11 | new_element.attrib["style"] = "italic" 12 | elif element.tag == "J": 13 | new_element = Element("span") 14 | new_element.attrib["foreground"] = "red" 15 | elif element.tag == "text": 16 | new_element = Element("span") 17 | elif element.tag == "h": 18 | new_element = Element("span") 19 | new_element.attrib["size"] = "150%" 20 | new_element.attrib["weight"] = "bold" 21 | elif element.tag == "sup": 22 | new_element = Element("span") 23 | new_element.attrib["size"] = "75%" 24 | new_element.attrib["rise"] = "4000" 25 | elif element.tag == "S": 26 | if element.tail is not None: 27 | return element.tail 28 | else: 29 | return "" 30 | elif element.tag == "f": 31 | if element.tail is not None: 32 | return element.tail 33 | else: 34 | return "" 35 | elif element.tag == "br": 36 | if element.tail is not None: 37 | return "\r" + element.tail 38 | else: 39 | return "\r" 40 | elif element.tag == "pb": 41 | if element.tail is not None: 42 | return "\r" + element.tail 43 | else: 44 | return "\r" 45 | else: 46 | new_element = Element("span") 47 | new_element.tail = element.tail 48 | new_element.text = element.text 49 | for i in element: 50 | ret = convert(i) 51 | if isinstance(ret, Element) : 52 | new_element.append(ret) 53 | elif isinstance(ret, str): 54 | if len(new_element) == 0: 55 | if new_element.text is not None: 56 | new_element.text += ret 57 | else: 58 | new_element.text = ret 59 | else: 60 | if new_element[-1].tail is not None: 61 | new_element[-1].tail += ret 62 | else: 63 | new_element[-1].tail = ret 64 | return new_element 65 | bible = None 66 | def setBible(bible_in): 67 | global bible 68 | bible = bible_in 69 | 70 | def has_tag(text, tag, some_bool): 71 | 72 | try: 73 | parser = XMLParser() 74 | text = ""+string+"" 75 | element = ET.fromstring(text, parser=parser) 76 | except ParseError: 77 | return False 78 | ret = element.findall(".//"+tag) 79 | return len(ret) > 0 80 | def convert_mybible_to_markdown( 81 | string, 82 | verse_number, 83 | remove_pre_whitespace, 84 | remove_post_whitespace): 85 | try: 86 | parser = XMLParser() 87 | text = ""+string+"" 88 | element = ET.fromstring(text, parser=parser) 89 | except ParseError: 90 | return '' 91 | if verse_number is not None: 92 | new_element = Element("sup") 93 | new_element.text = str(verse_number) 94 | new_element.tail = element.text 95 | if isinstance(new_element, Element) : 96 | element.insert(0, new_element) 97 | new_root = convert(element) 98 | return tostring(new_root, encoding='unicode') 99 | def convert_mybible_to_text( 100 | string, 101 | verse_number, 102 | remove_pre_whitespace, 103 | remove_post_whitespace): 104 | try: 105 | parser = XMLParser() 106 | text = ""+string+"" 107 | element = ET.fromstring(text, parser=parser) 108 | except ParseError: 109 | return '' 110 | new_root = convert(element) 111 | return tostring(new_root, encoding='unicode', method='text') 112 | 113 | 114 | print(convert_mybible_to_markdown('A song of degrees, or Psalm of David.
If the Lord had not been [1]on our side, (may Israel now say)',None, False,False)) 115 | -------------------------------------------------------------------------------- /src/window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | app.preferences 10 | Preferences 11 | 12 |
13 |
14 | 15 | app.about 16 | About 17 | 18 |
19 |
20 | 21 |
22 | 23 | app.strongs 24 | Strongs 25 | 26 | 27 | app.define 28 | Define 29 | 30 |
31 |
32 | 109 |
110 | -------------------------------------------------------------------------------- /src/text_rendering.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | app.strongs 10 | Strongs 11 | 12 | 13 | app.define 14 | Define 15 | 16 |
17 |
18 | 112 |
113 | -------------------------------------------------------------------------------- /src/settings.py: -------------------------------------------------------------------------------- 1 | from .path_order import find_file_on_path, walk_files_on_path 2 | from .Bible_Parser import BibleParser, allParsers 3 | from gi.repository import GObject, GLib, Gtk, Gio 4 | from gi.repository import Adw 5 | import os 6 | import shutil 7 | from .config import user_data_dir, default_translation 8 | import gi 9 | gi.require_version('Gtk', '4.0') 10 | gi.require_version('Adw', '1') 11 | 12 | 13 | Adw.init() 14 | 15 | 16 | @Gtk.Template(resource_path='/net/lugsole/bible_gui/settings.ui') 17 | class BibleSettings(Adw.PreferencesWindow): 18 | __gtype_name__ = 'BibleSettings' 19 | translation = Gtk.Template.Child() 20 | importTranslation = Gtk.Template.Child() 21 | 22 | def __init__(self, App): 23 | super().__init__() 24 | self.App = App 25 | self.bible_file = "" 26 | self.translation_array = [] 27 | try: 28 | settings = Gio.Settings.new(self.App.BASE_KEY) 29 | self.bible_file = settings.get_string("bible-translation") 30 | except Exception: 31 | print("gsettings error") 32 | self.importTranslation.connect('clicked', self.import_translation) 33 | self.translations_load() 34 | self.show() 35 | if not os.path.isdir(user_data_dir): 36 | os.makedirs(user_data_dir, exist_ok=True) 37 | 38 | def copy_translation(self, filename): 39 | shutil.copy(filename, user_data_dir) 40 | self.translations_load() 41 | 42 | def import_translation(self, button): 43 | chooser = Gtk.FileChooserNative() 44 | chooser.set_transient_for(self) 45 | chooser.set_action(Gtk.FileChooserAction.OPEN) 46 | 47 | filter = Gtk.FileFilter() 48 | filter.set_name("Supported Bible files") 49 | chooser.add_filter(filter) 50 | for parser in allParsers: 51 | filter1 = Gtk.FileFilter() 52 | filter1.set_name(parser.getParserName(parser)) 53 | for ending in parser.getParserEndings(parser): 54 | filter.add_pattern("*." + ending) 55 | filter1.add_pattern("*." + ending) 56 | chooser.add_filter(filter1) 57 | filter = Gtk.FileFilter() 58 | filter.set_name("All Files") 59 | filter.add_pattern("*") 60 | chooser.add_filter(filter) 61 | 62 | chooser.connect('response', self.import_translation_load) 63 | chooser.show() 64 | self.chooser = chooser 65 | 66 | def import_translation_load(self, chooser, res): 67 | if res == Gtk.ResponseType.ACCEPT: 68 | for file in chooser.get_files(): 69 | self.copy_translation(file.get_path()) 70 | chooser.destroy() 71 | self.chooser.destroy() 72 | else: 73 | chooser.destroy() 74 | self.chooser.destroy() 75 | 76 | def translations_load(self): 77 | for item in self.translation_array: 78 | self.translation.remove(item) 79 | self.translation_array = [] 80 | all_files = [] 81 | base = user_data_dir 82 | for root, dirs, files in os.walk(base): 83 | for filename in files: 84 | rel_path = os.path.join(root, filename) 85 | all_files.append(rel_path) 86 | all_files.sort() 87 | 88 | def_check = Gtk.CheckButton() 89 | 90 | files = walk_files_on_path() 91 | rel_files = sorted(files.keys()) 92 | for k in rel_files: 93 | # print(k) 94 | bible_file = os.path.join(files[k], k) 95 | path_file_name = k 96 | # print("") 97 | try: 98 | p = BibleParser(bible_file) 99 | if p is not None: 100 | p.loadInfo() 101 | 102 | row = BibleTranslationRow( 103 | p.bible.translationName, 104 | self.translations_change, 105 | path_file_name, 106 | def_check) 107 | if str(path_file_name) == str(self.bible_file) or ( 108 | self.bible_file == "" and path_file_name == default_translation): 109 | row.select() 110 | else: 111 | row.deselect() 112 | self.translation.add(row) 113 | self.translation_array.append(row) 114 | except Exception as error: 115 | print(bible_file, "Must not be a bible file") 116 | print(error) 117 | 118 | def translations_change(self, button, data): 119 | try: 120 | #print("data", data) 121 | settings = Gio.Settings.new(self.App.BASE_KEY) 122 | settings.set_string("bible-translation", data) 123 | self.bible_file = settings.get_string("bible-translation") 124 | except Exception: 125 | print("gsettings error") 126 | 127 | 128 | class BibleTranslationRow(Adw.ActionRow): 129 | def __init__(self, p, cb, rel_path, main_check): 130 | super().__init__() 131 | 132 | base = user_data_dir 133 | self.cb = cb 134 | self.rel_path = rel_path 135 | self.set_property("title", str(p)) 136 | self.button = Gtk.CheckButton() 137 | self.button.add_css_class("selection-mode") 138 | self.button.connect('toggled', self.callback, rel_path) 139 | self.button.show() 140 | self.button.set_valign(Gtk.Align.CENTER) 141 | if main_check is not None: 142 | self.button.set_group(main_check) 143 | 144 | self.set_activatable_widget(self.button) 145 | 146 | self.box = Gtk.Box() 147 | self.box.append(self.button) 148 | # if True: 149 | # self.sys = Gtk.Button() 150 | # self.sys.add_css_class("pill") 151 | # self.sys.add_css_class("small") 152 | # self.sys.add_css_class("raised") 153 | # self.sys.set_label("system") 154 | # self.sys.show() 155 | # self.box.append(self.sys) 156 | self.add_suffix(self.box) 157 | self.show() 158 | 159 | def deselect(self): 160 | self.button.set_active(False) 161 | 162 | def select(self): 163 | self.button.set_active(True) 164 | 165 | def callback(self, button, data): 166 | if button.get_active(): 167 | self.cb(self, self.rel_path) 168 | 169 | def get_check(self): 170 | return self.button 171 | -------------------------------------------------------------------------------- /src/window.py: -------------------------------------------------------------------------------- 1 | 2 | import gi 3 | gi.require_version('Gtk', '4.0') 4 | gi.require_version('Gst', '1.0') 5 | gi.require_version('Adw', '1') 6 | 7 | from .Bible import Verse, Story 8 | from .path_order import find_file_on_path, walk_files_on_path 9 | from .mybible_to_markdown import convert_mybible_to_markdown, convert_mybible_to_text, has_tag, setBible,convert_mybible_to_text_buff 10 | from .Bible_Parser import BibleParser, BibleParserSqLit3 11 | from .tts import readText 12 | from gi.repository import Gtk, Gio, Gst, Gdk, Pango, GLib 13 | from gi.repository import Adw 14 | from .config import pkgdatadir, application_id, user_data_dir, translationdir, default_translation 15 | from .text_rendering import BibleSettings 16 | import os 17 | import time 18 | from gettext import gettext as _ 19 | import locale 20 | 21 | 22 | Adw.init() 23 | 24 | 25 | 26 | @Gtk.Template(resource_path='/net/lugsole/bible_gui/window.ui') 27 | class BibleWindow(Adw.ApplicationWindow): 28 | __gtype_name__ = 'BibleWindow' 29 | 30 | content_box = Gtk.Template.Child() 31 | sidebar = Gtk.Template.Child() 32 | search = Gtk.Template.Child() 33 | right_box = Gtk.Template.Child() 34 | book_list = Gtk.Template.Child() 35 | split_view = Gtk.Template.Child() 36 | tr = Gtk.Template.Child() 37 | 38 | def __init__(self, App, **kwargs): 39 | super().__init__(**kwargs) 40 | self.App = App 41 | # print(find_file_on_path("kjv.tsv")) 42 | # print(walk_files_on_path()) 43 | # print(default_translation) 44 | try: 45 | # try to connect to settings 46 | self.settings = Gio.Settings.new(self.App.BASE_KEY) 47 | base_file = self.settings.get_string("bible-translation") 48 | #base_file = "French_LouisSegond.spb" 49 | self.settings.connect( 50 | "changed::bible-translation", 51 | self.on_bible_translation_changed) 52 | full_path = os.path.join(find_file_on_path(base_file), base_file) 53 | #print(full_path) 54 | if base_file != "" and os.path.isfile(full_path): 55 | self.p = BibleParser(full_path) 56 | else: 57 | print("Falling back to KJV") 58 | base_file = default_translation 59 | full_path = os.path.join( 60 | find_file_on_path(base_file), base_file) 61 | self.p = BibleParser(full_path) 62 | self.p.loadAll() 63 | except Exception as e: 64 | print(e) 65 | print("Falling back to KJV") 66 | base_file = default_translation 67 | full_path = os.path.join(find_file_on_path(base_file), base_file) 68 | self.p = BibleParser(full_path) 69 | self.p.loadAll() 70 | self.origionalBible = self.p.bible 71 | self.Bible = self.origionalBible 72 | self.Bible.sort() 73 | 74 | self.search.connect( 75 | 'search-changed', self.search_call 76 | ) 77 | 78 | 79 | self.book = self.Bible.books[0] 80 | self.chapter = self.book.chapters[0] 81 | self.tr.setBible(self.Bible) 82 | self.tr.p = self.p 83 | self.tr.setApp(self.App) 84 | self.tr.book = self.book.number 85 | self.tr.chapter = self.chapter.number 86 | self.tr.UpdateTable(self.chapter.verses, None, self.p) 87 | self.tr.update_prev_next() 88 | self.UpdateBooks() 89 | self.show() 90 | 91 | def add_book(self, Bible, book, chapters): 92 | flowbox = Gtk.FlowBox() 93 | flowbox.set_valign(Gtk.Align.START) 94 | flowbox.set_max_children_per_line(30) 95 | flowbox.set_selection_mode(Gtk.SelectionMode.NONE) 96 | flowbox.show() 97 | for chapter in chapters: 98 | button = Gtk.Button(label=str(chapter.number)) 99 | button.show() 100 | button.connect( 101 | 'clicked', 102 | self.show_page, 103 | self.right_box, 104 | book, chapter) 105 | 106 | flowbox.insert(button, -1) 107 | expand = Adw.ExpanderRow() 108 | expand.set_property("title", book.bookName) 109 | 110 | expand.add_row(flowbox) 111 | expand.show() 112 | self.book_list.append(expand) 113 | 114 | def UpdateBooks(self): 115 | while self.book_list.get_row_at_index(0) is not None: 116 | self.book_list.remove(self.book_list.get_row_at_index(0)) 117 | for book in self.Bible.books: 118 | chapters = book.chapters 119 | self.add_book(self.Bible, book, chapters) 120 | 121 | def search_call(self, b): 122 | search_term = self.search.get_text() 123 | if len(search_term) < 3: 124 | self.Bible = self.origionalBible 125 | else: 126 | filtered = self.origionalBible.search_chapters(search_term) 127 | self.Bible = filtered 128 | self.UpdateBooks() 129 | 130 | def show_page(self, button, page, book, chapter): 131 | if self.chapter is not chapter: 132 | self.App.player.end() 133 | self.chapter = chapter 134 | self.book = book 135 | self.update_play_icon() 136 | verses = chapter.verses 137 | self.tr.book = book.number 138 | self.tr.chapter = chapter.number 139 | self.tr.UpdateTable(verses, None, self.p) 140 | self.tr.update_prev_next() 141 | self.split_view.set_show_content(True) 142 | 143 | def set_chapter(self, book_number, chapter_number): 144 | #print("set_chapter", book_number, chapter_number) 145 | 146 | self.book = self.Bible.getBookByNum(book_number) 147 | self.chapter = self.book.getChapter(chapter_number) 148 | 149 | def readChapter(self, button): 150 | read = "" 151 | if self.App.player.getstate() == Gst.State.PLAYING: 152 | self.App.player._pause() 153 | elif self.App.player.getstate() == Gst.State.PAUSED: 154 | self.App.player._play() 155 | else: 156 | self.App.player.start_file( 157 | readText( 158 | self.read_text, 159 | self.Bible.language)) 160 | self.App.player.set_title(str(self.book) + " " + str(self.chapter)) 161 | if self.Bible.translationName == '': 162 | self.App.player.set_artist( 163 | [self.Bible.translationAbbreviation]) 164 | else: 165 | self.App.player.set_artist([self.Bible.translationName]) 166 | self.App.player.set_album(_("Bible")) 167 | self.App.player._play() 168 | self.update_play_icon() 169 | 170 | def done_playing(self): 171 | self.App.player.end() 172 | self.update_play_icon() 173 | 174 | def update_play_icon(self): 175 | action = "start" 176 | if self.App.player.getstate() == Gst.State.PLAYING: 177 | action = "pause" 178 | 179 | def on_bible_translation_changed(self, settings, key): 180 | base_file = settings.get_string("bible-translation") 181 | #print("Loading new file") 182 | if base_file == "": 183 | base_file = default_translation 184 | full_path = os.path.join(find_file_on_path(base_file), base_file) 185 | self.p = BibleParser(full_path) 186 | else: 187 | full_path = os.path.join(find_file_on_path(base_file), base_file) 188 | self.p = BibleParser(full_path) 189 | #print("File loaded", find_file_on_path(base_file)) 190 | self.p.loadAll() 191 | self.origionalBible = self.p.bible 192 | self.Bible = self.origionalBible 193 | self.Bible.sort() 194 | 195 | self.UpdateBooks() 196 | 197 | self.book = self.Bible.books[0] 198 | self.chapter = self.book.chapters[0] 199 | self.tr.book = self.book.number 200 | self.tr.chapter = self.chapter.number 201 | self.tr.setBible(self.Bible) 202 | self.tr.p = self.p 203 | self.tr.UpdateTable(self.chapter.verses, None, self.p) 204 | self.tr.update_prev_next() 205 | 206 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | 4 | import gi 5 | gi.require_version('Gtk', '4.0') 6 | gi.require_version('Adw', '1') 7 | gi.require_version('Gst', '1.0') 8 | 9 | from gi.repository import Adw, Gtk, Gio, GLib 10 | from .window import BibleWindow 11 | from .Audio_Player import Player 12 | from .config import application_id, VERSION 13 | from .settings import BibleSettings 14 | from gettext import gettext as _ 15 | 16 | 17 | class Application(Gtk.Application): 18 | def __init__(self): 19 | super().__init__(application_id=application_id, 20 | flags=Gio.ApplicationFlags.HANDLES_OPEN) 21 | self.props.resource_base_path = "/net/lugsole/bible_gui" 22 | 23 | self.BASE_KEY = application_id 24 | GLib.set_application_name(_("Bible")) 25 | GLib.set_prgname(application_id) 26 | GLib.setenv("PULSE_PROP_media.role", "music", True) 27 | self.player = Player(self) 28 | action_print = Gio.SimpleAction.new("preferences", None) 29 | action_print.connect("activate", self.launch_settings) 30 | self.add_action(action_print) 31 | action_print = Gio.SimpleAction.new("about", None) 32 | action_print.connect("activate", self.show_about) 33 | self.add_action(action_print) 34 | #print("init ended") 35 | 36 | def do_activate(self): 37 | # print("activate") 38 | self._window = self.props.active_window 39 | if not self._window: 40 | self._window = BibleWindow(self, application=self) 41 | self._window.present() 42 | 43 | if self.props.application_id == "net.lugsole.bible_gui.Devel": 44 | self._window.get_style_context().add_class('devel') 45 | 46 | def do_open(self, a1, a2, a3): 47 | # print("open") 48 | # print(self) 49 | # print(a1) 50 | # print(a2) 51 | # print(a3) 52 | # print(dir(a1[0])) 53 | 54 | self._window = self.props.active_window 55 | if not self._window: 56 | self._window = BibleWindow(self, application=self) 57 | self._window.present() 58 | 59 | if self.props.application_id == "net.lugsole.bible_gui.Devel": 60 | self._window.get_style_context().add_class('devel') 61 | 62 | for i in range(len(a1)): 63 | print("get_basename", a1[i].get_basename()) 64 | #print("get_child", a1[i].get_child()) 65 | #print("get_child_for_display_name", a1[i].get_child_for_display_name()) 66 | #print("get_data", a1[i].get_data()) 67 | print("get_parent", a1[i].get_parent()) 68 | print("get_parse_name", a1[i].get_parse_name()) 69 | print("get_path", a1[i].get_path()) 70 | #print("get_properties", a1[i].get_properties()) 71 | #print("get_property", a1[i].get_property()) 72 | #print("get_qdata", a1[i].get_qdata()) 73 | #print("get_relative_path", a1[i].get_relative_path()) 74 | print("get_uri", a1[i].get_uri()) 75 | print("get_uri_scheme", a1[i].get_uri_scheme()) 76 | #print("getv", a1[i].getv()) 77 | self.parse(a1[i].get_basename()) 78 | 79 | def raide_main_window(self): 80 | self._window.present() 81 | 82 | def launch_settings(self, e1, e2): 83 | #print(e1) 84 | #print(e2) 85 | settings = BibleSettings(self) 86 | if self.props.application_id == "net.lugsole.bible_gui.Devel": 87 | settings.get_style_context().add_class('devel') 88 | settings.set_transient_for(self._window) 89 | 90 | def show_about(self, e1, e2): 91 | dialog = Adw.AboutWindow() 92 | if self.props.application_id == "net.lugsole.bible_gui.Devel": 93 | dialog.get_style_context().add_class('devel') 94 | dialog.set_transient_for(self._window) 95 | dialog.set_application_name(_("Bible")) 96 | dialog.set_website("https://lugsole.net/programs/bible") 97 | dialog.set_copyright("Copyright © 2020-2023 Lugsole") 98 | dialog.set_version(VERSION) 99 | dialog.set_license_type(Gtk.License.MIT_X11) 100 | dialog.set_developers(["Lugsole"]) 101 | dialog.set_application_icon(application_id) 102 | dialog.show() 103 | 104 | def parse(self, books_chapter): 105 | t1 = books_chapter.split(' ', 1) 106 | book_number = int(t1[0]) 107 | print("book_number", book_number) 108 | rest = t1[1] 109 | start_chapter_str = '' 110 | while len(rest) > 0 and '0' <= rest[0] and '9' >= rest[0]: 111 | # print(rest[0]) 112 | start_chapter_str += rest[0] 113 | rest = rest[1:] 114 | start_chapter_number = int(start_chapter_str) 115 | print("start_chapter_number", start_chapter_number) 116 | if 0 == len(rest): 117 | print("Done, no more") 118 | self._window.set_chapter(book_number, start_chapter_number) 119 | self._window.UpdateTable(self._window.chapter.verses, None) 120 | self._window.content_box.set_visible_child(self._window.right_box) 121 | elif rest[0] == ':': 122 | print("parsing verse") 123 | start_verse_str = '' 124 | rest = rest[1:] 125 | while len(rest) > 0 and '0' <= rest[0] and '9' >= rest[0]: 126 | # print(rest[0]) 127 | start_verse_str += rest[0] 128 | rest = rest[1:] 129 | start_verse_number = int(start_verse_str) 130 | print("start_verse_number", start_verse_number) 131 | if 0 == len(rest): 132 | print("Done, no more") 133 | self._window.set_chapter(book_number, start_chapter_number) 134 | self._window.UpdateTable( 135 | list( 136 | filter( 137 | lambda x: ( 138 | x.verse == start_verse_number), 139 | self._window.chapter.verses)), 140 | None) 141 | self._window.content_box.set_visible_child( 142 | self._window.right_box) 143 | elif rest[0] == '-': 144 | print("parsing verse") 145 | next_str = '' 146 | rest = rest[1:] 147 | while len(rest) > 0 and '0' <= rest[0] and '9' >= rest[0]: 148 | # print(rest[0]) 149 | next_str += rest[0] 150 | rest = rest[1:] 151 | next_number = int(next_str) 152 | if 0 == len(rest): 153 | print("Done, no more") 154 | print("ending verse", next_number) 155 | self._window.set_chapter(book_number, start_chapter_number) 156 | self._window.UpdateTable( 157 | list( 158 | filter( 159 | lambda x: ( 160 | x.verse >= start_verse_number and x.verse <= next_number), 161 | self._window.chapter.verses)), 162 | self._window.book.bookName + 163 | " " + 164 | str(start_chapter_number) + 165 | ":" + 166 | str(start_verse_number) + 167 | "-" + 168 | str(next_number)) 169 | self._window.book.bookName 170 | 171 | self._window.content_box.set_visible_child( 172 | self._window.right_box) 173 | elif rest[0] == ':': 174 | print("parsing verse") 175 | end_verse_str = '' 176 | rest = rest[1:] 177 | while len(rest) > 0 and '0' <= rest[0] and '9' >= rest[0]: 178 | # print(rest[0]) 179 | end_verse_str += rest[0] 180 | rest = rest[1:] 181 | end_verse_number = int(end_verse_str) 182 | print("ending chapter", next_number) 183 | print("ending verse", end_verse_number) 184 | elif rest[0] == ',': 185 | rest = rest[1:] 186 | lines = [start_verse_number] + rest.split(',') 187 | print(lines) 188 | elif rest[0] == '-': 189 | print("parsing end chapter") 190 | end_chapter_number = int(rest[1:]) 191 | print("end_chapter_number", end_chapter_number) 192 | 193 | 194 | def main(version): 195 | app = Application() 196 | return app.run(sys.argv) 197 | -------------------------------------------------------------------------------- /src/text_rendering.py: -------------------------------------------------------------------------------- 1 | from gi.repository import Gtk, Gio, Gst, Gdk, Pango, GLib, GObject 2 | from gi.repository import Adw 3 | from gettext import gettext as _ 4 | 5 | from .Bible import Verse, Story 6 | from .mybible_to_markdown import convert_mybible_to_markdown, convert_mybible_to_text, has_tag, setBible,convert_mybible_to_text_buff 7 | from .Bible_Parser import BibleParser, BibleParserSqLit3 8 | from .tts import readText 9 | import gi 10 | gi.require_version('Gtk', '4.0') 11 | gi.require_version('Adw', '1') 12 | 13 | 14 | Adw.init() 15 | 16 | 17 | @Gtk.Template(resource_path='/net/lugsole/bible_gui/text_rendering.ui') 18 | class BibleSettings(Adw.NavigationPage): 19 | __gtype_name__ = 'TextRendering' 20 | sub_header_bar = Gtk.Template.Child() 21 | play_button = Gtk.Template.Child() 22 | play_image = Gtk.Template.Child() 23 | previous_button = Gtk.Template.Child() 24 | next_button = Gtk.Template.Child() 25 | scrolled = Gtk.Template.Child() 26 | viewport = Gtk.Template.Child() 27 | bible_text = Gtk.Template.Child() 28 | Chapter = 0 29 | Book = 0 30 | 31 | def __init__(self): 32 | super().__init__() 33 | #print("init") 34 | self.Bible = None 35 | 36 | @GObject.Property(type=int) 37 | def chapter(self): 38 | return self.Chapter 39 | 40 | @chapter.setter 41 | def chapter(self, Chapter): 42 | self.Chapter = Chapter 43 | 44 | 45 | @GObject.Property(type=int) 46 | def book(self): 47 | return self.Book 48 | 49 | @book.setter 50 | def book(self, Book): 51 | self.Book = Book 52 | 53 | def setBible(self, b): 54 | self.Bible = b 55 | 56 | def setApp(self, app): 57 | self.App = app 58 | self.App.player.add_callback(self.done_playing) 59 | self.App.player.add_state_change_callback(self.update_play_icon) 60 | 61 | def UpdateTable(self, verses, title, p): 62 | if p is None: 63 | p = self.p 64 | setBible(self.Bible) 65 | if len(verses) <= 0: 66 | return 67 | chNum = verses[0].chapter 68 | bookNum = verses[0].bookNumber 69 | SameCh = True 70 | SameBook = True 71 | for verse in verses: 72 | if verse.chapter != chNum: 73 | SameCh = False 74 | if verse.bookNumber != bookNum: 75 | SameBook = False 76 | break 77 | if title is None: 78 | book = self.Bible.getBookByNum(self.book) 79 | if book is not None: 80 | title = book.bookName.removesuffix('\u200e') + ' ' + str(self.chapter) 81 | 82 | text = Gtk.Label() 83 | if self.Bible.right_to_left: 84 | text.set_direction(Gtk.TextDirection.RTL) 85 | else: 86 | text.set_direction(Gtk.TextDirection.LTR) 87 | text.set_label(title) 88 | self.sub_header_bar.set_title_widget(text) 89 | 90 | 91 | read_text = "" 92 | use_md = False 93 | if isinstance(p, BibleParserSqLit3): 94 | for i in range(len(verses)): 95 | verse = verses[i] 96 | if has_tag(verse.text, "pb", i == 0): 97 | use_md = True 98 | break 99 | tb = Gtk.TextBuffer() 100 | title_props = {} 101 | title_props["scale"] = 1.5 102 | title_props["weight"] = Pango.Weight.BOLD 103 | tb.create_tag("Title", **title_props) 104 | tb.create_tag("h", **title_props) #, text_transform=Pango.TextTransform.CAPITALIZE) 105 | tb.create_tag("f", size=8*Pango.SCALE, rise=-6*Pango.SCALE, invisible=True) 106 | tb.create_tag("S", invisible=True) 107 | tb.create_tag("sup", scale=0.75, rise=4000) 108 | tb.create_tag("n", style=Pango.Style.OBLIQUE) 109 | tb.create_tag("e", style=Pango.Style.ITALIC) 110 | tb.create_tag("i", style=Pango.Style.OBLIQUE) 111 | tb.create_tag("t", style=Pango.Style.OBLIQUE, left_margin=20) 112 | tb.create_tag("J", foreground="Red",style=Pango.Style.OBLIQUE) 113 | tb.create_tag("underline", underline=Pango.Underline.SINGLE) 114 | for i in range(len(verses)): 115 | verse = verses[i] 116 | 117 | text_label = "" 118 | if not SameBook: 119 | text_label += self.Bible.getBookName( 120 | verse.bookNumber).bookName + " " 121 | if not SameBook or not SameCh: 122 | text_label += str(verse.chapter) + ":" 123 | text_label += str(verse.verse) 124 | if isinstance(verse, Verse): 125 | if use_md and SameBook and SameCh: 126 | convert_mybible_to_text_buff(verse.text, verse.verse, i == 0, True, tb, "Text") 127 | elif not use_md and isinstance(p, BibleParserSqLit3): 128 | 129 | start, end = tb.get_bounds() 130 | tb.insert(end, text_label + " " + \ 131 | convert_mybible_to_text(verse.text, None, True, True) + '\r') 132 | else: 133 | start, end = tb.get_bounds() 134 | tb.insert(end, text_label + " " + verse.text + '\r') 135 | elif isinstance(verse, Story): 136 | if 0 < i: 137 | start, end = tb.get_bounds() 138 | tb.insert(end, '\r') 139 | 140 | convert_mybible_to_text_buff(verse.text, None, True, True, tb, "Title") 141 | 142 | if isinstance(p, BibleParserSqLit3): 143 | read_text += convert_mybible_to_text( 144 | verse.text, None, True, True) + ' ' 145 | else: 146 | read_text += verse.text + ' ' 147 | 148 | self.read_text = read_text 149 | 150 | self.bible_text.set_buffer(tb) 151 | 152 | 153 | @Gtk.Template.Callback() 154 | def next_chapter_cb(self, a): 155 | #GLib.idle_add(print,"Hello",priority=GLib.PRIORITY_HIGH_IDLE) 156 | self.next_action() 157 | 158 | def next_action(self): 159 | Found, book, chapter = self.Bible.getNextChapterByNum(self.Book,self.Chapter) 160 | if Found: 161 | self.Book = book.number 162 | self.Chapter = chapter.number 163 | self.UpdateTable(chapter.verses, None, None) 164 | status = self.App.player.get_status() 165 | self.App.player.end() 166 | self.update_prev_next() 167 | if status == "Playing": 168 | self.readChapter(None) 169 | else: 170 | self.update_play_icon() 171 | self.scrolled.set_vadjustment(None) 172 | 173 | def can_next(self): 174 | Found, book, chapter = self.Bible.getNextChapterByNum(self.Book, self.Chapter) 175 | return Found 176 | 177 | @Gtk.Template.Callback() 178 | def previous_chapter_cb(self, a): 179 | self.previous_action() 180 | 181 | def previous_action(self): 182 | Found, book, chapter = self.Bible.getPreviousChapterByNum(self.Book, self.Chapter) 183 | if Found: 184 | self.book = book.number 185 | self.chapter = chapter.number 186 | self.UpdateTable(chapter.verses, None, None) 187 | status = self.App.player.get_status() 188 | self.App.player.end() 189 | self.update_prev_next() 190 | if status == "Playing": 191 | self.readChapter(None) 192 | else: 193 | self.update_play_icon() 194 | self.scrolled.set_vadjustment(None) 195 | 196 | def can_previous(self): 197 | Found, book, chapter = self.Bible.getPreviousChapterByNum(self.Book, self.Chapter) 198 | return Found 199 | 200 | 201 | @Gtk.Template.Callback() 202 | def readChapter(self, button): 203 | read = "" 204 | if self.App.player.getstate() == Gst.State.PLAYING: 205 | self.App.player._pause() 206 | elif self.App.player.getstate() == Gst.State.PAUSED: 207 | self.App.player._play() 208 | else: 209 | self.App.player.start_file( 210 | readText( 211 | self.read_text, 212 | self.Bible.language)) 213 | self.App.player.set_title(str(self.book) + " " + str(self.chapter)) 214 | if self.Bible.translationName == '': 215 | self.App.player.set_artist( 216 | [self.Bible.translationAbbreviation]) 217 | else: 218 | self.App.player.set_artist([self.Bible.translationName]) 219 | self.App.player.set_album(_("Bible")) 220 | self.App.player._play() 221 | self.update_play_icon() 222 | 223 | def update_prev_next(self): 224 | self.next_button.set_sensitive(self.can_next()) 225 | self.previous_button.set_sensitive(self.can_previous()) 226 | 227 | def done_playing(self): 228 | self.App.player.end() 229 | self.update_play_icon() 230 | 231 | def update_play_icon(self): 232 | action = "start" 233 | if self.App.player.getstate() == Gst.State.PLAYING: 234 | action = "pause" 235 | self.play_image.set_property( 236 | "icon_name", 237 | "media-playback-" + 238 | action + 239 | "-symbolic") 240 | -------------------------------------------------------------------------------- /src/Bible.py: -------------------------------------------------------------------------------- 1 | 2 | class Bible: 3 | def __init__(self): 4 | self.translationName = "" 5 | self.translationAbbreviation = "" 6 | self.translationInformation = "" 7 | self.books = [] 8 | self.language = "" 9 | self.right_to_left = False 10 | 11 | def __str__(self): 12 | ret = "" 13 | 14 | ret += "translation name: " + self.translationName + '\n' 15 | ret += "translation abbreviation: " + self.translationAbbreviation + '\n' 16 | ret += "translation information: " + self.translationInformation + '\n' 17 | for book in self.books: 18 | ret += str(book) + '\n' 19 | return ret 20 | 21 | def append(self, book): 22 | self.books.append(book) 23 | 24 | def addVerse(self, verse): 25 | for book in self.books: 26 | if book.number == verse.bookNumber: 27 | book.addVerse(verse) 28 | break 29 | 30 | def getBookNames(self): 31 | books = [] 32 | for book in self.books: 33 | books.append(book.bookName) 34 | return books 35 | 36 | def getBookName(self, num): 37 | for book in self.books: 38 | if book.number == num: 39 | return book.bookName 40 | return "" 41 | 42 | def getBooksChapterNames(self, bookName): 43 | for book in self.books: 44 | if bookName == book.bookName: 45 | return book.getChapterNames() 46 | return [] 47 | 48 | def getBookByNum(self, num): 49 | for book in self.books: 50 | if book.number == num: 51 | return book 52 | return None 53 | 54 | def getVerses(self, bookName, chapters): 55 | for book in self.books: 56 | if book.bookName == bookName: 57 | return book.getVerses(chapters) 58 | 59 | def search(self, string): 60 | ret = [] 61 | string = string.lower() 62 | for book in self.books: 63 | ret += book.search(string) 64 | return ret 65 | 66 | def search_chapters(self, string): 67 | copy = Bible() 68 | 69 | copy.translationName = self.translationName 70 | copy.translationAbbreviation = self.translationAbbreviation 71 | copy.translationInformation = self.translationInformation 72 | copy.language = self.language 73 | for book in self.books: 74 | ret = book.search_chapters(string) 75 | if ret is not None: 76 | copy.append(ret) 77 | return copy 78 | 79 | def sort(self): 80 | self.books.sort(key=lambda x: x.number) 81 | for book in self.books: 82 | book.sort() 83 | 84 | def next(self, current_book, chapter): 85 | found_next = False 86 | next_chapter = None 87 | found_next, next_chapter = current_book.next(chapter) 88 | if found_next: 89 | return found_next, current_book, next_chapter 90 | try: 91 | next_book_index = self.books.index(current_book) + 1 92 | 93 | if next_book_index < len(self.books): 94 | next_book = self.books[next_book_index] 95 | return True, next_book, next_book.chapters[0] 96 | except ValueError: 97 | return False, None, None 98 | return False, None, None 99 | 100 | def previous(self, current_book, chapter): 101 | found_previous = False 102 | previous_chapter = None 103 | found_previous, previous_chapter = current_book.previous(chapter) 104 | if found_previous: 105 | return found_previous, current_book, previous_chapter 106 | try: 107 | previous_book_index = self.books.index(current_book) - 1 108 | if previous_book_index >= 0: 109 | previous_book = self.books[previous_book_index] 110 | return True, previous_book, previous_book.chapters[len( 111 | previous_book.chapters) - 1] 112 | except ValueError: 113 | return False, None, None 114 | return False, None, None 115 | 116 | def getNextChapterByNum(self, book_number, chapter_number): 117 | book = self.getBookByNum(book_number) 118 | if book is None: 119 | return False, None, None 120 | chapter = book.getChapter(chapter_number) 121 | if chapter is None: 122 | return False, None, None 123 | return self.next(book,chapter) 124 | 125 | def getPreviousChapterByNum(self, book_number, chapter_number): 126 | book = self.getBookByNum(book_number) 127 | if book is None: 128 | return False, None, None 129 | chapter = book.getChapter(chapter_number) 130 | if chapter is None: 131 | return False, None, None 132 | return self.previous(book,chapter) 133 | 134 | class Book: 135 | def __init__(self, name, number=-1, shortName=""): 136 | 137 | self.bookName = name 138 | self.number = number 139 | self.shortName = shortName 140 | self.chapters = [] 141 | 142 | def addChapter(self, chapter): 143 | self.chapters.append(chapter) 144 | 145 | def __str__(self): 146 | return self.bookName 147 | 148 | def addVerse(self, verse): 149 | found = False 150 | for chapter in self.chapters: 151 | if chapter.number == verse.chapter: 152 | chapter.addVerse(verse) 153 | found = True 154 | break 155 | if not found: 156 | c = Chapter(verse.chapter) 157 | c.addVerse(verse) 158 | self.chapters.append(c) 159 | 160 | def search(self, string): 161 | ret = [] 162 | string = string.lower() 163 | for chapter in self.chapters: 164 | ret += chapter.search(string) 165 | return ret 166 | 167 | def search_chapters(self, string): 168 | copy = Book(self.bookName, self.number, self.shortName) 169 | for chapter in self.chapters: 170 | 171 | ret = chapter.search_chapters(string) 172 | if ret is not None: 173 | copy.addChapter(ret) 174 | if len(copy.chapters) > 0: 175 | return copy 176 | else: 177 | return None 178 | 179 | def getChapterNames(self): 180 | ret = [] 181 | for chapter in self.chapters: 182 | ret.append(chapter.number) 183 | return ret 184 | 185 | def getVerses(self, thereChapters): 186 | for chapter in self.chapters: 187 | if chapter.number == thereChapters: 188 | return chapter.verses 189 | 190 | def getChapter(self, thereChapters): 191 | for chapter in self.chapters: 192 | if chapter.number == thereChapters: 193 | return chapter 194 | 195 | def sort(self): 196 | self.chapters.sort(key=lambda x: x.number) 197 | for chapter in self.chapters: 198 | chapter.sort() 199 | 200 | def next(self, current_chapter): 201 | next_chapter_index = self.chapters.index(current_chapter) + 1 202 | if next_chapter_index < len(self.chapters): 203 | next_chapter = self.chapters[next_chapter_index] 204 | return True, next_chapter 205 | return False, None 206 | 207 | def previous(self, current_chapter): 208 | previous_chapter_index = self.chapters.index(current_chapter) - 1 209 | if previous_chapter_index >= 0: 210 | previous_chapter = self.chapters[previous_chapter_index] 211 | return True, previous_chapter 212 | return False, None 213 | 214 | 215 | def sort_verse(item): 216 | if isinstance(item, Verse): 217 | return item.verse * 2 218 | elif isinstance(item, Story): 219 | return item.verse * 2 - 1 220 | 221 | 222 | class Chapter: 223 | def __init__(self, number=-1): 224 | self.number = number 225 | self.verses = [] 226 | 227 | def __str__(self): 228 | return str(self.number) 229 | 230 | def addVerse(self, verse): 231 | self.verses.append(verse) 232 | 233 | def search(self, string): 234 | ret = [] 235 | string = string.lower() 236 | for verse in self.verses: 237 | if string in verse.text.lower(): 238 | ret.append(verse) 239 | return ret 240 | 241 | def search_chapters(self, string): 242 | ret = [] 243 | string = string.lower() 244 | found_match = False 245 | for verse in self.verses: 246 | if string in verse.text.lower(): 247 | found_match = True 248 | break 249 | if not found_match: 250 | return None 251 | copy = Chapter(self.number) 252 | for verse in self.verses: 253 | copy.addVerse(verse) 254 | return copy 255 | 256 | def sort(self): 257 | self.verses.sort(key=sort_verse) 258 | 259 | 260 | class Verse: 261 | def __init__(self, bookNumber, chapter, verse, text): 262 | 263 | self.bookNumber = bookNumber 264 | self.chapter = chapter 265 | self.verse = verse 266 | self.text = text 267 | 268 | def getText(self): 269 | return self.text 270 | 271 | def __str__(self): 272 | return "Verse(" + str(self.bookNumber) + ", " + str(self.chapter) + \ 273 | ", " + str(self.verse) + ", " + self.text + ")" 274 | 275 | 276 | class Story: 277 | def __init__(self, bookNumber, chapter, verse, order, text): 278 | 279 | self.bookNumber = bookNumber 280 | self.chapter = chapter 281 | self.verse = verse 282 | self.text = text 283 | self.order = order 284 | 285 | def getText(self): 286 | return self.text 287 | 288 | def __str__(self): 289 | return "Story(" + str(self.bookNumber) + ", " + str(self.chapter) + \ 290 | ", " + str(self.verse) + ", " + str(self.order) + ", " + self.text + ")" 291 | 292 | 293 | class bibleSearchResults: 294 | def __init__(self): 295 | self.sameBookNumber = True 296 | self.sameChapter = True 297 | self.results = [] 298 | 299 | def __add__(self, other): 300 | for result in other.results: 301 | self.add(result) 302 | 303 | def add(self, result): 304 | self.results.apped(result) 305 | if not result.bookNumber == self.results[0].bookNumber: 306 | self.sameBookNumber = False 307 | if not result.chapter == self.results[0].chapter: 308 | self.sameBookNumber = False 309 | if not result.verse == self.results[0].verse: 310 | self.sameBookNumber = False 311 | -------------------------------------------------------------------------------- /src/mybible_to_markdown.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | bible = None 4 | def setBible(bible_in): 5 | global bible 6 | bible = bible_in 7 | 8 | class tag_renderer: 9 | def __init__(self, tag_name, pre_tag, post_tag): 10 | self.pre_tag = pre_tag 11 | self.post_tag = post_tag 12 | self.clear_format = False 13 | self.tag_name = tag_name 14 | 15 | def convert_to_markdown(self, tag): 16 | ret = "" 17 | for i in tag.children: 18 | ret += i.convert_to_markdown() 19 | return self.pre_tag + ret + self.post_tag 20 | 21 | def convert_to_text(self, tag): 22 | ret = "" 23 | for i in tag.children: 24 | ret += i.convert_to_markdown() 25 | return ret 26 | 27 | 28 | class one_tag_renderer: 29 | def __init__(self, tag_name, ret_tag, ret_text): 30 | self.ret_tag = ret_tag 31 | self.ret_text = ret_text 32 | self.clear_format = False 33 | self.tag_name = tag_name 34 | 35 | def convert_to_markdown(self, tag): 36 | return self.ret_tag 37 | 38 | def convert_to_text(self, tag): 39 | return self.ret_text 40 | 41 | 42 | class tag_renderer_clear: 43 | def __init__(self, tag_name, pre_tag, post_tag): 44 | self.pre_tag = pre_tag 45 | self.post_tag = post_tag 46 | self.clear_format = True 47 | self.tag_name = tag_name 48 | 49 | def convert_to_markdown(self, tag): 50 | ret = "" 51 | for i in tag.children: 52 | ret += i.convert_to_markdown() 53 | return self.pre_tag + ret + self.post_tag 54 | 55 | def convert_to_text(self, tag): 56 | ret = "" 57 | for i in tag.children: 58 | ret += i.convert_to_markdown() 59 | return ret 60 | 61 | 62 | class x_tag_parser: 63 | def __init__(self): 64 | self.clear_format = True 65 | self.tag_name = 'x' 66 | 67 | def convert_to_markdown(self, tag): 68 | global bible 69 | ret = "" 70 | for i in tag.children: 71 | ret += i.convert_to_markdown() 72 | verse = ret.split(" ",1) 73 | num = int(verse[0]) 74 | book = "" 75 | if bible is not None: 76 | book = bible.getBookName(num) 77 | return '' + book + " " + verse[1] + '' 78 | 79 | def convert_to_text(self, tag): 80 | global bible 81 | ret = "" 82 | for i in tag.children: 83 | ret += i.convert_to_markdown() 84 | verse = ret.split(" ",1) 85 | num = int(verse[0]) 86 | book = "" 87 | if bible is not None: 88 | book = bible.getBookName(num) 89 | return book + " " + verse[1] 90 | 91 | usable_tags = { 92 | 'pb': one_tag_renderer('pb', '\r', '\r'), 93 | 'br': one_tag_renderer('br', '\r', '\r'), 94 | 'f': one_tag_renderer('f', '', ''), 95 | 't': tag_renderer('t', '', '\r'), 96 | 'J': tag_renderer('J', '', ''), 97 | 'n': tag_renderer('n', '', ''), 98 | 'Text': tag_renderer_clear('Text', '', ''), 99 | 'Title': tag_renderer_clear('Title', '', ''), 100 | 'i': tag_renderer('i', '', ''), 101 | 'e': tag_renderer('e', '', ''), 102 | 'sup': tag_renderer('sup', '', ' '), 103 | 'S': one_tag_renderer('S', '', ''), 104 | 'h': tag_renderer_clear('h', '', ''), 105 | 'x': x_tag_parser(), 106 | } 107 | 108 | 109 | class text: 110 | def __init__(self, text): 111 | self.text = text 112 | 113 | def convert_to_markdown(self): 114 | return self.text 115 | 116 | def convert_to_text(self): 117 | return self.text 118 | 119 | def is_whitespace(self): 120 | return self.text.isspace() 121 | 122 | 123 | class tag: 124 | def __init__(self, tag_name): 125 | self.tag = tag_name 126 | self.children = [] 127 | 128 | def add_sub_tag(self, child): 129 | self.children.append(child) 130 | 131 | def convert_to_markdown(self): 132 | if self.tag in usable_tags: 133 | return usable_tags[self.tag].convert_to_markdown(self) 134 | else: 135 | print("un known tag", self.tag) 136 | return "" 137 | 138 | def convert_to_text(self): 139 | if self.tag in usable_tags: 140 | return usable_tags[self.tag].convert_to_text(self) 141 | else: 142 | print("un known tag", self.tag) 143 | return "" 144 | 145 | def is_whitespace(self): 146 | return self.tag == "pb" or self.tag == "br" or self.tag not in usable_tags 147 | 148 | def has_tag(self, string): 149 | 150 | #print(self.tag, string) 151 | if self.tag == string: 152 | return True 153 | for i in self.children: 154 | #print(isinstance(i, tag) and i.has_tag(string)) 155 | if isinstance(i, tag) and i.has_tag(string): 156 | return True 157 | return False 158 | 159 | 160 | class root: 161 | def __init__(self): 162 | self.children = [] 163 | self.tag = "text" 164 | 165 | def add_sub_tag(self, child): 166 | self.children.append(child) 167 | 168 | def __str__(self): 169 | return "The amount of children are: " + str(len(self.children)) 170 | 171 | def convert_to_markdown(self): 172 | ret = "" 173 | for i in self.children: 174 | ret += i.convert_to_markdown() 175 | return ret 176 | 177 | def convert_to_text(self): 178 | ret = "" 179 | for i in self.children: 180 | ret += i.convert_to_text() 181 | return ret 182 | 183 | def remove_pre(self): 184 | while len(self.children) > 0 and self.children[0].is_whitespace(): 185 | del self.children[0] 186 | 187 | def insert_number(self, verse_number): 188 | i = 0 189 | while i < len(self.children) and self.children[i].is_whitespace(): 190 | i += 1 191 | new_tag = tag("sup") 192 | new_tag.add_sub_tag(text(str(verse_number))) 193 | self.children.insert(i, new_tag) 194 | 195 | def has_tag(self, string): 196 | for i in self.children: 197 | # print(i) 198 | #print(isinstance(i, tag) and i.has_tag(string)) 199 | if isinstance(i, tag) and i.has_tag(string): 200 | return True 201 | 202 | return False 203 | 204 | 205 | def parse_text_tags(string, parent): 206 | children = [] 207 | while string is not None and len(string) > 0: 208 | if '<' == string[0]: 209 | # print("if") 210 | if '/' == string[1]: 211 | #print("closing tag") 212 | return string 213 | #print("new tag") 214 | # print(parse_tag(string)) 215 | # return 216 | string, nothing = parse_tag(string) 217 | parent.add_sub_tag(nothing) 218 | else: 219 | # print("Parse_String") 220 | string, nothing = parse_string(string) 221 | parent.add_sub_tag(nothing) 222 | return string 223 | 224 | 225 | def parse_string(string): 226 | new_string = "" 227 | while len(string) > 0: 228 | #print("String char", string[0]) 229 | if '<' == string[0]: 230 | #print("String", new_string) 231 | return string, text(new_string) 232 | else: 233 | #print("string else", string[0]) 234 | new_string += string[0] 235 | string = string[1:] 236 | #print("String", new_string) 237 | return string, text(new_string) 238 | 239 | 240 | def parse_tag(string): 241 | string = string[1:] 242 | tag_name = "" 243 | closed = False 244 | while string is not None: 245 | if '>' == string[0]: 246 | string = string[1:] 247 | break 248 | elif '/' == string[0] and '>' == string[1]: 249 | string = string[2:] 250 | closed = True 251 | break 252 | 253 | else: 254 | tag_name += string[0] 255 | string = string[1:] 256 | #print("Tag Name: ", tag_name) 257 | new_tag = tag(tag_name) 258 | if closed: 259 | #print("self closed tag") 260 | return (string, new_tag) 261 | string = parse_text_tags(string, new_tag) 262 | #print("Almost Done") 263 | # print(string[:2]) 264 | if string is None: 265 | return (string, new_tag) 266 | 267 | string = string[2:] 268 | tag_name = "" 269 | while string is not None and len(string) > 0: 270 | if '>' == string[0]: 271 | #print("Closing tag Name: ", tag_name) 272 | return (string[1:], new_tag) 273 | else: 274 | tag_name += string[0] 275 | string = string[1:] 276 | #print("Closing tag Name: ", tag_name) 277 | return (string, new_tag) 278 | 279 | def get_all_tags(tb,tags): 280 | 281 | apply_tags = [] 282 | for tag_name in reversed(tags): 283 | if tb.get_tag_table().lookup(tag_name) is not None: 284 | apply_tags.append(tag_name) 285 | if tag_name in ["Title","Text","n"]: 286 | break 287 | return apply_tags 288 | 289 | def tree_to_text_buff(tb, tags, tree): 290 | tags = tags[:] 291 | tags.append(tree.tag) 292 | for i in tree.children: 293 | if isinstance(i, tag): 294 | if i.tag == "br" or i.tag == "pb": 295 | start, end = tb.get_bounds() 296 | tb.insert(end, '\r') 297 | elif i.tag == "x": 298 | ret = "" 299 | for ii in i.children: 300 | ret += ii.convert_to_text() 301 | verse = ret.split(" ",1) 302 | num = int(verse[0]) 303 | book = "" 304 | if bible is not None: 305 | book = bible.getBookName(num) 306 | apply_tags = get_all_tags(tb,tags) 307 | verse_text = book + " " + verse[1] 308 | #print(apply_tags, verse_text) 309 | start, end = tb.get_bounds() 310 | apply_tags.append("underline") 311 | tb.insert_with_tags_by_name(end, verse_text, *apply_tags) 312 | #return '' + book + " " + verse[1] + '' 313 | elif i.tag == "sup": 314 | start, end = tb.get_bounds() 315 | tb.insert(end, ' ') 316 | tree_to_text_buff(tb,tags,i) 317 | start, end = tb.get_bounds() 318 | tb.insert(end, ' ') 319 | else: 320 | tree_to_text_buff(tb,tags,i) 321 | if isinstance(i, text): 322 | apply_tags = get_all_tags(tb,tags) 323 | #print(apply_tags, i.text) 324 | start, end = tb.get_bounds() 325 | tb.insert_with_tags_by_name(end, i.text, *apply_tags) 326 | #print(i) 327 | 328 | def convert_mybible_to_text_buff( 329 | string, 330 | verse_number, 331 | remove_pre_whitespace, 332 | remove_post_whitespace, 333 | tb, 334 | root_tag_name): 335 | r = root() 336 | while string is not None and len(string) != 0: 337 | string = parse_text_tags(string, r) 338 | if string is not None and len(string) != 0: 339 | string = string.split(">",1)[1] 340 | if remove_pre_whitespace: 341 | r.remove_pre() 342 | if verse_number is not None: 343 | r.insert_number(verse_number) 344 | r.tag = root_tag_name 345 | #print_tree(r) 346 | textbuff = tree_to_text_buff(tb,[],r) 347 | #print(text) 348 | return textbuff 349 | 350 | def convert_mybible_to_markdown( 351 | string, 352 | verse_number, 353 | remove_pre_whitespace, 354 | remove_post_whitespace): 355 | r = root() 356 | while string is not None and len(string) != 0: 357 | string = parse_text_tags(string, r) 358 | if string is not None and len(string) != 0: 359 | string = string.split(">",1)[1] 360 | if remove_pre_whitespace: 361 | r.remove_pre() 362 | if verse_number is not None: 363 | r.insert_number(verse_number) 364 | #print_tree(r) 365 | text = r.convert_to_markdown() 366 | #print(text) 367 | return text 368 | 369 | 370 | def convert_mybible_to_text( 371 | string, 372 | verse_number, 373 | remove_pre_whitespace, 374 | remove_post_whitespace): 375 | r = root() 376 | while string is not None and len(string) != 0: 377 | string = parse_text_tags(string, r) 378 | if string is not None and len(string) != 0: 379 | string = string.split(">",1)[1] 380 | if remove_pre_whitespace: 381 | r.remove_pre() 382 | if verse_number is not None: 383 | r.insert_number(verse_number) 384 | return r.convert_to_text() 385 | 386 | 387 | def has_tag(string, text_tag, remove_pre_whitespace): 388 | r = root() 389 | while string is not None and len(string) != 0: 390 | string = parse_text_tags(string, r) 391 | if string is not None and len(string) != 0: 392 | string = string.split(">",1)[1] 393 | if remove_pre_whitespace: 394 | r.remove_pre() 395 | return r.has_tag(text_tag) 396 | 397 | 398 | def print_tree(tree,depth=""): 399 | print(depth, isinstance(tree,tag)) 400 | print(depth, isinstance(tree,root)) 401 | if isinstance(tree,root): 402 | print(depth, 'ROOT') 403 | if isinstance(tree,tag): 404 | print(depth, tree.tag) 405 | if isinstance(tree,tag) or isinstance(tree,root): 406 | for i in tree.children: 407 | print_tree(i, depth + '\t') 408 | 409 | 410 | -------------------------------------------------------------------------------- /src/Audio_Player.py: -------------------------------------------------------------------------------- 1 | from gi.repository import Gst, GObject, Gio, GLib, Gtk 2 | import re 3 | import gi 4 | gi.require_version('Gst', '1.0') 5 | 6 | 7 | class DBusInterface: 8 | 9 | def __init__(self, name, path, application): 10 | """Etablish a D-Bus session connection 11 | 12 | :param str name: interface name 13 | :param str path: object path 14 | :param GtkApplication application: The Application object 15 | """ 16 | self._path = path 17 | self._signals = None 18 | Gio.bus_get(Gio.BusType.SESSION, None, self._bus_get_sync, name) 19 | 20 | def _bus_get_sync(self, source, res, name): 21 | try: 22 | self._con = Gio.bus_get_finish(res) 23 | except GLib.Error as e: 24 | print( 25 | "Unable to connect to to session bus: {}".format(e.message)) 26 | return 27 | 28 | Gio.bus_own_name_on_connection( 29 | self._con, name, Gio.BusNameOwnerFlags.NONE, None, None) 30 | 31 | method_outargs = {} 32 | method_inargs = {} 33 | signals = {} 34 | for interface in Gio.DBusNodeInfo.new_for_xml(self.__doc__).interfaces: 35 | 36 | for method in interface.methods: 37 | method_outargs[method.name] = "(" + "".join( 38 | [arg.signature for arg in method.out_args]) + ")" 39 | method_inargs[method.name] = tuple( 40 | arg.signature for arg in method.in_args) 41 | 42 | for signal in interface.signals: 43 | args = {arg.name: arg.signature for arg in signal.args} 44 | signals[signal.name] = { 45 | 'interface': interface.name, 'args': args} 46 | 47 | self._con.register_object( 48 | object_path=self._path, interface_info=interface, 49 | method_call_closure=self._on_method_call) 50 | 51 | self._method_inargs = method_inargs 52 | self._method_outargs = method_outargs 53 | self._signals = signals 54 | 55 | def _on_method_call( 56 | self, connection, sender, object_path, interface_name, method_name, 57 | parameters, invocation): 58 | """GObject.Closure to handle incoming method calls. 59 | 60 | :param Gio.DBusConnection connection: D-Bus connection 61 | :param str sender: bus name that invoked the method 62 | :param srt object_path: object path the method was invoked on 63 | :param str interface_name: name of the D-Bus interface 64 | :param str method_name: name of the method that was invoked 65 | :param GLib.Variant parameters: parameters of the method invocation 66 | :param Gio.DBusMethodInvocation invocation: invocation 67 | """ 68 | args = list(parameters.unpack()) 69 | for i, sig in enumerate(self._method_inargs[method_name]): 70 | if sig == 'h': 71 | msg = invocation.get_message() 72 | fd_list = msg.get_unix_fd_list() 73 | args[i] = fd_list.get(args[i]) 74 | 75 | method_snake_name = DBusInterface.camelcase_to_snake_case(method_name) 76 | try: 77 | result = getattr(self, method_snake_name)(*args) 78 | except ValueError as e: 79 | print(method_snake_name) 80 | invocation.return_dbus_error(interface_name, str(e)) 81 | return 82 | result = (result,) 83 | 84 | out_args = self._method_outargs[method_name] 85 | if out_args != '()': 86 | variant = GLib.Variant(out_args, result) 87 | invocation.return_value(variant) 88 | else: 89 | invocation.return_value(None) 90 | 91 | def _dbus_emit_signal(self, signal_name, values): 92 | if self._signals is None: 93 | return 94 | signal = self._signals[signal_name] 95 | parameters = [] 96 | for arg_name, arg_signature in signal['args'].items(): 97 | value = values[arg_name] 98 | parameters.append(GLib.Variant(arg_signature, value)) 99 | 100 | variant = GLib.Variant.new_tuple(*parameters) 101 | self._con.emit_signal( 102 | None, self._path, signal['interface'], signal_name, variant) 103 | 104 | @staticmethod 105 | def camelcase_to_snake_case(name): 106 | s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) 107 | return '_' + re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() 108 | 109 | 110 | class Player(DBusInterface): 111 | ''' 112 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | ''' 195 | MEDIA_PLAYER2_IFACE = 'org.mpris.MediaPlayer2' 196 | MEDIA_PLAYER2_PLAYER_IFACE = 'org.mpris.MediaPlayer2.Player' 197 | MEDIA_PLAYER2_TRACKLIST_IFACE = 'org.mpris.MediaPlayer2.TrackList' 198 | MEDIA_PLAYER2_PLAYLISTS_IFACE = 'org.mpris.MediaPlayer2.Playlists' 199 | 200 | _playlist_nb_songs = 10 201 | 202 | def __init__(self, app): 203 | name = "org.mpris.MediaPlayer2.{}".format("Bible") 204 | path = '/org/mpris/MediaPlayer2' 205 | self.file = "" 206 | super().__init__(name, path, app) 207 | 208 | self._app = app 209 | 210 | self._title = "Title" 211 | self._album = "Album" 212 | self._artist = ["Artist"] 213 | self._album_artist = ["Artist"] 214 | self.playbin = None 215 | 216 | def _properties_changed(self, interface_name, changed_properties, 217 | invalidated_properties): 218 | self.__bus.emit_signal(None, 219 | self.__MPRIS_PATH, 220 | "org.freedesktop.DBus.Properties", 221 | "PropertiesChanged", 222 | GLib.Variant.new_tuple( 223 | GLib.Variant("s", interface_name), 224 | GLib.Variant("a{sv}", changed_properties), 225 | GLib.Variant("as", invalidated_properties))) 226 | 227 | def _get_metadata(self, coresong=None, index=None): 228 | # print(self._artist) 229 | metadata = { 230 | 'xesam:url': GLib.Variant('s', self.file), 231 | 'mpris:length': GLib.Variant('x', 0), 232 | 'xesam:title': GLib.Variant('s', self._title), 233 | 'xesam:album': GLib.Variant('s', self._album), 234 | 'xesam:artist': GLib.Variant('as', self._artist), 235 | 'xesam:albumArtist': GLib.Variant('as', self._album_artist) 236 | } 237 | if self.playbin is not None: 238 | # print(self.playbin.query_duration(Gst.Format.TIME)) 239 | rc, duration = self.playbin.query_duration(Gst.Format.TIME) 240 | if rc: 241 | metadata['mpris:length'] = GLib.Variant('i', duration / 1000) 242 | return metadata 243 | 244 | def _get(self, interface_name, property_name): 245 | try: 246 | return self._get_all(interface_name)[property_name] 247 | except KeyError: 248 | msg = "MPRIS does not handle {} property from {} interface".format( 249 | property_name, interface_name) 250 | self._log.warning(msg) 251 | raise ValueError(msg) 252 | 253 | def _get_all(self, interface_name): 254 | if interface_name == Player.MEDIA_PLAYER2_IFACE: 255 | application_id = self._app.props.application_id 256 | return { 257 | 'CanQuit': GLib.Variant('b', True), 258 | 'Fullscreen': GLib.Variant('b', False), 259 | 'CanSetFullscreen': GLib.Variant('b', False), 260 | 'CanRaise': GLib.Variant('b', True), 261 | 'HasTrackList': GLib.Variant('b', False), 262 | 'Identity': GLib.Variant('s', 'Bible'), 263 | 'DesktopEntry': GLib.Variant('s', application_id), 264 | 'SupportedUriSchemes': GLib.Variant('as', [ 265 | 'file' 266 | ]), 267 | 'SupportedMimeTypes': GLib.Variant('as', [ 268 | 'application/ogg', 269 | 'audio/x-vorbis+ogg', 270 | 'audio/x-flac', 271 | 'audio/mpeg' 272 | ]), 273 | } 274 | elif interface_name == Player.MEDIA_PLAYER2_PLAYER_IFACE: 275 | position_msecond = 0 276 | playback_status = "Playing" 277 | is_shuffle = False 278 | can_play = True 279 | has_previous = False 280 | return { 281 | 'PlaybackStatus': GLib.Variant('s', self.get_status()), 282 | 'LoopStatus': GLib.Variant('s', "None"), 283 | 'Rate': GLib.Variant('d', 1.0), 284 | 'Shuffle': GLib.Variant('b', is_shuffle), 285 | 'Metadata': GLib.Variant('a{sv}', self._get_metadata()), 286 | 'Position': GLib.Variant('x', position_msecond), 287 | 'MinimumRate': GLib.Variant('d', 1.0), 288 | 'MaximumRate': GLib.Variant('d', 1.0), 289 | 'CanGoNext': GLib.Variant('b', True), 290 | 'CanGoPrevious': GLib.Variant('b', True), 291 | 'CanPlay': GLib.Variant('b', self.has_content()), 292 | 'CanPause': GLib.Variant('b', self.has_content()), 293 | 'CanSeek': GLib.Variant('b', True), 294 | 'CanControl': GLib.Variant('b', True), 295 | } 296 | elif interface_name == 'org.freedesktop.DBus.Properties': 297 | return {} 298 | elif interface_name == 'org.freedesktop.DBus.Introspectable': 299 | return {} 300 | else: 301 | self._log.warning( 302 | "MPRIS does not implement {} interface".format(interface_name)) 303 | 304 | def _set(self, interface_name, property_name, new_value): 305 | if interface_name == MPRIS.MEDIA_PLAYER2_IFACE: 306 | if property_name == 'Fullscreen': 307 | pass 308 | elif interface_name == MPRIS.MEDIA_PLAYER2_PLAYER_IFACE: 309 | if property_name in ['Rate', 'Volume', 'LoopStatus', 'Shuffle']: 310 | pass 311 | else: 312 | self._log.warning( 313 | "MPRIS does not implement {} interface".format(interface_name)) 314 | 315 | def _properties_changed(self, interface_name, changed_properties, 316 | invalidated_properties): 317 | parameters = { 318 | 'interface_name': interface_name, 319 | 'changed_properties': changed_properties, 320 | 'invalidated_properties': invalidated_properties 321 | } 322 | self._dbus_emit_signal('PropertiesChanged', parameters) 323 | 324 | def _introspect(self): 325 | return self.__doc__ 326 | 327 | def get_status(self): 328 | if self.playbin is None: 329 | return "Stopped" 330 | (ret, state, pending) = self.playbin.get_state(Gst.CLOCK_TIME_NONE) 331 | if state == Gst.State.PLAYING: 332 | return "Playing" 333 | elif state == Gst.State.PAUSED: 334 | return "Paused" 335 | else: 336 | return "Stopped" 337 | 338 | def has_content(self): 339 | return self.playbin is not None 340 | 341 | def _next(self): 342 | if self._app._window: 343 | self._app._window.next() 344 | 345 | def _previous(self): 346 | if self._app._window: 347 | self._app._window.previous() 348 | 349 | cb = None 350 | 351 | def start_file(self, sound): 352 | # pathname2url escapes non-URL-safe characters 353 | import os 354 | from urllib.request import pathname2url 355 | 356 | Gst.init(None) 357 | if self.playbin is not None: 358 | self.end() 359 | self.playbin = Gst.ElementFactory.make('playbin', 'playbin3') 360 | if sound.startswith(('http://', 'https://')): 361 | self.playbin.props.uri = sound 362 | else: 363 | self.playbin.props.uri = 'file://' + \ 364 | pathname2url(os.path.abspath(sound)) 365 | self.file = sound 366 | self.loop = GObject.MainLoop() 367 | bus = self.playbin.get_bus() 368 | bus.add_signal_watch() 369 | bus.connect("message", self.bus_call, self.loop) 370 | properties = { 371 | 'CanPlay': GLib.Variant('b', self.has_content()), 372 | 'CanPause': GLib.Variant('b', self.has_content())} 373 | self._properties_changed( 374 | self.MEDIA_PLAYER2_PLAYER_IFACE, properties, []) 375 | 376 | def bus_call(self, bus, message, loop): 377 | t = message.type 378 | if t == Gst.MessageType.EOS: 379 | # print(t) 380 | self.loop.quit() 381 | if self.cb is not None: 382 | self.cb() 383 | elif t == Gst.MessageType.STATE_CHANGED: 384 | # print(message) 385 | old_state, new_state, pending_state = message.parse_state_changed() 386 | if self.state_change_cb is not None: 387 | self.state_change_cb() 388 | # print(old_state, new_state, pending_state) 389 | if old_state == Gst.State.NULL: 390 | properties = { 391 | "Metadata": GLib.Variant( 392 | "a{sv}", self._get_metadata())} 393 | self._properties_changed( 394 | self.MEDIA_PLAYER2_PLAYER_IFACE, properties, []) 395 | pass 396 | else: 397 | #print("Some other message type: " + str(message.type)) 398 | pass 399 | return True 400 | 401 | def set_title(self, title): 402 | self._title = title 403 | properties = {"Metadata": GLib.Variant("a{sv}", self._get_metadata())} 404 | self._properties_changed( 405 | self.MEDIA_PLAYER2_PLAYER_IFACE, properties, []) 406 | 407 | def set_artist(self, artist): 408 | self._artist = artist 409 | self._album_artist = artist 410 | properties = {"Metadata": GLib.Variant("a{sv}", self._get_metadata())} 411 | self._properties_changed( 412 | self.MEDIA_PLAYER2_PLAYER_IFACE, properties, []) 413 | 414 | def set_album(self, album): 415 | self._album = album 416 | properties = {"Metadata": GLib.Variant("a{sv}", self._get_metadata())} 417 | self._properties_changed( 418 | self.MEDIA_PLAYER2_PLAYER_IFACE, properties, []) 419 | 420 | def add_callback(self, cb): 421 | self.cb = cb 422 | 423 | def add_state_change_callback(self, cb): 424 | self.state_change_cb = cb 425 | 426 | def _play(self): 427 | set_result = self.playbin.set_state(Gst.State.PLAYING) 428 | if set_result == Gst.StateChangeReturn.FAILURE: 429 | raise PlaysoundException( 430 | "playbin.set_state returned " + repr(set_result)) 431 | properties = {"PlaybackStatus": GLib.Variant("s", self.get_status())} 432 | self._properties_changed( 433 | self.MEDIA_PLAYER2_PLAYER_IFACE, properties, []) 434 | 435 | def _pause(self): 436 | set_result = self.playbin.set_state(Gst.State.PAUSED) 437 | if set_result == Gst.StateChangeReturn.FAILURE: 438 | raise PlaysoundException( 439 | "playbin.set_state returned " + repr(set_result)) 440 | properties = {"PlaybackStatus": GLib.Variant("s", self.get_status())} 441 | self._properties_changed( 442 | self.MEDIA_PLAYER2_PLAYER_IFACE, properties, []) 443 | 444 | def _play_pause(self): 445 | (ret, state, pending) = self.playbin.get_state(Gst.CLOCK_TIME_NONE) 446 | if state == Gst.State.PLAYING: 447 | self._pause() 448 | else: 449 | self._play() 450 | 451 | def _seek(self, time): 452 | print("_seek", time) 453 | success, position = self.playbin.query_position(Gst.Format.TIME) 454 | if not success: 455 | raise GenericException( 456 | "Couldn't fetch current song position to update slider") 457 | print(position, time * 1000) 458 | print(position + time * 1000) 459 | if position + time * 1000 > 0: 460 | self.playbin.seek_simple( 461 | Gst.Format.TIME, 462 | Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, 463 | position + time * 1000) 464 | else: 465 | self.playbin.seek_simple( 466 | Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, 0) 467 | 468 | def _raise(self): 469 | self._app.raide_main_window() 470 | 471 | def end(self): 472 | if self.playbin is None: 473 | return 474 | set_result = self.playbin.set_state(Gst.State.NULL) 475 | if set_result == Gst.StateChangeReturn.FAILURE: 476 | raise PlaysoundException( 477 | "playbin.set_state returned " + repr(set_result)) 478 | else: 479 | self.playbin = None 480 | properties = { 481 | 'CanPlay': GLib.Variant('b', self.has_content()), 482 | 'CanPause': GLib.Variant('b', self.has_content())} 483 | self._properties_changed( 484 | self.MEDIA_PLAYER2_PLAYER_IFACE, properties, []) 485 | 486 | def getstate(self): 487 | if self.playbin is not None: 488 | (ret, state, pending) = self.playbin.get_state(Gst.CLOCK_TIME_NONE) 489 | return state 490 | else: 491 | return Gst.State.NULL 492 | --------------------------------------------------------------------------------