Re: [orca-list] Patch: Experimental eSpeak support using python-espeak



Howdy,
First of all, thanks a ton for doing this. This is something I, and a few others, have wanted for a long time. I installed it using your instructions, and it works as advertised. The only things I have noticed are:
When you change the voice, en-us in my case, if you hit apply, then change something else, it doesn't 
remember the voice if you hit apply again and goes back to the default.
The espeak volume doesn't seem to match the orca settings volume. If it is set at 10.0, that should be the 
equivalant of espeak -a 200, but it sounds closer to the default espeak volume, or about half that (espeak -a 
100).
When you change the speech rate, the voice doesn't accurately speed up with the actual end result. I was 
worried that the speech was going to be a bit slow for my tastes, but when I hit reply it was actually quite 
a bit faster than I usually use. I was very happy to get the fast speech, and I know of at least one person 
who listens to espeak much faster than I do, so they will be thrilled too.
Thanks again for doing this. I can't wait for when this is included into orca propper. From the test run, it 
seems like that will be very soon.
Thanks
Storm
On Mon, Aug 24, 2015 at 12:25:18AM +0200, Peter Vágner wrote:
Hello,
Today I was playing with orca a bit more than usual. The result is that I have implemented an alternative speech server subclass. I have taken speechdispatcherfactory module and I have replaced python-speechd API with python-espeak one accross the whole module. So by doing this I think I have created experimental direct eSpeak support for orca. It requires python-espeak to be installed. I tryed to do this because a while ago we were discussing this approach here on the list. You guys were hoping for additional responsiveness increase and easier way on how to add eSpeak specific features like variants support into orca. Also I have come up with another little advantage as I have been thinking about this more. By default eSpeak is built with both pulseaudio and portaudio sound outputs. It first tryes to use pulseaudio and if that is not available it plays its audio through portaudio thus using alsa directly. It can't switch between this dynamically at runtime but it at least works out what to use at initialization what translates to when launching the application when using python-espeak. There are no eSpeak specific features yet. Things which are working include speaking, changing voices, changing pitch, rate and volume. Say all support with proper progress tracking is also working. I am not sure I like this better than using eSpeak through speech-dispatcher like we are doing for years. I am using this experiment for just a few hours now. I am attaching a patch for those of you who are not afraid of risking a bit. Please make sure you know how to revert this and troubleshoot your system when it fails to speak. This is my first attempt at trying to implement something into orca so it is very likelly it may have loads of issues I even can't think of at the moment.
So how to apply it if you really want to test it out.
- Install python-espeak. If you are on arch linux, get it from the AUR. I guess on Debian or Ubuntu you can just do apt-get install python-espeak. With other distributions I don't know whether there are prebuilt packages. Python-espeak is hosted on launchpad and if there are no packages for your distro then you can get it from there and install using python setup tools. To check whether the patch applies cleanly into your cloned orca git tree you can run
git apply --check espeakfactory.patch
If there are no conflicts found you can then apply it by doing
git apply espeakfactory.patch
After doing this you can just build and install orca like normal.

Greetings

Peter


From 59b1e77f97edbde96f351b2408fcf21415536887 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Peter=20V=C3=A1gner?= <pvdeejay gmail com>
Date: Sun, 23 Aug 2015 20:23:27 +0200
Subject: [PATCH 1/2] direct eSpeak support using python-espeak. espeakfactory
is based off of speechdispatcherfactory replacing speechd API calls with
espeak API.

---
src/orca/Makefile.am      |   1 +
src/orca/espeakfactory.py | 461 ++++++++++++++++++++++++++++++++++++++++++++++
src/orca/guilabels.py     |   4 +
src/orca/settings.py      |   2 +-
4 files changed, 467 insertions(+), 1 deletion(-)
create mode 100644 src/orca/espeakfactory.py

diff --git a/src/orca/Makefile.am b/src/orca/Makefile.am
index f9f3086..275e5f7 100644
--- a/src/orca/Makefile.am
+++ b/src/orca/Makefile.am
@@ -22,6 +22,7 @@ orca_python_PYTHON = \
        common_keyboardmap.py \
        debug.py \
        desktop_keyboardmap.py \
+       espeakfactory.py \
        event_manager.py \
        eventsynthesizer.py \
        find.py \
diff --git a/src/orca/espeakfactory.py b/src/orca/espeakfactory.py
new file mode 100644
index 0000000..4585694
--- /dev/null
+++ b/src/orca/espeakfactory.py
@@ -0,0 +1,461 @@
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA  02110-1301 USA.
+
+
+"""Provides an Orca speech server for eSpeak backend."""
+
+__id__ = "$Id$"
+__version__   = "$Revision$"
+__date__      = "$Date$"
+__license__   = "LGPL"
+
+from gi.repository import GLib
+import re
+import time
+import os.path
+
+from . import chnames
+from . import debug
+from . import guilabels
+from . import messages
+from . import speechserver
+from . import settings
+from . import orca_state
+from . import punctuation_settings
+from .acss import ACSS
+
+try:
+    from espeak import espeak
+except:
+    _espeak_available = False
+else: + _espeak_available = True
+
+PUNCTUATION = re.compile('[^\w\s]', re.UNICODE)
+ELLIPSIS = re.compile('(\342\200\246|\.\.\.\s*)')
+
+#Parameter bounds
+minRate=80
+maxRate=450
+minPitch=0
+maxPitch=99
+
+class SpeechServer(speechserver.SpeechServer):
+    # See the parent class for documentation.
+
+    _active_servers = {}
+ + DEFAULT_SERVER_ID = 'default'
+    _SERVER_NAMES = {DEFAULT_SERVER_ID: guilabels.DEFAULT_SYNTHESIZER}
+
+    def getFactoryName():
+        return guilabels.ESPEAK
+    getFactoryName = staticmethod(getFactoryName)
+
+    def getSpeechServers():
+        servers = []
+        default = SpeechServer._getSpeechServer(SpeechServer.DEFAULT_SERVER_ID)
+        if default is not None:
+            servers.append(default)
+        return servers
+    getSpeechServers = staticmethod(getSpeechServers)
+
+    def _getSpeechServer(cls, serverId):
+        """Return an active server for given id.
+
+        Attempt to create the server if it doesn't exist yet.  Returns None
+        when it is not possible to create the server.
+ + """
+        if serverId not in cls._active_servers:
+            cls(serverId)
+        # Don't return the instance, unless it is succesfully added
+        # to `_active_Servers'.
+        return cls._active_servers.get(serverId)
+    _getSpeechServer = classmethod(_getSpeechServer)
+
+    def getSpeechServer(info=None):
+        if info is not None:
+            thisId = info[1]
+        else:
+            thisId = SpeechServer.DEFAULT_SERVER_ID
+        return SpeechServer._getSpeechServer(thisId)
+    getSpeechServer = staticmethod(getSpeechServer)
+
+    def shutdownActiveServers():
+        for server in list(SpeechServer._active_servers.values()):
+            server.shutdown()
+    shutdownActiveServers = staticmethod(shutdownActiveServers)
+
+    # *** Instance methods ***
+
+    def __init__(self, serverId):
+        super(SpeechServer, self).__init__()
+        self._id = serverId
+        self._client = None
+        self._current_voice_properties = {}
+        self._acss_manipulators = (
+            (ACSS.RATE, self._set_rate),
+            (ACSS.AVERAGE_PITCH, self._set_pitch),
+            (ACSS.GAIN, self._set_volume),
+            (ACSS.FAMILY, self._set_family),
+            )
+        if not _espeak_available:
+            debug.println(debug.LEVEL_WARNING,
+                          "eSpeak interface not installed.")
+            return
+        self._PUNCTUATION_MODE_MAP = {
+            settings.PUNCTUATION_STYLE_ALL:  espeak.Punctuation.All,
+            settings.PUNCTUATION_STYLE_MOST: espeak.Punctuation.Custom,
+            settings.PUNCTUATION_STYLE_SOME: espeak.Punctuation.Custom,
+            settings.PUNCTUATION_STYLE_NONE: espeak.Punctuation.Any,
+            }
+        self._CALLBACK_TYPE_MAP = {
+            espeak.event_SENTENCE: speechserver.SayAllContext.PROGRESS,
+            #espeak.event_END: speechserver.SayAllContext.INTERRUPTED,
+            espeak.event_MSG_TERMINATED: speechserver.SayAllContext.COMPLETED,
+           #espeak.event_MARK:speechserver.SayAllContext.PROGRESS,
+            }
+
+        self._default_voice_name = guilabels.SPEECH_DEFAULT_VOICE % serverId
+ + try:
+            self._init()
+        except:
+            debug.println(debug.LEVEL_WARNING,
+                          "eSpeak failed to initialize:")
+            debug.printException(debug.LEVEL_WARNING)
+        else:
+            SpeechServer._active_servers[serverId] = self
+
+        self._lastKeyEchoTime = None
+
+    def _init(self):
+        self._current_voice_properties = {}
+        mode = self._PUNCTUATION_MODE_MAP[settings.verbalizePunctuationStyle]
+        espeak.set_parameter(espeak.Parameter.Punctuation,mode,0)
+
+    def updateCapitalizationStyle(self):
+        """Updates the capitalization style used by the speech server."""
+        pass
+
+    def updatePunctuationLevel(self):
+        """ Punctuation level changed, inform this speechServer. """
+        mode = self._PUNCTUATION_MODE_MAP[settings.verbalizePunctuationStyle]
+        espeak.set_parameter(espeak.Parameter.Punctuation,mode,0)
+
+    def _paramToPercent(self, current, min, max):
+        """Convert a raw parameter value to a percentage given the current, minimum and maximum raw values.
+        @param current: The current value.
+        @type current: int
+        @param min: The minimum value.
+        @type current: int
+        @param max: The maximum value.
+        @type max: int
+        """
+        return int(round(float(current - min) / (max - min) * 100))
+
+    def _percentToParam(self, percent, min, max):
+        """Convert a percentage to a raw parameter value given the current percentage and the minimum and 
maximum raw parameter values.
+        @param percent: The current percentage.
+        @type percent: int
+        @param min: The minimum raw parameter value.
+        @type min: int
+        @param max: The maximum raw parameter value.
+        @type max: int
+        """
+        return int(round(float(percent) / 100 * (max - min) + min))
+
+    def _set_rate(self, acss_rate):
+        rate = self._percentToParam(acss_rate, minRate, maxRate)
+        espeak.set_parameter(espeak.Parameter.Rate, rate, 0)
+
+    def _set_pitch(self, acss_pitch):
+        pitch = self._percentToParam((acss_pitch *10), minPitch, maxPitch)
+        espeak.set_parameter(espeak.Parameter.Pitch, pitch, 0)
+
+    def _set_volume(self, acss_volume):
+        volume = int(acss_volume *10)
+        espeak.set_parameter(espeak.Parameter.Volume, volume, 0)
+
+    def _set_family(self, acss_family):
+        familyLocale = acss_family.get(speechserver.VoiceFamily.LOCALE)
+        if not familyLocale:
+            import locale
+            familyLocale, encoding = locale.getdefaultlocale()
+        if familyLocale:
+            lang = familyLocale.split('_')[0]
+            if lang:
+                espeak.set_voice(lang)
+        else:
+            name = acss_family.get(speechserver.VoiceFamily.NAME)
+            if name != self._default_voice_name:
+                espeak.set_voice(name)
+
+    def _apply_acss(self, acss):
+        if acss is None:
+            acss = settings.voices[settings.DEFAULT_VOICE]
+        current = self._current_voice_properties
+        for acss_property, method in self._acss_manipulators:
+            value = acss.get(acss_property)
+            if value is not None:
+                if current.get(acss_property) != value:
+                    method(value)
+                    current[acss_property] = value
+            elif acss_property == ACSS.AVERAGE_PITCH:
+                method(5.0)
+                current[acss_property] = 5.0
+            elif acss_property == ACSS.FAMILY \
+                    and acss == settings.voices[settings.DEFAULT_VOICE]:
+                # We need to explicitly reset (at least) the family.
+                # See bgo#626072.
+                #
+                method({})
+                current[acss_property] = {}
+
+    def __addVerbalizedPunctuation(self, oldText):
+        """Depending upon the users verbalized punctuation setting,
+        adjust punctuation symbols in the given text to their pronounced
+        equivalents. The pronounced text will either replace the
+        punctuation symbol or be inserted before it. In the latter case,
+        this is to retain spoken prosity.
+
+        Arguments:
+        - oldText: text to be parsed for punctuation.
+
+        Returns a text string with the punctuation symbols adjusted accordingly.
+        """
+
+        spokenEllipsis = messages.SPOKEN_ELLIPSIS + " "
+        newText = re.sub(ELLIPSIS, spokenEllipsis, oldText)
+        symbols = set(re.findall(PUNCTUATION, newText))
+        for symbol in symbols:
+            try:
+                level, action = punctuation_settings.getPunctuationInfo(symbol)
+            except:
+                continue
+
+            if level != punctuation_settings.LEVEL_NONE:
+                # eSpeak should handle it.
+                #
+                continue
+
+            charName = " %s " % chnames.getCharacterName(symbol)
+            if action == punctuation_settings.PUNCTUATION_INSERT:
+                charName += symbol
+            newText = re.sub(symbol, charName, newText)
+
+        if orca_state.activeScript:
+            newText = orca_state.activeScript.utilities.adjustForDigits(newText)
+
+        return newText
+
+    def _speak(self, text, acss, callback=None):
+        if isinstance(text, ACSS):
+            text = ''
+        text = self.__addVerbalizedPunctuation(text)
+        if orca_state.activeScript:
+            text = orca_state.activeScript.\
+                utilities.adjustForPronunciation(text)
+
+        # We need to make several replacements.
+        text = text.translate({
+            0x1: None, # used for embedded commands
+            0x3C: u"&lt;", # <: because of XML
+            0x3E: u"&gt;", # >: because of XML
+            0x5B: u" [", # [: [[ indicates phonemes
+        })
+
+        self._apply_acss(acss)
+        espeak.set_SynthCallback(callback)
+        espeak.synth(text)
+
+    def _say_all(self, iterator, orca_callback):
+        """Process another sayAll chunk.
+
+        Called by the gidle thread.
+
+        """
+        try:
+            context, acss = next(iterator)
+        except StopIteration:
+            pass
+        else:
+            def callback(event, pos, len):
+                t = self._CALLBACK_TYPE_MAP[event]
+                if t == speechserver.SayAllContext.PROGRESS:
+                    if pos >1:
+                        context.currentOffset = (context.startOffset +pos -1)
+                    else:
+                        context.currentOffset = context.startOffset
+                elif t == speechserver.SayAllContext.COMPLETED:
+                    context.currentOffset = context.endOffset
+                GLib.idle_add(orca_callback, context, t)
+                if t == speechserver.SayAllContext.COMPLETED:
+                    GLib.idle_add(self._say_all, iterator, orca_callback)
+            self._speak(context.utterance, acss, callback=callback)
+        return False # to indicate, that we don't want to be called again.
+
+    def _cancel(self):
+        espeak.cancel()
+
+    def _change_default_speech_rate(self, step, decrease=False):
+        acss = settings.voices[settings.DEFAULT_VOICE]
+        delta = step * (decrease and -1 or +1)
+        try:
+            rate = acss[ACSS.RATE]
+        except KeyError:
+            rate = 50
+        acss[ACSS.RATE] = max(0, min(99, rate + delta))
+        debug.println(debug.LEVEL_CONFIGURATION,
+                      "Speech rate is now %d" % rate)
+
+        self.speak(decrease and messages.SPEECH_SLOWER \
+                   or messages.SPEECH_FASTER, acss=acss)
+
+    def _change_default_speech_pitch(self, step, decrease=False):
+        acss = settings.voices[settings.DEFAULT_VOICE]
+        delta = step * (decrease and -1 or +1)
+        try:
+            pitch = acss[ACSS.AVERAGE_PITCH]
+        except KeyError:
+            pitch = 5
+        acss[ACSS.AVERAGE_PITCH] = max(0, min(9, pitch + delta))
+        debug.println(debug.LEVEL_CONFIGURATION,
+                      "Speech pitch is now %d" % pitch)
+
+        self.speak(decrease and messages.SPEECH_LOWER \
+                   or messages.SPEECH_HIGHER, acss=acss)
+
+    def _change_default_speech_volume(self, step, decrease=False):
+        acss = settings.voices[settings.DEFAULT_VOICE]
+        delta = step * (decrease and -1 or +1)
+        try:
+            volume = acss[ACSS.GAIN]
+        except KeyError:
+            volume = 5
+        acss[ACSS.GAIN] = max(0, min(9, volume + delta))
+        debug.println(debug.LEVEL_CONFIGURATION,
+                      "Speech volume is now %d" % volume)
+
+        self.speak(decrease and messages.SPEECH_SOFTER \
+                   or messages.SPEECH_LOUDER, acss=acss)
+
+    def getInfo(self):
+        return [self._SERVER_NAMES.get(self._id, self._id), self._id]
+
+    def getVoiceFamilies(self):
+        # Always offer the configured default voice with a language
+        # set according to the current locale.
+        from locale import getlocale, LC_MESSAGES
+        locale = getlocale(LC_MESSAGES)[0]
+        if locale is None or locale == 'C':
+            lang = None
+            dialect = None
+        else:
+            lang, dialect = locale.split('_')
+        list_synthesis_voices = espeak.list_voices()
+        families = [speechserver.VoiceFamily({ \
+              speechserver.VoiceFamily.NAME: self._default_voice_name,
+              #speechserver.VoiceFamily.GENDER: speechserver.VoiceFamily.MALE,
+              speechserver.VoiceFamily.DIALECT: dialect,
+              speechserver.VoiceFamily.LOCALE: lang})]
+        for voice in list_synthesis_voices:
+            families.append(speechserver.VoiceFamily({ \
+                speechserver.VoiceFamily.NAME: voice.name,
+                speechserver.VoiceFamily.DIALECT: voice.variant,
+                speechserver.VoiceFamily.LOCALE: voice.identifier.split("/")[-1]}))
+        return families
+
+    def speak(self, text=None, acss=None, interrupt=True):
+        #if interrupt:
+        #    self._cancel()
+
+        # "We will not interrupt a key echo in progress." (Said the comment in
+        # speech.py where these next two lines used to live. But the code here
+        # suggests we haven't been doing anything with the lastKeyEchoTime in
+        # years. TODO - JD: Dig into this and if it's truly useless, kill it.)
+        if self._lastKeyEchoTime:
+            interrupt = interrupt and (time.time() - self._lastKeyEchoTime) > 0.5
+
+        if text:
+            self._speak(text, acss)
+
+    def speakUtterances(self, utteranceList, acss=None, interrupt=True):
+        #if interrupt:
+        #    self._cancel()
+        for utterance in utteranceList:
+            if utterance:
+                self._speak(utterance, acss)
+
+    def sayAll(self, utteranceIterator, progressCallback):
+        GLib.idle_add(self._say_all, utteranceIterator, progressCallback)
+
+    def speakCharacter(self, character, acss=None):
+        #self._apply_acss(acss)
+
+        name = chnames.getCharacterName(character)
+        if not name:
+            self.speak(character, acss)
+            return
+
+        if orca_state.activeScript:
+            name = orca_state.activeScript.\
+                utilities.adjustForPronunciation(name)
+        self.speak(name, acss)
+
+    def speakKeyEvent(self, event):
+        if event.isPrintableKey() and event.event_string.isupper():
+            acss = settings.voices[settings.UPPERCASE_VOICE]
+        else:
+            acss = ACSS(settings.voices[settings.DEFAULT_VOICE])
+
+        event_string = event.getKeyName()
+        if orca_state.activeScript:
+            event_string = orca_state.activeScript.\
+                utilities.adjustForPronunciation(event_string)
+
+        lockingStateString = event.getLockingStateString()
+        event_string = "%s %s" % (event_string, lockingStateString)
+        self.speak(event_string, acss=acss)
+        self._lastKeyEchoTime = time.time()
+
+    def increaseSpeechRate(self, step=5):
+        self._change_default_speech_rate(step)
+
+    def decreaseSpeechRate(self, step=5):
+        self._change_default_speech_rate(step, decrease=True)
+
+    def increaseSpeechPitch(self, step=0.5):
+        self._change_default_speech_pitch(step)
+
+    def decreaseSpeechPitch(self, step=0.5):
+        self._change_default_speech_pitch(step, decrease=True)
+
+    def increaseSpeechVolume(self, step=0.5):
+        self._change_default_speech_volume(step)
+
+    def decreaseSpeechVolume(self, step=0.5):
+        self._change_default_speech_volume(step, decrease=True)
+
+    def stop(self):
+        self._cancel()
+
+    def shutdown(self):
+        pass
+
+    def reset(self, text=None, acss=None):
+        pass
+
diff --git a/src/orca/guilabels.py b/src/orca/guilabels.py
index 8d8d9cf..b5e365e 100644
--- a/src/orca/guilabels.py
+++ b/src/orca/guilabels.py
@@ -700,6 +700,10 @@ SPEECH_VOICE_TYPE_UPPERCASE = C_("VoiceType", "Uppercase")
# system. (http://devel.freebsoft.org/speechd)
SPEECH_DISPATCHER = _("Speech Dispatcher")

+# Translators this label refers to the name of particular speech synthesiser.
+# (http://espeak.sourceforge.net/)
+ESPEAK = _("eSpeak")
+
# Translators: This is a label for a group of options related to Orca's behavior
# when presenting an application's spell check dialog.
SPELL_CHECK = C_("OptionGroup", "Spell Check")
diff --git a/src/orca/settings.py b/src/orca/settings.py
index 045817d..933ec52 100644
--- a/src/orca/settings.py
+++ b/src/orca/settings.py
@@ -187,7 +187,7 @@ activeProfile   = ['Default', 'default']
profile         = ['Default', 'default']

# Speech
-speechFactoryModules         = ["speechdispatcherfactory"]
+speechFactoryModules         = ["speechdispatcherfactory","espeakfactory"]
speechServerFactory          = "speechdispatcherfactory"
speechServerInfo             = None # None means let the factory decide.
enableSpeech                 = True
--
2.5.0


From 62136a85f539b27782cc6e6191a732f5ef75b302 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Peter=20V=C3=A1gner?= <pvdeejay gmail com>
Date: Sun, 23 Aug 2015 21:03:23 +0200
Subject: [PATCH 2/2] Added author credits and fixes issue where less than and
greater symbols where spoken as their xml entities

---
src/orca/espeakfactory.py | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/src/orca/espeakfactory.py b/src/orca/espeakfactory.py
index 4585694..f4f5008 100644
--- a/src/orca/espeakfactory.py
+++ b/src/orca/espeakfactory.py
@@ -1,3 +1,10 @@
+# Orca
+#
+# Copyright 2015 Peter V??gner <pvdeejay gmail com>
+# Copyright 2006, 2007, 2008, 2009 Brailcom, o.p.s.
+#
+# Author: Tomas Cerha <cerha brailcom org>
+#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
@@ -273,8 +280,6 @@ class SpeechServer(speechserver.SpeechServer):
        # We need to make several replacements.
        text = text.translate({
            0x1: None, # used for embedded commands
-            0x3C: u"&lt;", # <: because of XML
-            0x3E: u"&gt;", # >: because of XML
            0x5B: u" [", # [: [[ indicates phonemes
        })

--
2.5.0


_______________________________________________
orca-list mailing list
orca-list gnome org
https://mail.gnome.org/mailman/listinfo/orca-list
Visit http://live.gnome.org/Orca for more information on Orca.
The manual is at http://library.gnome.org/users/gnome-access-guide/nightly/ats-2.html
The FAQ is at http://live.gnome.org/Orca/FrequentlyAskedQuestions
Log bugs and feature requests at http://bugzilla.gnome.org
Find out how to help at http://live.gnome.org/Orca/HowCanIHelp


--
Powered by Arch Linux! I am registered Linux user number 508465: https://linuxcounter.net/user/508465.html
My blog, Thoughts of a Dragon: http://www.stormdragon.tk/
get my public PGP key: gpg --keyserver wwwkeys.pgp.net --recv-key 43DDC193
Free and open source social networking, get your account TODAY! http://social.2mb.solutions/main/register
My Blackberry is Broken: http://is.gd/my_blackberry_is_broken
Well I asked my old pappy why he called his brew White lightnin' stead of mountain dew, I took a little sip 
and right away I knew. And my eyes bugged out and my face turned blue, lightnin' started flashin' thunder 
started crashin, (whew white lightnin).
George Jones

Attachment: signature.asc
Description: PGP signature



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]