├── databases └── index.php ├── output └── index.php ├── .gitignore ├── resources ├── propresenter6_group_element.xml ├── propresenter6_slide_text.txt ├── propresenter6_file_wrapper.xml ├── propresenter6_slide_element.xml ├── Amazing Grace.pro6 └── Amazing Grace expanded.pro6 ├── process.php ├── config.php ├── README.md └── functions.php /databases/index.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /output/index.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.dat 2 | *.db 3 | *.zip 4 | databases/*/ 5 | output/*/*.txt 6 | output/*/*.pro6 7 | test/ -------------------------------------------------------------------------------- /resources/propresenter6_group_element.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | %%SLIDES%% 4 | 5 | -------------------------------------------------------------------------------- /resources/propresenter6_slide_text.txt: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf830 2 | {\fonttbl\f0\fswiss\fcharset0 ArialMT;} 3 | {\colortbl;\red255\green255\blue255;\red255\green255\blue255;\red51\green51\blue51;} 4 | {\*\expandedcolortbl;;\csgray\c100000;\csgenericrgb\c20000\c20000\c20000;} 5 | \pard\slleading320\pardirnatural\qc\partightenfactor0 6 | 7 | \f0\b\fs127\fsmilli63999 \cf2 \expnd0\expndtw0\kerning0 8 | \outl0\strokewidth-80 \strokec3 %%TEXT%%} -------------------------------------------------------------------------------- /resources/propresenter6_file_wrapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %%GROUPS%% 9 | 10 | 11 | -------------------------------------------------------------------------------- /resources/propresenter6_slide_element.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {0 100 0 1920 864} 6 | 0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506} 7 | 8 | 0 0 0 1 9 | 1.000000 10 | 11 | %%TEXT%% 12 | 13 | 14 | -------------------------------------------------------------------------------- /process.php: -------------------------------------------------------------------------------- 1 | 9 | * @version 0.2 10 | * @link https://github.com/jamesinglis/ew61-export 11 | */ 12 | 13 | require 'config.php'; 14 | require 'functions.php'; 15 | 16 | $connection_songs = 'sqlite:' . $song_db_path; 17 | $dbh = new PDO('sqlite:' . $song_db_path) or die("cannot open the database"); 18 | $dbh_lyrics = new PDO('sqlite:' . $song_words_db_path) or die("cannot open the database"); 19 | $query = "SELECT * FROM song"; 20 | 21 | if ($test_single_song_id && is_numeric($test_single_song_id)) { 22 | $query .= " WHERE rowid = " . $test_single_song_id; 23 | } 24 | 25 | $songs = []; 26 | 27 | foreach ($dbh->query($query) as $song) { 28 | $songs[$song['rowid']] = [ 29 | 'id' => $song['rowid'], 30 | 'title' => process_ew_title($song['title']) 31 | ]; 32 | } 33 | 34 | $counter = 0; 35 | 36 | foreach ($songs as $id => $song) { 37 | $query2 = "SELECT words FROM word WHERE song_id = $id"; 38 | foreach ($dbh_lyrics->query($query2) as $lyrics) { 39 | $songs[$id]['text'] = process_ew_lyrics($lyrics['words']); 40 | if ($file_export_type === 'propresenter6') { 41 | $songs[$id]['text'] = generate_prop6_file_contents($songs[$id]); 42 | } 43 | save_text_file($songs[$id], $file_export_type); 44 | } 45 | if ($test_mode && $counter > 8) { 46 | break; 47 | } 48 | $counter++; 49 | } -------------------------------------------------------------------------------- /config.php: -------------------------------------------------------------------------------- 1 | false, // Capitalize some property names, defined in $words_to_capitalize 13 | 'remove_end_punctuation' => false, // Remove line-ending punctuation 14 | 'fix_mid_line_punctuation' => false, // Fix mid-line punctuation 15 | 'straighten_curly_quotes' => false, // Straighten curly quotes 16 | 'remove_x2' => false, // Remove 'x2' type references and empty parentheses 17 | 'start_with_capital' => false, // Begin all lines with capital letter 18 | 'standardize_song_sections' => false, // Standardize the names of the song sections to fit ProPresenter's defaults 19 | 'standardize_title_format' => false, // Standardize the formatting of the name of the songs 20 | 'prevent_overwrites' => true, // Prevent overwriting files - adds a '(1)' style suffix 21 | 'add_metadata_to_export_files' => true, // Adds the metadata block to the top of the export files 22 | 'condense_slide_breaks' => false, // Condense all slide breaks 23 | 'reflow_large_blocks' => false, // Reflow large blocks (target line length defined in $reflow_max_lines) 24 | 'output_subdirectory' => true, // Output files in a timestamp labelled sub-directory 25 | 'aggressive_text_encoding' => false, // Aggressively convert songs to ISO-8859-1 character set - this will most likely break songs with non-Latin characters! 26 | 'prop6_add_blank_intro' => false, // Adds a blank "Intro" slide to ProPresenter files 27 | 'prop6_add_blank_end' => false, // Adds a blank "End" slide to ProPresenter files 28 | 'prop6_add_hotkeys' => false, // Adds hot keys to ProPresenter files 29 | ); 30 | 31 | $reflow_max_lines = 2; // How many lines should we try to 'reflow' the text to? 32 | $file_export_type = 'plain_text'; // set to 'propresenter6' to use the experimental ProPresenter6 output 33 | 34 | $song_section_names = array('Verse', 'Chorus', 'Pre-Chorus', 'Bridge', 'Tag', 'Intro', 'End'); 35 | $words_to_capitalize = array('Jesus', 'God', 'Gud', 'Lord', 'You', 'Your', 'Du', 'Din', 'Ditt', 'Han', 'Hans', 'Ham', 'Holy Spirit', 'Father'); 36 | 37 | // This array controls which hot keys are assigned to which sections in ProPresenter exports 38 | $propresenter_hotkey_map = array( 39 | 'Title' => 'T', 40 | 'Intro' => 'I', 41 | 'Verse 1' => 'A', 42 | 'Verse 2' => 'S', 43 | 'Verse 3' => 'D', 44 | 'Verse 4' => 'F', 45 | 'Verse 5' => 'G', 46 | 'Verse 6' => 'H', 47 | 'Verse 7' => 'J', 48 | 'Verse 8' => 'K', 49 | 'Verse 9' => 'L', 50 | 'Pre-Chorus' => 'X', 51 | 'Chorus 1' => 'C', 52 | 'Chorus 2' => 'V', 53 | 'Chorus 3' => '', 54 | 'Bridge 1' => 'B', 55 | 'Bridge 2' => 'M', 56 | 'Tag' => 'N', 57 | 'End' => 'Z', 58 | ); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EasyWorship 6.1 Exporter 2 | 3 | * Author: James Inglis 4 | * URL: https://github.com/jamesinglis/ew61-export 5 | * License: MIT (i.e. do whatever you want with it, but no warranty!) 6 | 7 | 8 | ## Overview 9 | 10 | This PHP-based solution accesses the EasyWorship 6.1 database and exports all of the songs as plain text files suitable for importing into another lyric projection system (tested with ProPresenter 6.1). 11 | 12 | Previous versions of EasyWorship (including 6.0) used a different database storage engine. EasyWorship 6.1 now stores its databases in SQLite3 format, which meant that all other export utilities no longer worked (see below for Other Export Methods that work with previous versions). 13 | 14 | This solution was conceived when faced with an EasyWorship 6.1 library of 800+ songs which no-one wanted to retype! It caused me to look closely at the database format and come to the conclusion that the best solution was to extract the data from the tables and clean up the contents programmatically. 15 | 16 | 17 | ## Other Export Methods 18 | 19 | Previous versions of EasyWorship have had different 3rd party solutions that have sprung up. 20 | 21 | For further information about these, the following links may be useful: 22 | 23 | * EasySearch - http://www.headwest-productions.co.uk/Software.htm 24 | * OpenLP's song importer - https://manual.openlp.org/songs.html 25 | * Video Psalm - http://myvideopsalm.weebly.com/how-to-import-songs-and-bibles-from-other-sources.html 26 | * https://forums.openlp.org/discussion/831/import-easyworship-songs 27 | 28 | 29 | ## Getting Started 30 | 31 | To use this solution, you'll need to be comfortable with running PHP scripts. It doesn't have a graphical user interface, however you can run this from the command line or from a web browser. 32 | 33 | * Ensure that PHP has PDO SQLite support and MBString support (see https://github.com/jamesinglis/ew61-export/issues/1 for details - thanks jonathantjm!) 34 | * Clone this repository to a location on your computer that PHP has write access to. 35 | * Locate the EasyWorship database files in the EasyWorship data directory 36 | * This is commonly in C:\Users\Public\Softouch\EasyWorship\Default\Databases\Data\ but may vary 37 | * On my installation, the installer for EasyWorship 6.1 had created a new subfolder for the 6.1 profile at C:\Users\Public\Softouch\EasyWorship\Default\6.1\ 38 | * Copy the following files from your EasyWorship data directory to [this repository root]/databases/: 39 | * Songs.db 40 | * SongWords.db 41 | * Review the custom formatting options in config.php and set the values to true or false 42 | * The default settings will export your songs in plain text files. If you want to use the experimental ProPresenter6 export feature, change `$file_export_type = 'plain_text';` to `$file_export_type = 'propresenter6';` in config.php. 43 | * From the command line, change directory to the repository root and run: 44 | * php ./process.php 45 | * Alternatively, if the repository in a directory served by a web server, you may run process.php in a web browser. 46 | * The progress of the conversion is displayed on the screen 47 | * The exported files can be found in [this repository root]/output/ 48 | 49 | Note: this script does not write anything to the EasyWorship database files, however accidents can happen. To safeguard your existing data, make sure that you run this script on copies of your database files. I take no responsibility for any data loss that may occur directly or indirectly from using this script! 50 | 51 | 52 | ## Features 53 | 54 | * Exports all songs and lyrics from EasyWorship 6.1 database files into a date-labeled sub-directory in /output directory 55 | * Attempts to strip all formatting from song words to make for a clean import into target system 56 | * Outputs to text files by default with experimental ProPresenter6 export (specify $file_export_type in config.php) 57 | * Attempts to handle inconsistencies with text encoding 58 | * Reflow song sections over 2 lines long (can be changed in config file) 59 | * Custom formatting functions (needs to be enabled in config file) 60 | * 'capitalize_names' - Capitalize some property names (defined in $words_to_capitalize variable) 61 | * 'remove_end_punctuation' - Remove line-ending punctuation 62 | * 'fix_mid_line_punctuation' - Fixes mid-line punctuation - replaces '.' with a line break, and makes sure ',;\?!' is always followed by a space 63 | * 'straighten_curly_quotes' - Straighten curly quotes 64 | * 'remove_x2' - Remove 'x2' type references and empty parentheses 65 | * 'start_with_capital' - Begin all lines with capital letter 66 | * 'standardize_song_sections' - Standardize the names of the song sections to fit ProPresenter's default 'Groups' 67 | * 'standardize_title_format' - Standardize the formatting of the name of the songs 68 | * 'prevent_overwrites' - Prevent overwriting files - adds a '(1)' style suffix 69 | * 'add_metadata_to_export_files' - Adds the metadata block to the top of the export files 70 | * 'condense_slide_breaks' - Condense all slide breaks 71 | * 'reflow_large_blocks' - Reflow large blocks (target line length defined in $reflow_max_lines) 72 | * 'output_subdirectory' - Output files in a timestamp labelled sub-directory 73 | * 'aggressive_text_encoding' - Aggressively convert songs to ISO-8859-1 character set - this will most likely break songs with non-Latin characters! 74 | * ProPresenter-specific settings 75 | * 'prop6_add_blank_intro' - Adds a blank "Intro" slide to ProPresenter files (useful for adding cues and arrangements) 76 | * 'prop6_add_blank_end' - Adds a blank "End" slide to ProPresenter files (useful for adding cues and arrangements) 77 | * 'prop6_add_hotkeys' - Assign hot keys to slide groups 78 | 79 | ### NEW in v0.2 - Export direct to ProPresenter6 .pro6 fileformat 80 | 81 | * Creates a directory of .pro6 song files 82 | * Creates ProPresenter slide groups from EasyWorship song sections 83 | * Adds hot keys to ProPresenter slide groups following [Luke McElroy's 2014 recommendation](https://www.worshiphousemedia.com/church-media-blog/software/simple-effective-propresenter-hot-key-system/) 84 | * Title - T 85 | * Intro - I 86 | * Verse 1 - A 87 | * Verse 2 - S 88 | * Verse 3 - D 89 | * Verse 4 - F 90 | * Verse 5 - G 91 | * Verse 6 - H 92 | * Verse 7 - J 93 | * Verse 8 - K 94 | * Verse 9 - L 95 | * Pre-Chorus - X 96 | * Chorus 1 - C 97 | * Chorus 2 - V 98 | * Chorus 3 - 99 | * Bridge 1 - B 100 | * Bridge 2 - M 101 | * Tag - N 102 | * End - Z 103 | 104 | ### NEW in v0.3 - Better text export 105 | 106 | * I didn't realize until now that the data is stored in EasyWorship in standard RTF format! My manual functions to strip this formatting didn't work for everyone. Implementing a more robust RTF to text formatting solution. 107 | * ProPresenter hot keys are now managed in the config file - easier to override! 108 | 109 | ## To Do in Future 110 | (if there's a demand for it) 111 | 112 | * Clean up the code so it's cleaner and more extensible 113 | * Not the best coding - this started as a quick and dirty solution! 114 | * Export to other file formats suitable for other projection 115 | * Include all song metadata 116 | * My test library didn't have any song metadata so it wasn't a priority! 117 | 118 | 119 | ## Questions & Answers 120 | 121 | * Why is this written in PHP? Surely [insert language/framework here] would be a better choice! 122 | * You may be right, however this script was developed to meet an immediate need for myself and PHP is the language in which I am most proficient! 123 | * What version of PHP do I need to run this? 124 | * The solution was written for PHP 5.6. I have run conversions with it in December 2021 with PHP 8.0.12 (with minor change made in v0.3.2). 125 | * Why am I getting strange characters in my export files? 126 | * It's most likely a text-encoding issue. This script was developed for a non-English EasyWorship library so unicode characters should be handled correctly, however I can't make any promises! 127 | * For what it's worth, my CLI version of PHP 5.6 garbles the "Å" character but the web server version of the same PHP installation has no problems encoding this character. 128 | * Why am I getting random references to fonts at the top of my song exports? 129 | * This is a remnant of the RTF formatting - I'm manually stripping out references to Arial and Tahoma, but if you used a different font in EasyWorship, that font name may show up. If this happens, add the name of your font to the regex on line 130 of functions.php. 130 | 131 | ## Version history 132 | 133 | ### 0.3.2 (2021-12-07) 134 | * Tweak for PHP8 compatibility 135 | * Adds reference to PHP library dependencies answered in jonathantjm's issue 136 | * Update config and readme documentation 137 | 138 | ### 0.3.1 (2018-10-21) 139 | * Adds missing ProPresenter slide text template 140 | * Update config and readme documentation 141 | 142 | ### 0.3 (2018-04-11) 143 | 144 | * Adds real RTF processing to the song data from EasyWorship! 145 | * Move ProPresenter hot keys to config file. 146 | 147 | ### 0.2 (2018-03-02) 148 | 149 | * Numerous bug fixes from real-world use 150 | * Adds experimental ProPresenter6 export feature 151 | 152 | ### 0.1 (2016-09-03) 153 | 154 | * Initial version -------------------------------------------------------------------------------- /resources/Amazing Grace.pro6: -------------------------------------------------------------------------------- 1 | {51 108 0 1816 864}0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506}0 0 0 11.000000e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibH0Ke1xjb2xvcnRibDtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7fQp7XCpcZXhwYW5kZWRjb2xvcnRibDs7fQp9{51 108 0 1816 864}0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506}0 0 0 11.000000e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBBbWF6aW5nIGdyYWNlISBIb3cgc3dlZXQgdGhlIHNvdW5kXApUaGF0IHNhdmVkIGEgd3JldGNoIGxpa2UgbWUhfQ=={51 108 0 1816 864}0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506}0 0 0 11.000000e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBJIG9uY2Ugd2FzIGxvc3QsIGJ1dCBub3cgYW0gZm91bmQ7XApXYXMgYmxpbmQsIGJ1dCBub3cgSSBzZWUufQ=={51 108 0 1816 864}0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506}0 0 0 11.000000e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBcJzkyVHdhcyBncmFjZSB0aGF0IHRhdWdodCBteSBoZWFydCB0byBmZWFyLFwKQW5kIGdyYWNlIG15IGZlYXJzIHJlbGlldmVkO30={51 108 0 1816 864}0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506}0 0 0 11.000000e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBIb3cgcHJlY2lvdXMgZGlkIHRoYXQgZ3JhY2UgYXBwZWFyXApUaGUgaG91ciBJIGZpcnN0IGJlbGlldmVkLn0={51 108 0 1816 864}0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506}0 0 0 11.000000e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBUaHJvdWdoIG1hbnkgZGFuZ2VycywgdG9pbHMgYW5kIHNuYXJlcyxcCkkgaGF2ZSBhbHJlYWR5IGNvbWU7fQ=={51 108 0 1816 864}0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506}0 0 0 11.000000e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBcJzkyVGlzIGdyYWNlIGhhdGggYnJvdWdodCBtZSBzYWZlIHRodXMgZmFyLFwKQW5kIGdyYWNlIHdpbGwgbGVhZCBtZSBob21lLn0={51 108 0 1816 864}0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506}0 0 0 11.000000e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBUaGUgTG9yZCBoYXMgcHJvbWlzZWQgZ29vZCB0byBtZSxcCkhpcyBXb3JkIG15IGhvcGUgc2VjdXJlczt9{51 108 0 1816 864}0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506}0 0 0 11.000000e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBIZSB3aWxsIG15IFNoaWVsZCBhbmQgUG9ydGlvbiBiZSxcCkFzIGxvbmcgYXMgbGlmZSBlbmR1cmVzLn0={51 108 0 1816 864}0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506}0 0 0 11.000000e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBZZWEsIHdoZW4gdGhpcyBmbGVzaCBhbmQgaGVhcnQgc2hhbGwgZmFpbCxcCkFuZCBtb3J0YWwgbGlmZSBzaGFsbCBjZWFzZSx9{51 108 0 1816 864}0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506}0 0 0 11.000000e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBJIHNoYWxsIHBvc3Nlc3MsIHdpdGhpbiB0aGUgdmVpbCxcCkEgbGlmZSBvZiBqb3kgYW5kIHBlYWNlLn0={51 108 0 1816 864}0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506}0 0 0 11.000000e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBUaGUgZWFydGggc2hhbGwgc29vbiBkaXNzb2x2ZSBsaWtlIHNub3csXApUaGUgc3VuIGZvcmJlYXIgdG8gc2hpbmU7fQ=={51 108 0 1816 864}0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506}0 0 0 11.000000e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBCdXQgR29kLCB3aG8gY2FsbGVkIG1lIGhlcmUgYmVsb3csXApXaWxsIGJlIGZvcmV2ZXIgbWluZS59{51 108 0 1816 864}0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506}0 0 0 11.000000e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBXaGVuIHdlXCc5MnZlIGJlZW4gdGhlcmUgdGVuIHRob3VzYW5kIHllYXJzLFwKQnJpZ2h0IHNoaW5pbmcgYXMgdGhlIHN1bix9{51 108 0 1816 864}0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506}0 0 0 11.000000e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBXZVwnOTJ2ZSBubyBsZXNzIGRheXMgdG8gc2luZyBHb2RcJzkycyBwcmFpc2VcClRoYW4gd2hlbiB3ZVwnOTJkIGZpcnN0IGJlZ3VuLn0={51 108 0 1816 864}0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506}0 0 0 11.000000e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibH0Ke1xjb2xvcnRibDtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7fQp7XCpcZXhwYW5kZWRjb2xvcnRibDs7fQp9 -------------------------------------------------------------------------------- /functions.php: -------------------------------------------------------------------------------- 1 | 9 | * @version 0.2 10 | * @link https://github.com/jamesinglis/ew61-export 11 | */ 12 | 13 | 14 | // Set the file encoding for everything to UTF-8 15 | mb_detect_order(array('UTF-8', 'ISO-8859-1', 'ASCII')); 16 | ini_set('default_charset', 'utf-8'); 17 | mb_internal_encoding('UTF-8'); 18 | mb_http_output('UTF-8'); 19 | mb_http_input('S'); 20 | mb_regex_encoding('UTF-8'); 21 | 22 | define('EW6_EXPORT_EOL', "\r\n"); 23 | 24 | // Output files in a timestamp labelled sub-directory 25 | if ($custom_settings['output_subdirectory']) { 26 | $output_directory = __DIR__ . '/output/' . date('YmdHis') . '_' . $file_export_type . '/'; 27 | mkdir($output_directory, 0777, true); 28 | } 29 | 30 | 31 | /** 32 | * Formats the title of the song 33 | * 34 | * @param $title Raw title from EasyWorship data store 35 | * @return string Formatted title 36 | */ 37 | function process_ew_title($title) 38 | { 39 | global $custom_settings; 40 | 41 | $title = trim($title); // We always trim, whether you like it or not! 42 | $title = preg_replace('/\s+/', ' ', $title); // We also always condense any whitespace characters down to single spaces 43 | 44 | if ($custom_settings['standardize_title_format']) { 45 | $title = ucwords($title); 46 | $title = preg_replace("/\s{2,}/", ' ', $title); 47 | $lc_words = ['and', 'of', 'the']; 48 | foreach ($lc_words as $lc_word) { 49 | $title = str_replace(ucwords($lc_word), $lc_word, $title); 50 | } 51 | $title = ucfirst($title); 52 | } 53 | 54 | return $title; 55 | } 56 | 57 | /** 58 | * All processing and formatting for song text 59 | * 60 | * @param $text Raw text blob from EasyWorship data store 61 | * @return string Cleaned and formatted song lyric text 62 | */ 63 | function process_ew_lyrics($text) 64 | { 65 | global $custom_settings; 66 | 67 | // Process as much of the RTF content as possible before we break up into lines 68 | $text = process_unicode($text); 69 | $text = rtf2text($text); 70 | $text = str_replace(array('\r\n', '\r', '\n'), PHP_EOL, $text); 71 | 72 | // First pass - basic processing 73 | $text_lines = explode(PHP_EOL, $text); 74 | foreach ($text_lines as $line_number => &$text_line) { 75 | $text_line = process_ew_lyrics_line($text_line); 76 | } 77 | unset($text_line); 78 | $text = implode(PHP_EOL, $text_lines); 79 | 80 | // Second loop - makes sure we catch any newlines that were previously marked as "\line" in the original storage 81 | $text_lines = explode(PHP_EOL, trim($text)); 82 | foreach ($text_lines as $line_number => &$text_line) { 83 | $text_line = process_ew_lyrics_line_custom($text_line); 84 | } 85 | unset($text_line); 86 | $text = implode(PHP_EOL, $text_lines); 87 | 88 | // Remove any double line breaks - common with the RTF input 89 | $text = preg_replace('/(?<=[^\n])\n{2}(?=[^\n])/', PHP_EOL, trim($text)); 90 | 91 | // Condense all slide breaks (removes all paragraph breaks - will add in relevant newlines later) 92 | $line_breaks_to_condense = 3; 93 | $condensed_line_breaks = 2; 94 | if ($custom_settings['condense_slide_breaks']) { 95 | $line_breaks_to_condense--; 96 | $condensed_line_breaks--; 97 | } 98 | 99 | $text = preg_replace('/(\n{' . $line_breaks_to_condense . ',})/', str_repeat(PHP_EOL, $condensed_line_breaks), trim($text)); // Any time there is a block of newlines > 2, condense it to just two 100 | 101 | // Third loop 102 | $text_lines = explode(PHP_EOL, $text); 103 | foreach ($text_lines as $line_number => &$text_line) { 104 | $text_line = process_ew_lyrics_line_song_parts($text_line); 105 | } 106 | unset($text_line); 107 | $text = implode(PHP_EOL, $text_lines); 108 | 109 | $line_breaks_to_condense = 3; 110 | $condensed_line_breaks = 2; 111 | $text = preg_replace('/(\n{' . $line_breaks_to_condense . ',})/', str_repeat(PHP_EOL, $condensed_line_breaks), trim($text)); // Any time there is a block of newlines > 2, condense it to just two 112 | 113 | return process_ew_blocks($text); // Trim because that's what we do to keep our files clean, and also remove large quantities of newlines at start and end of the block 114 | } 115 | 116 | /** 117 | * Basic processing of text dump from EasyWorship database - cleans all unnecessary formatting data 118 | * This used to manually strip out RTF details - now we just clean up the remnants 119 | * 120 | * @param $text_line Single line of raw text from EasyWorship data store 121 | * @return string Cleaned text 122 | */ 123 | function process_ew_lyrics_line($text_line) 124 | { 125 | $text_line = process_unicode($text_line); 126 | $text_line = trim($text_line); 127 | $text_line = preg_replace('/^\.$/', PHP_EOL, $text_line); 128 | $text_line = preg_replace('/^(\(|\))$/', PHP_EOL, $text_line); 129 | $text_line = preg_replace('/^\(\)$/', PHP_EOL, $text_line); 130 | $text_line = preg_replace('/(Arial|Tahoma);/', PHP_EOL, $text_line); 131 | 132 | return trim($text_line); // Seeing a recurring theme here? 133 | } 134 | 135 | /** 136 | * Formatting functions according to custom settings array in config 137 | * 138 | * @param $text_line Single line of cleaned text 139 | * @return string Formatted line of text 140 | */ 141 | function process_ew_lyrics_line_custom($text_line) 142 | { 143 | global $custom_settings; 144 | global $words_to_capitalize; 145 | 146 | // Capitalize some property names 147 | if ($custom_settings['capitalize_names']) { 148 | foreach ($words_to_capitalize as $word) { 149 | $text_line = preg_replace('/( |^)(' . $word . ')( |$)/i', '$1' . $word . '$3', $text_line); 150 | } 151 | } 152 | 153 | // Remove line-ending punctuation 154 | if ($custom_settings['remove_end_punctuation']) { 155 | $text_line = preg_replace('/[.,;]+ ?$/', '', $text_line); 156 | } 157 | 158 | // Fix mid-line punctuation issues 159 | if ($custom_settings['fix_mid_line_punctuation']) { 160 | $text_line = preg_replace('/\./', "\n", $text_line); 161 | $text_line = preg_replace('/([,;\?!])([^ ])/', '$1 $2', $text_line); 162 | } 163 | 164 | // Straighten curly quotes 165 | if ($custom_settings['straighten_curly_quotes']) { 166 | $text_line = str_replace(['’', '´', '‘', '“', '”'], ["'", "'", "'", '"', '"'], $text_line); 167 | } 168 | 169 | // Remove 'x2' references and empty parentheses 170 | if ($custom_settings['remove_x2']) { 171 | $text_line = preg_replace('/(x\d+|\d+x)/i', '', $text_line); 172 | $text_line = str_replace(['()', '[]', '[ ]'], '', $text_line); 173 | } 174 | 175 | // Begin all lines with capital letter 176 | if ($custom_settings['start_with_capital']) { 177 | $text_line = ucfirst($text_line); // Always start with a capital 178 | } 179 | 180 | $text_line = trim($text_line); // We trim before the standardized song sections because we might actually want to keep some trailing newlines 181 | 182 | return $text_line; 183 | } 184 | 185 | /** 186 | * Standardize the names of the song sections to fit ProPresenter's defaults 187 | * 188 | * @param $text_line 189 | * @return string $text_line 190 | */ 191 | function process_ew_lyrics_line_song_parts($text_line) 192 | { 193 | global $custom_settings; 194 | global $song_section_names;; 195 | 196 | $line_breaks = str_repeat(PHP_EOL, 2); 197 | 198 | // Standardize the names of the song sections to fit ProPresenter's defaults 199 | if ($custom_settings['standardize_song_sections']) { 200 | if (preg_match('/^(Verse|Chorus|Bridge)\s?(\d+)$/i', $text_line, $matches)) { 201 | $text_line = ucwords(strtolower($matches[1])) . ' ' . $matches[2]; 202 | } elseif (preg_match('/^(Verse|Chorus|Bridge)$/i', $text_line, $matches)) { 203 | $text_line = ucwords(strtolower($matches[1])) . ' 1'; // When just a plain 'Chorus' 204 | } elseif (preg_match('/^Pre\-?Chorus/i', $text_line, $matches)) { 205 | $text_line = 'Pre-Chorus'; 206 | } elseif (preg_match('/^(Tag|Intro) ?\d+?$/i', $text_line, $matches)) { 207 | $text_line = ucwords(strtolower($matches[1])); 208 | } 209 | } 210 | 211 | // Add double line break before each song part 212 | if (preg_match('/^(' . implode('|', $song_section_names) . ')/i', $text_line, $matches)) { 213 | $text_line = $line_breaks . $text_line; 214 | } 215 | 216 | return $text_line; 217 | } 218 | 219 | /** 220 | * Prepare the metadata to add to the export 221 | * 222 | * @param array $song Song array 223 | * @return string Song metadata 224 | */ 225 | function process_ew_lyrics_metadata(array $song) 226 | { 227 | $meta_fields = array( 228 | 'title' => 'Title', 229 | 'author' => 'Author', 230 | 'copyright' => 'Copyright', 231 | 'ccli_no' => '', 232 | ); 233 | $meta_fields_added = 0; 234 | $metadata = ''; 235 | 236 | // Add the fields from the $song array to the meta block 237 | foreach ($meta_fields as $array_key => $label) { 238 | if (array_key_exists($array_key, $song)) { 239 | // The line format is the same for everything except the CCLI Number 240 | $line_format = '%s: %s'; 241 | if ($array_key === 'ccli_no') { 242 | $line_format = '%s[S A%s]'; 243 | } 244 | 245 | $metadata .= sprintf($line_format, $label, $song[$array_key]) . PHP_EOL; 246 | $meta_fields_added++; 247 | } 248 | } 249 | 250 | // If we have meta fields, add line breaks to separate from the next section 251 | if ($meta_fields_added > 0) { 252 | $metadata .= str_repeat(PHP_EOL, 2); 253 | } 254 | 255 | return $metadata; 256 | } 257 | 258 | function process_ew_blocks($text_lines) 259 | { 260 | $text_blocks = explode(str_repeat(PHP_EOL, 2), trim($text_lines)); 261 | $text_blocks_array = array(); 262 | $text_blocks_output_array = array(); 263 | 264 | foreach ($text_blocks as &$text_block) { 265 | $text_block_array = process_ew_single_block($text_block); 266 | $text_blocks_array[] = $text_block_array; 267 | $text_blocks_output_array[] = $text_block_array['output']; 268 | } 269 | unset($text_block); 270 | 271 | return implode(str_repeat(PHP_EOL, 2), $text_blocks_output_array); 272 | } 273 | 274 | function process_ew_single_block($text_block) 275 | { 276 | global $custom_settings; 277 | global $song_section_names; 278 | global $reflow_max_lines; 279 | 280 | $text_lines = explode(PHP_EOL, $text_block); 281 | $text_block_array['heading'] = false; 282 | 283 | if (preg_match('/(' . implode('|', $song_section_names) . ') ?(\d+?)/', $text_lines[0], $matches)) { 284 | $text_block_array['heading'] = array( 285 | 'label' => $matches[0], 286 | 'type' => $matches[1], 287 | 'number' => $matches[2], 288 | ); 289 | unset($text_lines[0]); 290 | } 291 | 292 | $text_block_array['lines'] = array_values($text_lines); 293 | $text_block_array['line_count'] = count($text_lines); 294 | 295 | $group = 0; 296 | $group_size = count($text_lines); 297 | if ($custom_settings['reflow_large_blocks']) { 298 | $group_size = $reflow_max_lines; 299 | } 300 | 301 | // If there isn't an even split with the $reflow_max_lines number of lines, see if one line less helps 302 | if ($group_size > 2 && count($text_lines) % $group_size !== 0 && count($text_lines) % ($group_size - 1) === 0) { 303 | $group_size = $group_size - 1; 304 | } 305 | 306 | // Loop through the 307 | for ($i = 0; $i < count($text_lines); $i++) { 308 | $text_block_array['groups'][$group][] = $text_block_array['lines'][$i]; 309 | if (($i + 1) % $group_size === 0) { 310 | $group++; 311 | } 312 | } 313 | 314 | $temp_group_array = array(); 315 | 316 | if (array_key_exists('groups', $text_block_array) && count($text_block_array) > 0) { 317 | foreach ($text_block_array['groups'] as $group) { 318 | $temp_group_array[] = implode(str_repeat(PHP_EOL, 1), $group); 319 | } 320 | } 321 | 322 | // Build up the output by condensing arrays 323 | $text_block_array['output'] = $text_block_array['heading'] !== false ? $text_block_array['heading']['label'] . PHP_EOL : ''; 324 | $text_block_array['output'] .= implode(str_repeat(PHP_EOL, 2), $temp_group_array); 325 | 326 | return $text_block_array; 327 | } 328 | 329 | /** 330 | * Worker function that constructs the file contents and saves it to the output directory 331 | * 332 | * @param $song Song array 333 | */ 334 | function save_text_file($song, $file_export_type) 335 | { 336 | global $output_directory; 337 | global $custom_settings; 338 | 339 | $filename = sprintf('%s', $song['title']); // Filename is simply the song's title 340 | $filename = str_replace('/', ' -- ', $filename); 341 | $filename = preg_replace('/\s+/', ' ', trim($filename)); // Make sure we don't have any crazy spaces in the file name 342 | $filename = process_unicode($filename); 343 | $file_extension = ".txt"; 344 | if ($file_export_type === "propresenter6") { 345 | $file_extension = '.pro6'; 346 | } 347 | 348 | // Prevent overwriting files - adds a '(1)' style suffix 349 | if ($custom_settings['prevent_overwrites']) { 350 | $counter = 1; 351 | while (file_exists($output_directory . $filename . $file_extension)) { 352 | $filename = trim(preg_replace('/\(\d+\)/', '', $filename)); 353 | $filename .= sprintf(' (%d)', $counter); 354 | $counter++; 355 | } 356 | } 357 | 358 | // Set up the file contents 359 | $contents = ''; 360 | 361 | if ($file_export_type === "propresenter6") { 362 | $contents .= $song['text']; 363 | $break_char = php_sapi_name() === "cli" ? PHP_EOL : "
"; 364 | echo sprintf('Converting "%s" to ProPresenter6 (filename "%s%s", id: %d)...', $song['title'], $filename, $file_extension, $song['id']) . $break_char; 365 | } else { 366 | // Add the meta data to the top of the file if requested 367 | if ($custom_settings['add_metadata_to_export_files']) { 368 | $contents .= process_ew_lyrics_metadata($song); 369 | } 370 | 371 | $contents .= $song['text']; 372 | 373 | if ($custom_settings['aggressive_text_encoding']) { 374 | // Desperate attempt to get rid of any lingering unicode formatting issues! Forces text to ISO-8859-1 character set 375 | $contents = iconv("ISO-8859-1", "UTF-8", iconv("UTF-8", "ISO-8859-1//IGNORE", $contents)); 376 | } 377 | 378 | $break_char = php_sapi_name() === "cli" ? PHP_EOL : "
"; 379 | echo sprintf('Converting "%s" to plain text (filename "%s%s", id: %d)...', $song['title'], $filename, $file_extension, $song['id']) . $break_char; 380 | } 381 | 382 | file_put_contents($output_directory . $filename . $file_extension, "\xEF\xBB\xBF" . str_replace(PHP_EOL, EW6_EXPORT_EOL, $contents)); 383 | } 384 | 385 | function generateRandomHex($number = 1) 386 | { 387 | return strtoupper(substr(bin2hex(openssl_random_pseudo_bytes($number)), 0, $number)); 388 | } 389 | 390 | function convert_non_ascii_chars_to_hex($text) 391 | { 392 | $text = utf8_decode(mb_convert_encoding($text, "UTF-8")); 393 | $characters = utf8_decode(mb_convert_encoding("æøåÆØÅ", "UTF-8")); 394 | foreach (str_split($characters) as $char) { 395 | $converted_char = '\\\'' . bin2hex($char); 396 | $text = str_replace($char, $converted_char, $text); 397 | } 398 | return $text; 399 | } 400 | 401 | function generate_propresenter_guid() 402 | { 403 | // UUID Format: 94A8AD6C-2A51-44ED-9CAF-79DE1B722190 404 | return sprintf('%s-%s-%s-%s', generateRandomHex(8), generateRandomHex(4), generateRandomHex(4), generateRandomHex(12)); 405 | } 406 | 407 | function get_propresenter_section_color($section_name) 408 | { 409 | $color_map = array( 410 | 'Intro' => '0 0 0 1', 411 | 'Verse 1' => '0 0 1 1', 412 | 'Verse 2' => '0 0.501960814 1 1', 413 | 'Verse 3' => '0 1 1 1', 414 | 'Verse 4' => '0 1 0.501960814 1', 415 | 'Verse 5' => '0 1 0 1', 416 | 'Verse 6' => '0.501960814 1 0 1', 417 | 'Pre-Chorus' => '1 0.400000006 0.400000006 1', 418 | 'Chorus 1' => '1 0 0 1', 419 | 'Chorus 2' => '0.501960814 0 0 1', 420 | 'Chorus 3' => '0.501960814 0 0.250980407 1', 421 | 'Bridge 1' => '0.501960814 0 1 1', 422 | 'Bridge 2' => '0.8000000119 0.400000006 1 1', 423 | 'Tag' => '0 0 0 1', 424 | 'End' => '0 0 0 1', 425 | ); 426 | 427 | if (array_key_exists($section_name, $color_map)) { 428 | return $color_map[$section_name]; 429 | } 430 | return ''; 431 | } 432 | 433 | function get_propresenter_section_hotkey($section_name) 434 | { 435 | global $propresenter_hotkey_map; 436 | 437 | if (array_key_exists($section_name, $propresenter_hotkey_map)) { 438 | return strtolower($propresenter_hotkey_map[$section_name]); 439 | } 440 | return ''; 441 | } 442 | 443 | /** 444 | * Worker function that constructs the file contents and saves it to the output directory 445 | * 446 | * @param $song Song array 447 | */ 448 | function generate_prop6_file_contents($song) 449 | { 450 | global $custom_settings; 451 | $prop6_file_template = file_get_contents(dirname(__FILE__) . '/resources/propresenter6_file_wrapper.xml'); 452 | $prop6_group_template = file_get_contents(dirname(__FILE__) . '/resources/propresenter6_group_element.xml'); 453 | $prop6_slide_template = file_get_contents(dirname(__FILE__) . '/resources/propresenter6_slide_element.xml'); 454 | $prop6_slide_text = file_get_contents(dirname(__FILE__) . '/resources/propresenter6_slide_text.txt'); 455 | 456 | $slide_elements = array(); 457 | 458 | if ($custom_settings['prop6_add_blank_intro'] == true) { 459 | $slide_elements['Intro'] = array( 460 | 'name' => 'Intro', 461 | 'guid' => generate_propresenter_guid(), 462 | 'color' => get_propresenter_section_color('Intro'), 463 | 'hotkey' => get_propresenter_section_hotkey('Intro'), 464 | 'slides' => array( 465 | array( 466 | 'guid' => generate_propresenter_guid(), 467 | 'text' => '' 468 | ) 469 | ) 470 | ); 471 | } 472 | 473 | $section_counter = 1; 474 | $current_group_name = 'Verse ' . $section_counter; 475 | $slide_break = false; 476 | $has_groups = false; 477 | if (preg_match('/(Verse|Chorus|Bridge)/i', $song['text'], $matches)) { 478 | $has_groups = true; 479 | } 480 | 481 | $text_lines = explode(PHP_EOL, $song['text']); 482 | 483 | // Loop through the lines 484 | foreach ($text_lines as $text_line) { 485 | 486 | if ($has_groups === false && empty($text_line)) { 487 | $section_counter++; 488 | $current_group_name = 'Verse ' . $section_counter; 489 | $slide_break = true; 490 | continue; 491 | } elseif ($has_groups === true && empty($text_line)) { 492 | $slide_break = true; 493 | continue; 494 | } 495 | 496 | // Create a new group whenever a heading is found 497 | if (preg_match('/^(Verse|Chorus|Bridge)\s?(\d+)$/i', $text_line, $matches)) { 498 | $current_group_name = ucwords(strtolower($matches[1])) . ' ' . $matches[2]; 499 | } elseif (preg_match('/^(Verse|Chorus|Bridge)$/i', $text_line, $matches)) { 500 | $current_group_name = ucwords(strtolower($matches[1])) . ' 1'; // When just a plain 'Chorus' 501 | } elseif (preg_match('/^Pre\-?Chorus/i', $text_line, $matches)) { 502 | $current_group_name = 'Pre-Chorus'; 503 | } elseif (preg_match('/^(Tag|Intro) ?\d?$/i', $text_line, $matches)) { 504 | $current_group_name = ucwords(strtolower($matches[1])); 505 | } 506 | 507 | if (array_key_exists($current_group_name, $slide_elements) === false) { 508 | $slide_elements[$current_group_name] = array( 509 | 'name' => $current_group_name, 510 | 'guid' => generate_propresenter_guid(), 511 | 'color' => get_propresenter_section_color($current_group_name), 512 | 'hotkey' => get_propresenter_section_hotkey($current_group_name), 513 | 'slides' => array() 514 | ); 515 | continue; 516 | } 517 | 518 | // Create a slide for every chunk of lines 519 | if ($slide_break || count($slide_elements[$current_group_name]['slides']) === 0) { 520 | $slide_elements[$current_group_name]['slides'][] = array( 521 | 'guid' => generate_propresenter_guid(), 522 | 'text' => $text_line 523 | ); 524 | } else { 525 | $slide_index = count($slide_elements[$current_group_name]['slides']) - 1; 526 | $slide_elements[$current_group_name]['slides'][$slide_index]['text'] .= PHP_EOL . $text_line; 527 | } 528 | 529 | $slide_break = false; 530 | } 531 | 532 | if ($custom_settings['prop6_add_blank_end'] == true) { 533 | $slide_elements['End'] = array( 534 | 'name' => 'End', 535 | 'guid' => generate_propresenter_guid(), 536 | 'color' => get_propresenter_section_color('End'), 537 | 'hotkey' => get_propresenter_section_hotkey('End'), 538 | 'slides' => array( 539 | array( 540 | 'guid' => generate_propresenter_guid(), 541 | 'text' => '' 542 | ) 543 | ) 544 | ); 545 | } 546 | 547 | $groups_xml = ''; 548 | foreach ($slide_elements as $group) { 549 | $slides_xml = ''; 550 | foreach ($group['slides'] as $index => $slide) { 551 | $slide_build = $prop6_slide_template; 552 | if ($index === 0 && $custom_settings['prop6_add_hotkeys'] == true) { 553 | $slide_build = str_replace('%%HOTKEY%%', $group['hotkey'], $slide_build); 554 | } else { 555 | $slide_build = str_replace('%%HOTKEY%%', '', $slide_build); 556 | } 557 | $slide_build = str_replace('%%GUID%%', $slide['guid'], $slide_build); 558 | $slide_build = str_replace('%%TEXTGUID%%', generate_propresenter_guid(), $slide_build); 559 | $slide_text = str_replace(PHP_EOL, '\\' . PHP_EOL, $slide['text']); 560 | $slide_text = convert_non_ascii_chars_to_hex($slide_text); 561 | $slide_text = str_replace('%%TEXT%%', $slide_text, $prop6_slide_text); 562 | $slide_build = str_replace('%%TEXT%%', base64_encode($slide_text), $slide_build); 563 | $slides_xml .= $slide_build; 564 | } 565 | 566 | $group_build = $prop6_group_template; 567 | $group_build = str_replace('%%NAME%%', $group['name'], $group_build); 568 | $group_build = str_replace('%%COLOR%%', $group['color'], $group_build); 569 | $group_build = str_replace('%%GUID%%', $group['guid'], $group_build); 570 | $group_build = str_replace('%%SLIDES%%', $slides_xml, $group_build); 571 | $groups_xml .= $group_build; 572 | } 573 | 574 | $prop6_file_template = str_replace('%%TITLE%%', $song['title'], $prop6_file_template); 575 | $prop6_file_template = str_replace('%%GUID%%', generate_propresenter_guid(), $prop6_file_template); 576 | $prop6_file_template = str_replace('%%GROUPS%%', $groups_xml, $prop6_file_template); 577 | 578 | return $prop6_file_template; 579 | } 580 | 581 | /** 582 | * Custom function to try to handle various unicode formatting issues that will come our way from EasyWorship 583 | * 584 | * @param $string 585 | * @return string 586 | */ 587 | function process_unicode($string) 588 | { 589 | $string = preg_replace('/\\\\x([0-9A-F]{2})\?/', '&#x$1;', $string); 590 | $string = preg_replace('/\\\\u(\d+)\?/', '&#$1;', $string); 591 | return html_entity_decode($string, ENT_NOQUOTES, 'UTF-8'); 592 | } 593 | 594 | /* Functions adapted from http://webcheatsheet.com/php/reading_the_clean_text_from_rtf.php */ 595 | 596 | function rtf_isPlainText($s) 597 | { 598 | $arrfailAt = array("*", "fonttbl", "colortbl", "datastore", "themedata"); 599 | for ($i = 0; $i < count($arrfailAt); $i++) 600 | if (!empty($s[$arrfailAt[$i]])) return false; 601 | return true; 602 | } 603 | 604 | function rtf2text($text) 605 | { 606 | if (!strlen($text)) 607 | return ""; 608 | 609 | // Create empty stack array. 610 | $document = ""; 611 | $stack = array(); 612 | $j = -1; 613 | // Read the data character-by- character… 614 | for ($i = 0, $len = strlen($text); $i < $len; $i++) { 615 | $c = $text[$i]; 616 | 617 | // Depending on current character select the further actions. 618 | switch ($c) { 619 | // the most important key word backslash 620 | case "\\": 621 | // read next character 622 | $nc = $text[$i + 1]; 623 | 624 | // If it is another backslash or nonbreaking space or hyphen, 625 | // then the character is plain text and add it to the output stream. 626 | if ($nc == '\\' && rtf_isPlainText($stack[$j])) $document .= '\\'; 627 | elseif ($nc == '~' && rtf_isPlainText($stack[$j])) $document .= ' '; 628 | elseif ($nc == '_' && rtf_isPlainText($stack[$j])) $document .= '-'; 629 | // If it is an asterisk mark, add it to the stack. 630 | elseif ($nc == '*') $stack[$j]["*"] = true; 631 | // If it is a single quote, read next two characters that are the hexadecimal notation 632 | // of a character we should add to the output stream. 633 | elseif ($nc == "'") { 634 | $hex = substr($text, $i + 2, 2); 635 | if (rtf_isPlainText($stack[$j])) 636 | $document .= html_entity_decode("&#" . hexdec($hex) . ";"); 637 | //Shift the pointer. 638 | $i += 2; 639 | // Since, we’ve found the alphabetic character, the next characters are control word 640 | // and, possibly, some digit parameter. 641 | } elseif ($nc >= 'a' && $nc <= 'z' || $nc >= 'A' && $nc <= 'Z') { 642 | $word = ""; 643 | $param = null; 644 | 645 | // Start reading characters after the backslash. 646 | for ($k = $i + 1, $m = 0; $k < strlen($text); $k++, $m++) { 647 | $nc = $text[$k]; 648 | // If the current character is a letter and there were no digits before it, 649 | // then we’re still reading the control word. If there were digits, we should stop 650 | // since we reach the end of the control word. 651 | if ($nc >= 'a' && $nc <= 'z' || $nc >= 'A' && $nc <= 'Z') { 652 | if (empty($param)) 653 | $word .= $nc; 654 | else 655 | break; 656 | // If it is a digit, store the parameter. 657 | } elseif ($nc >= '0' && $nc <= '9') 658 | $param .= $nc; 659 | // Since minus sign may occur only before a digit parameter, check whether 660 | // $param is empty. Otherwise, we reach the end of the control word. 661 | elseif ($nc == '-') { 662 | if (empty($param)) 663 | $param .= $nc; 664 | else 665 | break; 666 | } else 667 | break; 668 | } 669 | // Shift the pointer on the number of read characters. 670 | $i += $m - 1; 671 | 672 | // Start analyzing what we’ve read. We are interested mostly in control words. 673 | $toText = ""; 674 | switch (strtolower($word)) { 675 | // If the control word is "u", then its parameter is the decimal notation of the 676 | // Unicode character that should be added to the output stream. 677 | // We need to check whether the stack contains \ucN control word. If it does, 678 | // we should remove the N characters from the output stream. 679 | case "u": 680 | $toText .= html_entity_decode("&#x" . dechex($param) . ";"); 681 | $ucDelta = @$stack[$j]["uc"]; 682 | if ($ucDelta > 0) 683 | $i += $ucDelta; 684 | break; 685 | // Select line feeds, spaces and tabs. 686 | case "par": 687 | case "page": 688 | case "column": 689 | case "line": 690 | case "lbr": 691 | $toText .= "\n"; 692 | break; 693 | case "emspace": 694 | case "enspace": 695 | case "qmspace": 696 | $toText .= " "; 697 | break; 698 | case "tab": 699 | $toText .= "\t"; 700 | break; 701 | // Add current date and time instead of corresponding labels. 702 | case "chdate": 703 | $toText .= date("m.d.Y"); 704 | break; 705 | case "chdpl": 706 | $toText .= date("l, j F Y"); 707 | break; 708 | case "chdpa": 709 | $toText .= date("D, j M Y"); 710 | break; 711 | case "chtime": 712 | $toText .= date("H:i:s"); 713 | break; 714 | // Replace some reserved characters to their html analogs. 715 | case "emdash": 716 | $toText .= html_entity_decode("—"); 717 | break; 718 | case "endash": 719 | $toText .= html_entity_decode("–"); 720 | break; 721 | case "bullet": 722 | $toText .= html_entity_decode("•"); 723 | break; 724 | case "lquote": 725 | $toText .= html_entity_decode("‘"); 726 | break; 727 | case "rquote": 728 | $toText .= html_entity_decode("’"); 729 | break; 730 | case "ldblquote": 731 | $toText .= html_entity_decode("«"); 732 | break; 733 | case "rdblquote": 734 | $toText .= html_entity_decode("»"); 735 | break; 736 | // Add all other to the control words stack. If a control word 737 | // does not include parameters, set ¶m to true. 738 | default: 739 | $stack[$j][strtolower($word)] = empty($param) ? true : $param; 740 | break; 741 | } 742 | // Add data to the output stream if required. 743 | if (array_key_exists($j, $stack) && rtf_isPlainText($stack[$j])) 744 | $document .= $toText; 745 | } 746 | 747 | $i++; 748 | break; 749 | // If we read the opening brace {, then new subgroup starts and we add 750 | // new array stack element and write the data from previous stack element to it. 751 | case "{": 752 | $j++; 753 | if (array_key_exists($j, $stack)) { 754 | array_push($stack, $stack[$j]); 755 | } 756 | break; 757 | // If we read the closing brace }, then we reach the end of subgroup and should remove 758 | // the last stack element. 759 | case "}": 760 | array_pop($stack); 761 | $j--; 762 | break; 763 | // Skip “trash”. 764 | case '\0': 765 | case '\r': 766 | case '\f': 767 | case '\n': 768 | break; 769 | // Add other data to the output stream if required. 770 | default: 771 | if (array_key_exists($j, $stack) && rtf_isPlainText($stack[$j])) 772 | $document .= $c; 773 | break; 774 | } 775 | } 776 | // Return result. 777 | return $document; 778 | } -------------------------------------------------------------------------------- /resources/Amazing Grace expanded.pro6: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {51 108 0 1816 864} 15 | 0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506} 16 | 17 | 0 0 0 1 18 | 1.000000 19 | 20 | e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibH0Ke1xjb2xvcnRibDtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7fQp7XCpcZXhwYW5kZWRjb2xvcnRibDs7fQp9 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {51 108 0 1816 864} 33 | 0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506} 34 | 35 | 0 0 0 1 36 | 1.000000 37 | 38 | e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBBbWF6aW5nIGdyYWNlISBIb3cgc3dlZXQgdGhlIHNvdW5kXApUaGF0IHNhdmVkIGEgd3JldGNoIGxpa2UgbWUhfQ== 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | {51 108 0 1816 864} 47 | 0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506} 48 | 49 | 0 0 0 1 50 | 1.000000 51 | 52 | e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBJIG9uY2Ugd2FzIGxvc3QsIGJ1dCBub3cgYW0gZm91bmQ7XApXYXMgYmxpbmQsIGJ1dCBub3cgSSBzZWUufQ== 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | {51 108 0 1816 864} 65 | 0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506} 66 | 67 | 0 0 0 1 68 | 1.000000 69 | 70 | e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBcJzkyVHdhcyBncmFjZSB0aGF0IHRhdWdodCBteSBoZWFydCB0byBmZWFyLFwKQW5kIGdyYWNlIG15IGZlYXJzIHJlbGlldmVkO30= 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | {51 108 0 1816 864} 79 | 0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506} 80 | 81 | 0 0 0 1 82 | 1.000000 83 | 84 | e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBIb3cgcHJlY2lvdXMgZGlkIHRoYXQgZ3JhY2UgYXBwZWFyXApUaGUgaG91ciBJIGZpcnN0IGJlbGlldmVkLn0= 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | {51 108 0 1816 864} 97 | 0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506} 98 | 99 | 0 0 0 1 100 | 1.000000 101 | 102 | e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBUaHJvdWdoIG1hbnkgZGFuZ2VycywgdG9pbHMgYW5kIHNuYXJlcyxcCkkgaGF2ZSBhbHJlYWR5IGNvbWU7fQ== 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | {51 108 0 1816 864} 111 | 0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506} 112 | 113 | 0 0 0 1 114 | 1.000000 115 | 116 | e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBcJzkyVGlzIGdyYWNlIGhhdGggYnJvdWdodCBtZSBzYWZlIHRodXMgZmFyLFwKQW5kIGdyYWNlIHdpbGwgbGVhZCBtZSBob21lLn0= 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | {51 108 0 1816 864} 129 | 0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506} 130 | 131 | 0 0 0 1 132 | 1.000000 133 | 134 | e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBUaGUgTG9yZCBoYXMgcHJvbWlzZWQgZ29vZCB0byBtZSxcCkhpcyBXb3JkIG15IGhvcGUgc2VjdXJlczt9 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | {51 108 0 1816 864} 143 | 0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506} 144 | 145 | 0 0 0 1 146 | 1.000000 147 | 148 | e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBIZSB3aWxsIG15IFNoaWVsZCBhbmQgUG9ydGlvbiBiZSxcCkFzIGxvbmcgYXMgbGlmZSBlbmR1cmVzLn0= 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | {51 108 0 1816 864} 161 | 0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506} 162 | 163 | 0 0 0 1 164 | 1.000000 165 | 166 | e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBZZWEsIHdoZW4gdGhpcyBmbGVzaCBhbmQgaGVhcnQgc2hhbGwgZmFpbCxcCkFuZCBtb3J0YWwgbGlmZSBzaGFsbCBjZWFzZSx9 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | {51 108 0 1816 864} 175 | 0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506} 176 | 177 | 0 0 0 1 178 | 1.000000 179 | 180 | e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBJIHNoYWxsIHBvc3Nlc3MsIHdpdGhpbiB0aGUgdmVpbCxcCkEgbGlmZSBvZiBqb3kgYW5kIHBlYWNlLn0= 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | {51 108 0 1816 864} 193 | 0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506} 194 | 195 | 0 0 0 1 196 | 1.000000 197 | 198 | e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBUaGUgZWFydGggc2hhbGwgc29vbiBkaXNzb2x2ZSBsaWtlIHNub3csXApUaGUgc3VuIGZvcmJlYXIgdG8gc2hpbmU7fQ== 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | {51 108 0 1816 864} 207 | 0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506} 208 | 209 | 0 0 0 1 210 | 1.000000 211 | 212 | e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBCdXQgR29kLCB3aG8gY2FsbGVkIG1lIGhlcmUgYmVsb3csXApXaWxsIGJlIGZvcmV2ZXIgbWluZS59 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | {51 108 0 1816 864} 225 | 0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506} 226 | 227 | 0 0 0 1 228 | 1.000000 229 | 230 | e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBXaGVuIHdlXCc5MnZlIGJlZW4gdGhlcmUgdGVuIHRob3VzYW5kIHllYXJzLFwKQnJpZ2h0IHNoaW5pbmcgYXMgdGhlIHN1bix9 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | {51 108 0 1816 864} 239 | 0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506} 240 | 241 | 0 0 0 1 242 | 1.000000 243 | 244 | e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEFyaWFsTVQ7fQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7XHJlZDUxXGdyZWVuNTFcYmx1ZTUxO30Ke1wqXGV4cGFuZGVkY29sb3J0Ymw7O1xjc2dyYXlcYzEwMDAwMDtcY3NnZW5lcmljcmdiXGMyMDAwMFxjMjAwMDBcYzIwMDAwO30KXHBhcmRcc2xsZWFkaW5nMzIwXHBhcmRpcm5hdHVyYWxccWNccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMTI3XGZzbWlsbGk2Mzk5OSBcY2YyIFxleHBuZDBcZXhwbmR0dzBca2VybmluZzAKXG91dGwwXHN0cm9rZXdpZHRoLTgwIFxzdHJva2VjMyBXZVwnOTJ2ZSBubyBsZXNzIGRheXMgdG8gc2luZyBHb2RcJzkycyBwcmFpc2VcClRoYW4gd2hlbiB3ZVwnOTJkIGZpcnN0IGJlZ3VuLn0= 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | {51 108 0 1816 864} 257 | 0.000000|0 0 0 0.3333333432674408|{4.0000002002606152, -3.9999995259110506} 258 | 259 | 0 0 0 1 260 | 1.000000 261 | 262 | e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYxNTA0XGNvY29hc3VicnRmODMwCntcZm9udHRibH0Ke1xjb2xvcnRibDtccmVkMjU1XGdyZWVuMjU1XGJsdWUyNTU7fQp7XCpcZXhwYW5kZWRjb2xvcnRibDs7fQp9 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | --------------------------------------------------------------------------------