I’m trying to memorize bible verses. So far I’ve just done a little hacking. OSX has a command line program, “say”, that will do just that, say whatever you tell it to. It has several voices — check out the manual.
BibleGateway has an ESV Bible audio API where you can download bible passages. So I threw together a shell script and a Python script to make MP3 files for the passages I want to memorize.
The first step was to make stock audio: The ordinal numbers I’d need (first, second and third) and all the cardinal numbers (1-150), the books of the bible (Genesis - Revelation) and some pauses. The say command recognizes by the string ”[[slnc 300]]” where e.g. 300 is the length of the pause. I put all these string in a text file and ran the following bash script to produce the stock MP3s.
Bash script to make MP3s from OSX’s say command
#!/bin/bash
trap "echo signal received; exit" 0 INT HUP QUIT TERM PIPE SEGV
while read line; do
lower=$(echo $line | awk '{print tolower($0)}');
lowertrimmed=$(echo $lower | sed 's/ //g');
say -o $lowertrimmed.m4af $lowertrimmed;
ffmpeg -i $lowertrimmed.m4af -ac 1 -ar 22050 -ab 32k $lowertrimmed.mp3 < /dev/null
rm $lowertrimmed.m4af
done < books.txt
Stock audio complete, the next step was to write a Python script to read in a file with the passages I want to memorize, download them from BibleGateway, and append a header and footer with the passage name. For example, Proverbs 25: 8-10 would sound like this:
Proverbs twenty-five, eight through ten… Do not hastily bring into court for what will you do in the end, when your neighbor puts you to shame? Argue your case with your neighbor himself, and do not reveal another’s secret, lest he who hears you bring shame upon you, and your ill repute have no end… Proverbs twenty-five, eight through ten
Python script to make audio bible passages using FFMPEG
#!/usr/bin/env python
import urllib
import sys
import codecs
import re
import getopt
import os.path
import shutil
import os
KEY = 'IP'
OUTDIR = "/Users/joshuapearce/Dropbox/Code/MakeEsvAudio/output"
PASSAGEDIR = "/Users/joshuapearce/Dropbox/Code/MakeEsvAudio/passages"
STOCKDIR = "/Users/joshuapearce/Dropbox/Code/MakeEsvAudio/stock"
TEMPDIR = "/Users/joshuapearce/Dropbox/Code/MakeEsvAudio/temp"
PAUSE100 = STOCKDIR + '/' + 'pause100.mp3'
PAUSE200 = STOCKDIR + '/' + 'pause200.mp3'
PAUSE300 = STOCKDIR + '/' + 'pause300.mp3'
PAUSE400 = STOCKDIR + '/' + 'pause400.mp3'
PAUSE800 = STOCKDIR + '/' + 'pause800.mp3'
FIRST = STOCKDIR + '/' + 'first.mp3'
SECOND = STOCKDIR + '/' + 'second.mp3'
THIRD = STOCKDIR + '/' + 'third.mp3'
THROUGH = STOCKDIR + '/' + 'through.mp3'
class ESVSession:
def __init__(self, key):
textoptions = ['include-short-copyright=0',
'output-format=plain-text',
'include-passage-horizontal-lines=0',
'include-heading-horizontal-lines=0',
'include-passage-references=false',
'include-first-verse-numbers=false',
'include-headings=false',
'include-subheadings=false',
'include-selahs=false',
'include-footnotes=false']
mp3options = ['output-format=mp3']
self.textoptions = '&'.join(textoptions)
self.textBaseUrl = 'http://www.esvapi.org/v2/rest/passageQuery?key=%s' % (key)
self.mp3options = '&'.join(mp3options)
self.mp3BaseUrl = 'http://www.esvapi.org/v2/rest/passageQuery?key=%s' % (key)
def getPassage(self, passage):
passage = passage.split()
passage = '+'.join(passage)
url = self.textBaseUrl + '&passage=%s&%s' % (passage, self.textoptions)
page = urllib.urlopen(url)
return page.read()
def getMp3(self, passage, filename):
passage = passage.split()
passage = '+'.join(passage)
url = self.mp3BaseUrl + '&passage=%s&%s' % (passage, self.mp3options)
urllib.urlretrieve (url, filename)
def usage():
print "USAGE: program.py -f /path/to/file"
class Usage(Exception):
def __init__(self, msg):
self.msg = msg
# This doesn't work, but I'd like it to. I have no idea what this guy's talking about:
# http://www.artima.com/weblogs/viewpost.jsp?thread=4829
class Error(Exception):
def __init__(self, msg):
self.msg = msg
def main(argv=None):
if argv is None:
argv = sys.argv
try:
try:
opts, args = getopt.getopt(argv[1:], "f:h", ["file=", "help"])
except getopt.error, msg:
raise Usage(msg)
file_path = ''
for opt, arg in opts:
if opt in ("-h", "--help"):
usage()
if opt == "-f":
file_path = arg
if (file_path == ''):
usage()
return
f = open(file_path, 'r')
passages = f.read().splitlines()
f.close()
bible = ESVSession(KEY)
line_num = 0
for p in passages:
line_num = line_num + 1
if not p.startswith("#"):
match = re.search("^([0-3]{0,1})([a-zA-Z ]+)([0-9]{1,3}) ?: ?([0-9]{1,2}) ?([\-0-9]{0,3})$", p)
#try:
book_num = match.group(1)
book_num_str = book_num
book = match.group(2).strip()
chapter = match.group(3)
verse_start = match.group(4)
verse_end_str = match.group(5)
verse_end = verse_end_str.replace('-', '')
if book_num != '':
book_num_str = book_num + ' '
passage = '%s%s %s:%s%s' % (book_num_str, book, chapter, verse_start, verse_end_str)
bgw_filename = '%s%s_%s_%s%s.mp3' % (book_num_str.replace(' ', '_'), book.replace(' ', '_'), chapter, verse_start, verse_end_str)
bgw_filepath = PASSAGEDIR + '/' + bgw_filename
cat_filename = bgw_filename.replace('.mp3', '_cat.mp3')
cat_filepath = TEMPDIR + '/' + cat_filename
final_filepath = OUTDIR + '/' + bgw_filename
if not os.path.isfile(bgw_filepath):
bible.getMp3(passage, bgw_filepath)
mp3_files = [PAUSE300]
if book_num == '1':
mp3_files.append(FIRST)
elif book_num == '2':
mp3_files.append(SECOND)
elif book_num == '3':
mp3_files.append(THIRD)
mp3_files.append(STOCKDIR + '/' + book.replace(' ', '').lower() + '.mp3')
mp3_files.append(STOCKDIR + '/' + chapter + '.mp3')
mp3_files.append(STOCKDIR + '/' + verse_start + '.mp3')
if verse_end != '':
mp3_files.append(THROUGH)
mp3_files.append(STOCKDIR + '/' + verse_end + '.mp3')
mp3_files.append(PAUSE200)
mp3_files.append(bgw_filepath)
mp3_files.append(PAUSE200)
mp3_files.extend(mp3_files[:-2])
mp3_files.append(PAUSE800)
cat_file = open(cat_filepath, 'wb')
for mp3_file in mp3_files:
shutil.copyfileobj(open(mp3_file, 'rb'), cat_file)
cat_file.close()
popen_args = ['ffmpeg', '-i', cat_filepath]
popen_args += ['-metadata', 'title="' + passage + '"']
popen_args += ['-metadata', 'author="God"']
popen_args += ['-metadata', 'artist="God"']
popen_args += ['-metadata', 'album="Verses to Memorize"']
popen_args += ['-metadata', 'comment="Created by Josh Pearce, www.beechtreetech.com"']
popen_args += ['-y', final_filepath]
#print " ".join(popen_args)
proc = os.popen(" ".join(popen_args))
#except Exception as err:
#print "trouble reading line: " + str(line_num)
#return 1
except Usage as err:
print >>sys.stderr, err.msg
print >>sys.stderr, "for help use --help"
return 2
if __name__ == "__main__":
sys.exit(main())
Resources for say, FFMPEG and the Bible Gateway API
Blog to Podcast
say manual from Apple
Bible Gateway ESV MP3 API