<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://www.eric-scheibler.de/">
  <id>https://www.eric-scheibler.de/</id>
  <title>Erics Blog</title>
  <updated>2022-04-30T20:52:05Z</updated>
  <link rel="alternate" href="https://www.eric-scheibler.de/" type="text/html"/>
  <link rel="self" href="https://www.eric-scheibler.de/en/blog/feed.xml" type="application/atom+xml"/>
  <author>
    <name>Eric Scheibler</name>
    <uri>https://www.eric-scheibler.de</uri>
  </author>
  <entry>
    <id>tag:www.eric-scheibler.de,2022-04-30:/en/blog/2022/04/enable-the-android-screen-reader-talkback-from-the-linux-command-line/</id>
    <title type="html">Enable the Android screen reader Talkback from the Linux command line</title>
    <published>2022-04-30T20:52:05Z</published>
    <updated>2022-04-30T20:52:05Z</updated>
    <link rel="alternate" href="https://www.eric-scheibler.de/en/blog/2022/04/enable-the-android-screen-reader-talkback-from-the-linux-command-line/" type="text/html"/>
    <content type="html">&lt;p&gt;You can enable Androids screen reader from the Linux command line
with the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;adb shell settings put secure enabled_accessibility_services com.google.android.marvin.talkback/com.google.android.marvin.talkback.TalkBackService&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unfortunately you had to enable &lt;a
href="https://developer.android.com/studio/debug/dev-options"&gt;USB
Debugging&lt;/a&gt; on your Android device before. Furthermore you must
install the &lt;a
href="https://developer.android.com/studio/command-line/adb"&gt;Android
Debug Bridge&lt;/a&gt; on your PC. Otherwise the command above will not
work.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>tag:www.eric-scheibler.de,2021-07-03:/en/blog/2021/07/shell-script-for-adf-scanner-fujitsu-sp-1120/</id>
    <title type="html">Shell script for ADF scanner Fujitsu SP-1120</title>
    <published>2021-07-03T21:10:31Z</published>
    <updated>2021-07-03T21:10:31Z</updated>
    <link rel="alternate" href="https://www.eric-scheibler.de/en/blog/2021/07/shell-script-for-adf-scanner-fujitsu-sp-1120/" type="text/html"/>
    <content type="html">&lt;p&gt;Recently I bought a &lt;a
href="https://www.fujitsu.com/global/products/computing/peripheral/scanners/business/sp1120/"&gt;Fujitsu
SP-1120&lt;/a&gt; to replace my rather slow and old flatbed scanner. The
Fujitsu is an an automatic document feeder (ADF) with duplex support.
It’s faster, scans front and back pages in one go and produces a much
better image quality for ocr.&lt;/p&gt;
&lt;p&gt;This article describes the installation under Debian Linux and
provides a simple scan-to-pdf shell script.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2 id="install-the-scanner"&gt;Install the scanner&lt;/h2&gt;
&lt;p&gt;First download the &lt;a
href="https://imagescanner.fujitsu.com/global/dl/index-sps.html"&gt;Linux
driver package&lt;/a&gt; - at least it works under Ubuntu and Debian.&lt;/p&gt;
&lt;p&gt;Then install:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# dpkg -i pfusp-ubuntu18.04_2.1.1_amd64.deb&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And create the udev rule &lt;code&gt;99-fujitsu-sp-1120.rules&lt;/code&gt; under
&lt;code&gt;/etc/udev/rules.d&lt;/code&gt; with the following content:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Fujitsu ScanSnap SP-1120
ATTRS{idVendor}==&amp;quot;04c5&amp;quot;, ATTRS{idProduct}==&amp;quot;1473&amp;quot;, MODE=&amp;quot;0664&amp;quot;, GROUP=&amp;quot;scanner&amp;quot;, ENV{libsane_matched}=&amp;quot;yes&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adapt the product id, if you bought a SP-1125 or SP-1130. Afterwards
reload the udev rules:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# udevadm control --reload
# udevadm trigger&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly install the sane package (Scanner Access Now Easy) and turn on
the scanner and check, if the scanner was installed correctly:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# apt install sane
# scanadf --list-devices&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expected output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;device `pfusp:SP1120:001:093&amp;#39; is a FUJITSU SP1120 scanner&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="scan-script"&gt;Scan script&lt;/h2&gt;
&lt;p&gt;Download the shell script below, make it executable and adjust some
defaults to your liking. For example you may change the pdf viewer
variable. I use another shell script for that, which you can finde &lt;a
href="/en/blog/2015/04/script-to-extract-text-from-images-and-scanned-pdf-files/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To start scanning, put some document into your scanner and type:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;adf&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use the &lt;code&gt;-p&lt;/code&gt; parameter, to preview the ocr result of the
first page and &lt;code&gt;-h&lt;/code&gt; for help.&lt;/p&gt;
&lt;p&gt;Download: &lt;a href="/downloads/adf"&gt;adf&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash

# Script to control an ADF scanner
# - start scanning and create a single pdf file
# - with empty page and orientation detection
# - tested with Fujitsu SP-1120
#
# ... excessively borrowed from https://github.com/rocketraman/sane-scan-pdf
#
# Version: 0.1
# Date:    2021-06-16
# License: GNU General Public License
# Author:  Eric Scheibler
# E-Mail:  email [at] eric-scheibler [dot] de
# URL:     http://eric-scheibler.de/en/blog/2015/04/script-to-extract-text-from-images-and-scanned-pdf-files/
#
# Install:
#   sudo apt install imagemagick poppler-utils sane tesseract-ocr tesseract-ocr-deu tesseract-ocr-eng unpaper

OUTPUT=&amp;quot;scan.pdf&amp;quot;
TEXT_EDITOR=&amp;quot;/usr/bin/vim&amp;quot;
PDF_VIEWER=&amp;quot;$HOME/bin/ocr&amp;quot;
HELP=0
VERBOSE=0

# scanner params
DEVICE=pfusp
RESOLUTION=400
MODE=Lineart

# ocr params
OCR_LANGUAGE=deu
OCR_PREVIEW_FIRST_PAGE=0
OVERWRITE_OUTPUT_FILE=0


#####

TMP_DIR=$(mktemp -d -p &amp;quot;&amp;quot; scan.XXXXXXXXXX)
cleanup() {
    rm -rf &amp;quot;$TMP_DIR&amp;quot;
}
trap cleanup EXIT

function yes_or_no {
    while true; do
        read -p &amp;quot;$* [y/n]: &amp;quot; yn
        case $yn in
            [Yy]*) return 0  ;;
            [Nn]*) echo &amp;quot;Aborted&amp;quot; ; return  1 ;;
        esac
    done
}


# Parse command-line options
while [[ $# &amp;gt; 0 ]]; do
    case &amp;quot;$1&amp;quot; in
    -h|--help) HELP=1 ;;
    -v|--verbose) VERBOSE=1 ;;
    -o|--output) shift; OUTPUT=&amp;quot;$1&amp;quot; ;;
    -x|--device) shift; DEVICE=$1;;
    -m|--mode) shift; MODE=$1 ;;
    -r|--resolution) shift; RESOLUTION=$1 ;;
    -l|--language) shift; OCR_LANGUAGE=$1 ;;
    -p|--preview-first-page) OCR_PREVIEW_FIRST_PAGE=1 ;;
    -w|--overwrite-output-file) OVERWRITE_OUTPUT_FILE=1 ;;
    esac
    shift     # next option
done

if [[ $HELP == 1 ]]; then
    echo &amp;quot;$(basename $0) [OPTIONS]... [OUTPUT]&amp;quot;
    echo &amp;quot;&amp;quot;
    echo &amp;quot;OPTIONS&amp;quot;
    echo &amp;quot; -x, --device&amp;quot;
    echo &amp;quot;    Override scanner device name, defaulting to \&amp;quot;pfusp\&amp;quot;&amp;quot;
    echo &amp;quot; -m, --mode&amp;quot;
    echo &amp;quot;     Mode e.g. Lineart (default), Halftone, Gray, Color, etc.&amp;quot;
    echo &amp;quot; -r, --resolution&amp;quot;
    echo &amp;quot;     Resolution e.g 300 (default)&amp;quot;
    echo &amp;quot; -l, --language &amp;lt;lang&amp;gt;&amp;quot;
    echo &amp;quot;     which language to use for OCR&amp;quot;
    echo &amp;quot; -p, --preview-first-page&amp;quot;
    echo &amp;quot;     OCR first page and preview in $TEXT_EDITOR&amp;quot;
    echo &amp;quot;&amp;quot;
    echo &amp;quot;OUTPUT&amp;quot;
    echo &amp;quot; -o, --output &amp;lt;outputfile&amp;gt;&amp;quot;
    echo &amp;quot;     Output to named file default=scan.pdf&amp;quot;
    echo &amp;quot; -w, --overwrite-output-file&amp;quot;
    echo &amp;quot;     Overwrite the output pdf file, if it already exists&amp;quot;
    echo &amp;quot; -v, --verbose&amp;quot;
    exit 0
fi

if [[ $VERBOSE == 0 ]]; then
    quiet_paran=&amp;quot;--quiet&amp;quot;
    suppress_error_messages=&amp;quot;2&amp;gt; /dev/null&amp;quot;
fi

if [[ &amp;quot;$OUTPUT&amp;quot; == &amp;quot;&amp;quot; ]]; then
    echo &amp;gt;&amp;amp;2 &amp;quot;Output file must be specified. Aborting.&amp;quot;
    exit 1
fi

if [[ -f &amp;quot;$OUTPUT&amp;quot; ]]; then
    if [[ $OVERWRITE_OUTPUT_FILE == 0 ]]; then
        echo &amp;gt;&amp;amp;2 &amp;quot;Output file $OUTPUT already exists. Aborting.&amp;quot;
        exit 1
    else
        rm &amp;quot;$OUTPUT&amp;quot;
    fi
fi


echo &amp;gt;&amp;amp;2 &amp;quot;Scanning...&amp;quot;
scanadf --device-name &amp;quot;$DEVICE&amp;quot; --source Adf-duplex --resolution $RESOLUTION --mode $MODE -o $TMP_DIR/scan-%04d
if [[ $? != 0 ]]; then
    exit 1
fi
echo &amp;quot;&amp;quot;


shopt -s extglob nullglob
image_files=($TMP_DIR/scan-[0-9]*)
num_scans=${#image_files[@]}
if [[ $num_scans &amp;gt; 0 ]]; then

    if [[ $OCR_PREVIEW_FIRST_PAGE == 1 ]]; then
        echo &amp;quot;Creating preview...&amp;quot;
        preview_image_file=&amp;quot;${image_files[0]}&amp;quot;
        preview_text_file=&amp;quot;$TMP_DIR/preview_first_page.txt&amp;quot;
        # ocr
        eval tesseract $preview_image_file ${preview_text_file%.*} -l $OCR_LANGUAGE --psm 12 $suppress_error_messages
        # show
        $TEXT_EDITOR $preview_text_file
        if ! yes_or_no &amp;quot;Proceed?&amp;quot;; then
            exit 0
        fi
        # remove preview text file
        rm $preview_text_file
        echo &amp;quot;&amp;quot;
    fi

    echo &amp;quot;Processing $num_scans pages&amp;quot;
    for image_file in ${image_files[@]}; do
        echo &amp;quot;Process $(basename $image_file)&amp;quot;

        # unpaper
        eval unpaper $quiet_paran --overwrite --dpi $RESOLUTION $image_file $image_file $suppress_error_messages

        # convert to tiff
        convert -density ${RESOLUTION}x${RESOLUTION} -units PixelsPerInch $image_file ${image_file}.tiff
        rm $image_file

        # orientation detection
        orientation_result=$(eval tesseract ${image_file}.tiff - --psm 0 $suppress_error_messages) || orientation_result=
        if [[ $orientation_result == *&amp;quot;Rotate: 180&amp;quot;* ]]; then
            echo &amp;quot;Image orientation is upside down, rotate&amp;quot;
            convert -rotate 180 ${image_file}.tiff ${image_file}.tiff
        fi

        # empty page detection
        percentage_white=$(convert ${image_file}.tiff -fuzz 0% -negate -threshold 0 -negate -format &amp;quot;%[fx:100*mean]&amp;quot; info:) || percentage_white=0
        is_empty_page=$(echo &amp;quot;$percentage_white &amp;gt;= 99.8&amp;quot; | bc -l)
        if [[ $is_empty_page == 1 &amp;amp;&amp;amp; $orientation_result == &amp;quot;&amp;quot; ]]; then
            echo &amp;quot;Empty page removed&amp;quot;

        else
            eval tesseract ${image_file}.tiff $image_file -l $OCR_LANGUAGE pdf $suppress_error_messages
            rm ${image_file}.tiff
        fi

        echo &amp;quot;&amp;quot;
    done

    # rename or unite created pdf(s)
    pdf_files=($TMP_DIR/scan-[0-9]*.pdf)
    num_pdf_files=${#pdf_files[@]}
    if [[ $num_pdf_files == 1 ]]; then
        echo &amp;quot;Renaming...&amp;quot;
        mv $TMP_DIR/scan-0*.pdf &amp;quot;$OUTPUT&amp;quot;
    elif [[ $num_pdf_files &amp;gt; 1 ]]; then
        echo &amp;quot;Concatenating pdfs...&amp;quot;
        pdfunite &amp;quot;${pdf_files[@]}&amp;quot; &amp;quot;$OUTPUT&amp;quot;
    fi
fi

if [[ -f &amp;quot;$OUTPUT&amp;quot; ]]; then
    echo &amp;quot;Done.&amp;quot;
    if [[ $PDF_VIEWER != &amp;quot;&amp;quot; ]]; then
        if yes_or_no &amp;quot;Open ${OUTPUT}?&amp;quot;; then
            $PDF_VIEWER $OUTPUT
        fi
    fi
else
    echo &amp;quot;No scans found.&amp;quot;
fi
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <id>tag:www.eric-scheibler.de,2021-06-12:/en/blog/2021/06/easy-and-accessible-network-request-analysis/</id>
    <title type="html">Easy and accessible network request analysis</title>
    <published>2021-06-12T12:56:47Z</published>
    <updated>2021-06-12T12:56:47Z</updated>
    <link rel="alternate" href="https://www.eric-scheibler.de/en/blog/2021/06/easy-and-accessible-network-request-analysis/" type="text/html"/>
    <content type="html">&lt;p&gt;I had to analyse some network requests while I was working on an
Android app project. That turned out to be surprisingly easy, using &lt;a
href="https://mitmproxy.org"&gt;mitmproxy&lt;/a&gt;. This article covers the
proxy installation and usage by &lt;code&gt;wget&lt;/code&gt; and within the source
code of an Android app.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Basically it works like a &lt;a
href="https://en.wikipedia.org/wiki/Man-in-the-middle_attack"&gt;mitm
attack&lt;/a&gt;. You have to install a proxy server somewhere in your local
network and route client requests through it. I’ve chosen mitmproxy,
because it’s available for the Linux console, simple to use and its ui
is fairly accessible to screen readers like &lt;a
href="https://brltty.app"&gt;brltty&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Install: &lt;code&gt;sudo apt install mitmproxy&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Run:
&lt;code&gt;mitmproxy --listen-host 192.168.100.100 --listen-port 22222 --save-stream-file +requests.log&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="wget"&gt;Wget&lt;/h2&gt;
&lt;p&gt;Now try to send a http request through the proxy:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wget -e https_proxy=192.168.100.100:22222 http://example.org&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It should appear in the main view of mitmproxy.&lt;/p&gt;
&lt;p&gt;If you want to intercept encrypted requests as well, your client must
trust the ssl certificate, which mitmproxy created during its first
launch. You may find it under
&lt;code&gt;~/.mitmproxy/mitmproxy-ca-cert.pem&lt;/code&gt;. Copy the file onto your
client machine and send a second request:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wget -e https_proxy=192.168.100.100:22222 --ca-certificate /path/to/mitmproxy-ca-cert.pem https://example.org&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If everything went well, you should see two lines in the mitmproxy
main view. Use the arrow keys to select a request and press ENTER to
show its details.&lt;/p&gt;
&lt;p&gt;Unfortunately the view doesn’t use the hardware console cursor.
Therefore screen readers can’t announce the selected list item properly.
If you use brltty, you may disable the autom. cursor tracking
(CAPSLOCK+ENTER) and follow the selected list item manually. It’s
prefixed by “&amp;gt;&amp;gt;”.&lt;/p&gt;
&lt;p&gt;The request details view consists of three tabs: request, response
and details. Select them with left and right arrow keys or tab through).
I was only interested in the request cookie headers, which I found
directly in the request tab.&lt;/p&gt;
&lt;h2 id="android-app-development"&gt;Android app development&lt;/h2&gt;
&lt;p&gt;First add the same mitmproxy certificate to your apps “res”
folder:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir /path/to/android_project/app/src/main/res/raw/
cp ~/.mitmproxy/mitmproxy-ca-cert.pem /path/to/android_project/app/src/main/res/raw/mitm_proxy.pem&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then include the following code into your app to:&lt;/p&gt;
&lt;ol type="1"&gt;
&lt;li&gt;set the proxy&lt;/li&gt;
&lt;li&gt;use the SSL certificate from above for the request&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="sourceCode" id="cb4"&gt;&lt;pre
class="sourceCode java"&gt;&lt;code class="sourceCode java"&gt;&lt;span id="cb4-1"&gt;&lt;a href="#cb4-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;import&lt;/span&gt; &lt;span class="im"&gt;org&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;example&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;project&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;BuildConfig&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-2"&gt;&lt;a href="#cb4-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;import&lt;/span&gt; &lt;span class="im"&gt;java&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;net&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;InetSocketAddress&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-3"&gt;&lt;a href="#cb4-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;import&lt;/span&gt; &lt;span class="im"&gt;java&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;net&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;Proxy&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-4"&gt;&lt;a href="#cb4-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;import&lt;/span&gt; &lt;span class="im"&gt;java&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;net&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;URL&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-5"&gt;&lt;a href="#cb4-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;import&lt;/span&gt; &lt;span class="im"&gt;java&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;security&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;KeyStore&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-6"&gt;&lt;a href="#cb4-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;import&lt;/span&gt; &lt;span class="im"&gt;java&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;security&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;cert&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;CertificateFactory&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-7"&gt;&lt;a href="#cb4-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;import&lt;/span&gt; &lt;span class="im"&gt;javax&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;net&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;ssl&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;HttpsURLConnection&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-8"&gt;&lt;a href="#cb4-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;import&lt;/span&gt; &lt;span class="im"&gt;javax&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;net&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;ssl&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;SSLContext&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-9"&gt;&lt;a href="#cb4-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;import&lt;/span&gt; &lt;span class="im"&gt;javax&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;net&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;ssl&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="im"&gt;TrustManagerFactory&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-10"&gt;&lt;a href="#cb4-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb4-11"&gt;&lt;a href="#cb4-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;URL&lt;/span&gt; url &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="kw"&gt;new&lt;/span&gt; &lt;span class="bu"&gt;URL&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;https://example.org&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-12"&gt;&lt;a href="#cb4-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;String&lt;/span&gt; proxyHost &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;192.168.100.100&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-13"&gt;&lt;a href="#cb4-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dt"&gt;int&lt;/span&gt; proxyPort &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="dv"&gt;22222&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-14"&gt;&lt;a href="#cb4-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb4-15"&gt;&lt;a href="#cb4-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;HttpsURLConnection&lt;/span&gt; connection &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="kw"&gt;null&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-16"&gt;&lt;a href="#cb4-16" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="cf"&gt;if&lt;/span&gt; &lt;span class="op"&gt;(&lt;/span&gt;BuildConfig&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;DEBUG&lt;/span&gt;&lt;span class="op"&gt;)&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-17"&gt;&lt;a href="#cb4-17" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb4-18"&gt;&lt;a href="#cb4-18" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="bu"&gt;Proxy&lt;/span&gt; proxy &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="kw"&gt;new&lt;/span&gt; &lt;span class="bu"&gt;Proxy&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-19"&gt;&lt;a href="#cb4-19" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            &lt;span class="bu"&gt;Proxy&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;Type&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;HTTP&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="kw"&gt;new&lt;/span&gt; &lt;span class="bu"&gt;InetSocketAddress&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;proxyHost&lt;span class="op"&gt;,&lt;/span&gt; proxyPort&lt;span class="op"&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-20"&gt;&lt;a href="#cb4-20" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    connection &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="bu"&gt;HttpsURLConnection&lt;/span&gt;&lt;span class="op"&gt;)&lt;/span&gt; url&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;openConnection&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;proxy&lt;span class="op"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-21"&gt;&lt;a href="#cb4-21" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb4-22"&gt;&lt;a href="#cb4-22" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="cf"&gt;try&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-23"&gt;&lt;a href="#cb4-23" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="bu"&gt;CertificateFactory&lt;/span&gt; cf &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="bu"&gt;CertificateFactory&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;getInstance&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;X.509&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-24"&gt;&lt;a href="#cb4-24" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="bu"&gt;InputStream&lt;/span&gt; caInput&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-25"&gt;&lt;a href="#cb4-25" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="co"&gt;// Create a KeyStore containing our trusted CAs&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-26"&gt;&lt;a href="#cb4-26" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="bu"&gt;KeyStore&lt;/span&gt; keyStore &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="bu"&gt;KeyStore&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;getInstance&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="bu"&gt;KeyStore&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;getDefaultType&lt;/span&gt;&lt;span class="op"&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-27"&gt;&lt;a href="#cb4-27" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        keyStore&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;load&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="kw"&gt;null&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="kw"&gt;null&lt;/span&gt;&lt;span class="op"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-28"&gt;&lt;a href="#cb4-28" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="co"&gt;// load mitm_proxy certificate&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-29"&gt;&lt;a href="#cb4-29" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        caInput &lt;span class="op"&gt;=&lt;/span&gt; ApplicationInstance&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;getContext&lt;/span&gt;&lt;span class="op"&gt;().&lt;/span&gt;&lt;span class="fu"&gt;getResources&lt;/span&gt;&lt;span class="op"&gt;().&lt;/span&gt;&lt;span class="fu"&gt;openRawResource&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;R&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;raw&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;mitm_proxy&lt;/span&gt;&lt;span class="op"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-30"&gt;&lt;a href="#cb4-30" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="cf"&gt;try&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-31"&gt;&lt;a href="#cb4-31" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            keyStore&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;setCertificateEntry&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;ca1&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; cf&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;generateCertificate&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;caInput&lt;span class="op"&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-32"&gt;&lt;a href="#cb4-32" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="op"&gt;}&lt;/span&gt; &lt;span class="cf"&gt;finally&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-33"&gt;&lt;a href="#cb4-33" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;            caInput&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;close&lt;/span&gt;&lt;span class="op"&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-34"&gt;&lt;a href="#cb4-34" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-35"&gt;&lt;a href="#cb4-35" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="co"&gt;// Create a TrustManager that trusts the CAs in our KeyStore&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-36"&gt;&lt;a href="#cb4-36" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="bu"&gt;String&lt;/span&gt; tmfAlgorithm &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="bu"&gt;TrustManagerFactory&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;getDefaultAlgorithm&lt;/span&gt;&lt;span class="op"&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-37"&gt;&lt;a href="#cb4-37" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="bu"&gt;TrustManagerFactory&lt;/span&gt; tmf &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="bu"&gt;TrustManagerFactory&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;getInstance&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;tmfAlgorithm&lt;span class="op"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-38"&gt;&lt;a href="#cb4-38" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        tmf&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;init&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;keyStore&lt;span class="op"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-39"&gt;&lt;a href="#cb4-39" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="co"&gt;// Create an SSLContext that uses our TrustManager&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-40"&gt;&lt;a href="#cb4-40" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="bu"&gt;SSLContext&lt;/span&gt; sslContext &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="bu"&gt;SSLContext&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;getInstance&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;TLS&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-41"&gt;&lt;a href="#cb4-41" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        sslContext&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;init&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="kw"&gt;null&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; tmf&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;getTrustManagers&lt;/span&gt;&lt;span class="op"&gt;(),&lt;/span&gt; &lt;span class="kw"&gt;null&lt;/span&gt;&lt;span class="op"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-42"&gt;&lt;a href="#cb4-42" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        connection&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;setSSLSocketFactory&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;sslContext&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;getSocketFactory&lt;/span&gt;&lt;span class="op"&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-43"&gt;&lt;a href="#cb4-43" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="op"&gt;}&lt;/span&gt; &lt;span class="cf"&gt;catch&lt;/span&gt; &lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="bu"&gt;Exception&lt;/span&gt; e&lt;span class="op"&gt;)&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-44"&gt;&lt;a href="#cb4-44" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;        &lt;span class="bu"&gt;System&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;out&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;println&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-45"&gt;&lt;a href="#cb4-45" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;                &lt;span class="bu"&gt;String&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;format&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;Cert exception: &lt;/span&gt;&lt;span class="sc"&gt;%1$s&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; e&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;getMessage&lt;/span&gt;&lt;span class="op"&gt;()));&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-46"&gt;&lt;a href="#cb4-46" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-47"&gt;&lt;a href="#cb4-47" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb4-48"&gt;&lt;a href="#cb4-48" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt; &lt;span class="cf"&gt;else&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-49"&gt;&lt;a href="#cb4-49" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;    connection &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="bu"&gt;HttpsURLConnection&lt;/span&gt;&lt;span class="op"&gt;)&lt;/span&gt; url&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="fu"&gt;openConnection&lt;/span&gt;&lt;span class="op"&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-50"&gt;&lt;a href="#cb4-50" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Of course it’s also possible to reroute every request from the
Android device through your proxy. Then you have to add the certificate
to your cert-store (Android settings -&amp;gt; security) and set a
system-wide proxy. Unfortunately not every application respects this -
for example browsers may define their own proxy settings.&lt;/p&gt;
&lt;p&gt;Therefore if you want to debug your own app, go for the direct
integration - much easier to implement.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>tag:www.eric-scheibler.de,2018-12-29:/en/blog/2018/12/mpd-announce-current-song/</id>
    <title type="html">MPD - Announce current song</title>
    <published>2018-12-29T02:14:13Z</published>
    <updated>2018-12-29T02:14:13Z</updated>
    <link rel="alternate" href="https://www.eric-scheibler.de/en/blog/2018/12/mpd-announce-current-song/" type="text/html"/>
    <content type="html">&lt;p&gt;My server runs an &lt;a href="https://www.musicpd.org"&gt;MPD&lt;/a&gt; instance
for music playback. The following script for Mac announces the currently
playing song after every song change. It uses Mac OS build in TTS and
tries to guess the song title language.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Download: &lt;a
href="/downloads/mpd_speak_current_song.py"&gt;mpd_speak_current_song.py&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/Users/eric/.virtualenvs/speak_current_song/bin/python
# -*- coding: utf-8 -*-

# mpd: speak currently playing song after it has changed
#
# This script uses the mpd client mpc to watch for song changes on the running mpd server and speaks them aloud
# Song title language detection with https://github.com/Mimino666/langdetect
# It&amp;#39;s written for Mac OS but could be easily adapted to Linux as well (change tts_command variable)
#
# author: Eric Scheibler &amp;lt;email@eric-scheibler.de&amp;gt;
# Date: 2019-03-29
# Version: 0.2
#
# Installation requirements:
#   brew install mpc
#   pip install langdetect
#
# Usage: Must run in background for example within a screen session
#   ./mpd_speak_current_song

# configuration
#
# adapt the following params to your needs
# leave them blank if not required
#
# mpd host and port
mpd_host = &amp;quot;192.168.2.11&amp;quot;
mpd_port = &amp;quot;6600&amp;quot;
# audio output device
# Mac: get all devices with: say -a &amp;#39;?&amp;#39;
audio_output_device = &amp;quot;&amp;quot;
# voice table
# Mac: get all installed voices with: say -v &amp;#39;?&amp;#39;
voices = {
        &amp;quot;de&amp;quot; : {&amp;quot;name&amp;quot;:&amp;quot;Yannick&amp;quot;, &amp;quot;rate&amp;quot;:280},
        &amp;quot;en&amp;quot; : {&amp;quot;name&amp;quot;:&amp;quot;Samantha&amp;quot;, &amp;quot;rate&amp;quot;:200},
        &amp;quot;fr&amp;quot; : {&amp;quot;name&amp;quot;:&amp;quot;Thomas&amp;quot;, &amp;quot;rate&amp;quot;:200},
        # default: different German voice
        &amp;quot;default&amp;quot; : {&amp;quot;name&amp;quot;:&amp;quot;Anna&amp;quot;, &amp;quot;rate&amp;quot;:240}
        }


import subprocess, sys, threading
from langdetect import detect, DetectorFactory
from langdetect.lang_detect_exception import LangDetectException


# construct commands
#
# control mpd with: mpc ...
mpc_command = [&amp;quot;mpc&amp;quot;, &amp;quot;--quiet&amp;quot;]
if mpd_host:
    mpc_command += [&amp;quot;--host&amp;quot;, mpd_host]
if mpd_port:
    mpc_command += [&amp;quot;--port&amp;quot;, mpd_port]
# speak with: say ...
tts_command = [&amp;quot;say&amp;quot;]
if audio_output_device:
    tts_command += [&amp;quot;-a&amp;quot;, audio_output_device]


class SpeakCurrentSong(threading.Thread):

    def __init__(self, song, voice):
        threading.Thread.__init__(self)
        self.song = song
        self.voice = voice
        # class variables
        self.process = None
        self.finished = False
        self.killed = False

    def run(self):
        # pause playback
        if not self.killed:
            self.execute(mpc_command + [&amp;quot;pause&amp;quot;])
        # announce new song
        if not self.killed:
            self.execute(tts_command + [&amp;quot;-v&amp;quot;, self.voice.get(&amp;quot;name&amp;quot;), &amp;quot;-r&amp;quot;, str(self.voice.get(&amp;quot;rate&amp;quot;)), self.song])
        # resume playback
        if not self.killed:
            self.execute(mpc_command + [&amp;quot;play&amp;quot;])
        self.finished = True

    def execute(self, command):
        self.process = subprocess.Popen(command)
        self.process.wait()

    def kill(self):
        self.process.kill()
        self.killed = True


def main():
    speakCurrentSongThread = None
    # force deterministic language detection results
    DetectorFactory.seed = 0
    while True:
        try:
            # wait for change of current song
            current_song = subprocess.check_output(mpc_command + [&amp;quot;current&amp;quot;, &amp;quot;--wait&amp;quot;], encoding=&amp;#39;UTF-8&amp;#39;).strip()
        except subprocess.CalledProcessError as e:
            # could not get current song
            error_message = &amp;quot;Error: Could not get the current song&amp;quot;
            subprocess.Popen(tts_command + [&amp;quot;-v&amp;quot;, &amp;quot;Alex&amp;quot;, error_message])
            print(&amp;#39;{}: {}&amp;#39;.format(error_message, e))
            sys.exit(1)
        else:
            # detect language and get tts voice
            voice = voices.get(&amp;quot;default&amp;quot;)
            try:
                language = detect(current_song)
            except LangDetectException as e:
                pass
            else:
                if language in voices.keys():
                    voice = voices.get(language)
            # start SpeakCurrentSong thread
            if speakCurrentSongThread:
                # kill old thread if necessary
                if not speakCurrentSongThread.finished:
                    speakCurrentSongThread.kill()
                speakCurrentSongThread.join()
            speakCurrentSongThread = SpeakCurrentSong(current_song, voice)
            speakCurrentSongThread.start()
            # log to stdout
            print(&amp;#39;{}.{}: {}&amp;#39;.format(language, voice.get(&amp;quot;name&amp;quot;), current_song))


if __name__ == &amp;quot;__main__&amp;quot;:
    main()
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <id>tag:www.eric-scheibler.de,2018-02-10:/en/blog/2018/02/clipboardtospeech-android-app-speaks-the-clipboard-content/</id>
    <title type="html">ClipboardToSpeech - Android app speaks the clipboard content</title>
    <published>2018-02-10T09:43:24Z</published>
    <updated>2018-02-10T09:43:24Z</updated>
    <link rel="alternate" href="https://www.eric-scheibler.de/en/blog/2018/02/clipboardtospeech-android-app-speaks-the-clipboard-content/" type="text/html"/>
    <content type="html">&lt;p&gt;This Android application Monitors the clipboard and speaks newly
added text immediately.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Additional features are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses the default tts engine for speech output&lt;/li&gt;
&lt;li&gt;Contains a clipboard history with the last entries&lt;/li&gt;
&lt;li&gt;Autostarts at boot time&lt;/li&gt;
&lt;li&gt;Creates a low-priority notification when the background service is
running&lt;/li&gt;
&lt;li&gt;Requires at least Android version 4.1 and is compatible to 8.0 and
8.1&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Clone the git repository:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/scheibler/ClipboardToSpeech/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then install the pre-compiled version with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;adb install app/src/main/misc/releases/ClipboardToSpeech_vx.x.x.apk&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or build manually:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./gradlew build&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <id>tag:www.eric-scheibler.de,2017-08-13:/en/blog/2017/08/game-of-thrones-tactile-maps-for-the-blind-and-visually-impaired/</id>
    <title type="html">Game of thrones - Tactile maps for the blind and visually impaired</title>
    <published>2017-08-13T11:01:16Z</published>
    <updated>2017-08-13T11:01:16Z</updated>
    <link rel="alternate" href="https://www.eric-scheibler.de/en/blog/2017/08/game-of-thrones-tactile-maps-for-the-blind-and-visually-impaired/" type="text/html"/>
    <content type="html">&lt;p&gt;I’m a great fan of the TV series Game of Thrones and the books it’s
based on. In cooperation with Jens Bornschein from the TU Dresden I’ve
created tactile maps of Westeros and Essos and made them available for
download . Be aware, you need a braille printer or similar device to
produce tactile maps.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Currently the 7th season of Game of Thrones is running and outright
in the first episode we see a big and still incomplete map of Westeros.
Its creation was ordered by no lesser than Cersei Lannister. I suspect
her point in doing so is to be able to better explain the threats facing
house Lannister from all sides.&lt;/p&gt;
&lt;p&gt;As a blind fan my understanding of that worlds geography is limited
to the information I could extract from the books descriptions. To
obtain a better overview in regard to the locations of the great house’s
homes, I took this as an opportunity to crate a tactile map for my own
use. I asked Jens for help, because he was at the time part of project
&lt;a
href="https://tu-dresden.de/ing/informatik/ai/mci/forschung/forschungsgebiete/tangram"&gt;TANGRAM&lt;/a&gt;
at TU Dresden, focused on interactively creating tactile maps and images
for the blind.&lt;/p&gt;
&lt;p&gt;We took the map template from &lt;a
href="http://gameofthrones.net"&gt;gameofthrones.net&lt;/a&gt; and had a brief
discussion on which objects to include. Then Jens drew the contours of
the continents and placed the POI, while I reviewed the progress on a &lt;a
href="http://www.hyperbraille.de/?lang=en"&gt;2-dimensional braille
display&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Together we created these two maps:&lt;/p&gt;
&lt;ol type="1"&gt;
&lt;li&gt;A landscape format world map containing Westeros and Essos.
(unfortunately there was no space left to include Sothoryos)&lt;/li&gt;
&lt;li&gt;A portrait format map of Westeros, including the most important
POI.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The maps are available in SVG format, each containing a second page
detailing the abbreviations used.&lt;/p&gt;
&lt;p&gt;We offer two sets of maps:&lt;/p&gt;
&lt;p&gt;If you have access to a &lt;a href="https://viewplus.com"&gt;ViewPlus
braille printer&lt;/a&gt; please download &lt;a
href="/downloads/got_maps_viewPlus-tiger.zip"&gt;got_maps_viewPlus-tiger.zip&lt;/a&gt;.
It produces the best results and should be easy to use.&lt;/p&gt;
&lt;p&gt;Alternatively you can try &lt;a
href="/downloads/got_maps_print.zip"&gt;got_maps_print.zip&lt;/a&gt;. This
package is mainly intended for swell paper printers. The results are a
bit less accurate but it still may give you a principle understanding of
the worlds geography.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>tag:www.eric-scheibler.de,2016-07-03:/en/blog/2016/07/disable-laptop-screen-on-linux/</id>
    <title type="html">Disable laptop screen on Linux</title>
    <published>2016-07-03T20:29:42Z</published>
    <updated>2016-07-03T20:29:42Z</updated>
    <link rel="alternate" href="https://www.eric-scheibler.de/en/blog/2016/07/disable-laptop-screen-on-linux/" type="text/html"/>
    <content type="html">&lt;p&gt;This article describes how to disable the laptop screen on Lenovo
Thinkpads. This saves a lot of power and enhances your privacy. Root
privileges required. Tested with a Thinkpad T450 on Debian Stretch.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Save the following script under /usr/local/sbin/monitor:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
if [ -f /tmp/screen_stays_active ]; then
    echo 0 &amp;gt; /sys/class/backlight/intel_backlight/brightness
    rm /tmp/screen_stays_active
    echo &amp;quot;Disable screen&amp;quot;
else
    echo 850 &amp;gt; /sys/class/backlight/intel_backlight/brightness
    touch /tmp/screen_stays_active
    echo &amp;quot;Restore screen brightness&amp;quot;
fi&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and make it executable:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chmod +x /usr/local/sbin/monitor&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The laptop monitor enables itself in irregular intervals again.
Therefore you may create three cronjobs for the root user which disable
the screen every 20 seconds continuously. A script with an endless
while-loop certainly would serve the same purpose.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# crontab -e

*  *   *   *   *   if [ ! -f /tmp/screen_stays_active ]; then echo 0 &amp;gt; /sys/class/backlight/intel_backlight/brightness; fi
*  *   *   *   *   sleep 20 &amp;amp;&amp;amp; if [ ! -f /tmp/screen_stays_active ]; then echo 0 &amp;gt; /sys/class/backlight/intel_backlight/brightness; fi
*  *   *   *   *   sleep 40 &amp;amp;&amp;amp; if [ ! -f /tmp/screen_stays_active ]; then echo 0 &amp;gt; /sys/class/backlight/intel_backlight/brightness; fi&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Please note, that the screen is disabled directly after you’ve
created the cronjobs above and it will stay disabled even after
reboot.&lt;/p&gt;
&lt;p&gt;You may toggle the brightness state with the command
&lt;code&gt;sudo monitor&lt;/code&gt;. If you don’t want to enter a password every
time, you may define an exception in the sudoers file. To do so enter
the &lt;code&gt;visudo&lt;/code&gt; command as root and append the following
line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MY_USER_NAME ALL = NOPASSWD: /usr/local/sbin/monitor&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By the way: If you often enable the keyboard backlight accidentally
via fn+space and you want to disable that automatically too, add the
following cronjob:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# crontab -e

*  *   *   *   *   echo 0 &amp;gt; /sys/class/leds/tpacpi::kbd_backlight/brightness&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Find additional information at &lt;a
href="http://www.thinkwiki.org/wiki/ThinkLight"
class="uri"&gt;http://www.thinkwiki.org/wiki/ThinkLight&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>tag:www.eric-scheibler.de,2016-05-06:/en/blog/2016/05/android-app-tactileclock-vibrates-the-current-time/</id>
    <title type="html">Android app Tactile Clock vibrates the current time</title>
    <published>2016-05-06T21:45:20Z</published>
    <updated>2016-05-06T21:45:20Z</updated>
    <link rel="alternate" href="https://www.eric-scheibler.de/en/blog/2016/05/android-app-tactileclock-vibrates-the-current-time/" type="text/html"/>
    <content type="html">&lt;p&gt;This Android app vibrates the current time when the display is locked
and you double-click the power button of your device.&lt;/p&gt;
&lt;p&gt;The app is free software. You can find the links to the play store, a
f-droid repository and the source code below.&lt;/p&gt;
&lt;p&gt;The latest version was released on June 23rd, 2026.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2 id="how-it-works"&gt;How it works&lt;/h2&gt;
&lt;p&gt;Double-click the power button. This might take some practice to work
reliably. Press slowly and deliberately. If you double-click too fast
the system only recognizes a single click and turns the screen on
instead.&lt;/p&gt;
&lt;p&gt;The time vibration is structured as follows:&lt;/p&gt;
&lt;p&gt;A short vibration stands for the digit 1 and a long one for the digit
5. The 2 is represented by two consecutive short vibrations, the 6 by a
long and a short one and so on. The 0 constitutes an exception with two
long vibrations.&lt;/p&gt;
&lt;h3 id="examples"&gt;Examples:&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;00:00 =  --   --
01:18 =  .    .  -...
02:51 =  ..    -  .
10:11 =  .  --    .  .
19:06 =  .  -....    -.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="explanation"&gt;Explanation:&lt;/h3&gt;
&lt;p&gt;The time is processed digit by digit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;. = short vibration&lt;/li&gt;
&lt;li&gt;- = long vibration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Leading zeros in the hour and minute fields are omitted.&lt;/p&gt;
&lt;h3 id="gaps"&gt;Gaps:&lt;/h3&gt;
&lt;p&gt;To simplify the recognition of the vibration pattern, there exist
three kind of gaps with different durations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[]: A short gap between vibrations in the same digit&lt;/li&gt;
&lt;li&gt;[  ]: A medium gap for the separation of two digits within the hour
and minute field&lt;/li&gt;
&lt;li&gt;[    ]: A long gap to split hours and minutes&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="additional-features"&gt;Additional features&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Keep informed about the current time. The app can vibrate the
current time automatically every X minutes or every hour.&lt;/li&gt;
&lt;li&gt;Play the &lt;a
href="https://en.wikipedia.org/wiki/Greenwich_Time_Signal"&gt;Greenwich
Time Signal&lt;/a&gt; at the start of each hour, similar to a radio
station.&lt;/li&gt;
&lt;li&gt;Trigger time vibration with &lt;a
href="https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm"&gt;Tasker&lt;/a&gt;
(see below for details)&lt;/li&gt;
&lt;li&gt;Decide what to do if you double-click when the display is on. You
can choose between a warning vibration and having the time vibrated as
well.&lt;/li&gt;
&lt;li&gt;The app starts automatically after the system reboots.&lt;/li&gt;
&lt;li&gt;It runs on all devices with Android 6 or newer.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="double-click-conflict"&gt;Double-click conflict&lt;/h2&gt;
&lt;p&gt;TactileClock is triggered by clicking the power button twice in quick
succession. This may conflict with the ‘built-in’ double-click action of
your Android device. For example, a Google Pixel device will typically
launch the camera when the power button is double-clicked; a Samsung
device may take a screenshot when the power button is
double-clicked.&lt;/p&gt;
&lt;p&gt;If your device has a ‘built-in’ action when the power button is
double-clicked, there are three options to get TactileClock working.&lt;/p&gt;
&lt;h3
id="option-1-disable-the-built-in-double-click-action-of-your-device."&gt;Option
1: Disable the ‘built-in’ double-click action of your device.&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Google Pixel: (Settings &amp;gt; System &amp;gt; Gestures &amp;gt; Quickly open
camera)&lt;/li&gt;
&lt;li&gt;Samsung Galaxy: (Settings &amp;gt; Advanced Features &amp;gt; Side Button
&amp;gt; Double Press)&lt;/li&gt;
&lt;li&gt;Other devices may require different settings to disable the
double-click action.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once the ‘built-in’ double-click action has been disabled,
double-clicking the power button will trigger the TactileClock
action.&lt;/p&gt;
&lt;h3
id="option-2-configure-tactileclock-to-trigger-after-a-long-double-click."&gt;Option
2: Configure TactileClock to trigger after a long double-click.&lt;/h3&gt;
&lt;p&gt;This will allow the ‘built-in’ double-click action to co-exist with
the TactileClock double-click action.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. Open TactileClock
2. Swipe left to show the &amp;#39;Shortcut&amp;#39; screen.
3. Modify the &amp;#39;Click Interval:&amp;#39;.
     3a. Set &amp;#39;From:&amp;#39; to be 500ms.
     3b. Set &amp;#39;To:&amp;#39; to be 1500ms.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;From:&lt;/code&gt; and &lt;code&gt;To:&lt;/code&gt; values specified above
should work, but may need to be adjusted for optimal results on your
device. Once configured, a fast double-click will trigger your devices
‘built-in’ action, while a slow double-click will trigger the
TactileClock action.&lt;/p&gt;
&lt;table&gt;
&lt;colgroup&gt;
&lt;col style="width: 40%" /&gt;
&lt;col style="width: 60%" /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr class="header"&gt;
&lt;th&gt;Power button&lt;/th&gt;
&lt;th&gt;Double-click action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="odd"&gt;
&lt;td&gt;(click)(click)&lt;/td&gt;
&lt;td&gt;Trigger the ‘built-in’ action (e.g. open camera).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="even"&gt;
&lt;td&gt;(click)(pause-for-one-second)(click)&lt;/td&gt;
&lt;td&gt;Trigger the TactileClock action (i.e. vibrate the time).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3
id="option-3-use-tasker-to-trigger-time-vibration-with-custom-events"&gt;Option
3: Use Tasker to trigger time vibration with custom events&lt;/h3&gt;
&lt;p&gt;Use &lt;a
href="https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm"&gt;Tasker&lt;/a&gt;
to trigger time vibration from hardware buttons, device shake, NFC tags,
or any other event your phone supports.&lt;/p&gt;
&lt;p&gt;To set this up, create a new task in the Tasker-app, choose an event
and then add TactileClock’s vibrate-time action from the “Plugin”
category.&lt;/p&gt;
&lt;h2 id="download-and-source-code"&gt;Download and source code:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a
href="https://play.google.com/store/apps/details?id=de.eric_scheibler.tactileclock&amp;amp;hl=en"&gt;Google
Play Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
href="https://apt.izzysoft.de/packages/de.eric_scheibler.tactileclock"&gt;IzzySoft
Android Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;APK download: &lt;a
href="/downloads/TactileClock_v2.6.2.apk"&gt;TactileClock_v2.6.2.apk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/scheibler/TactileClock"&gt;Source code on
GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a
href="https://www.eric-scheibler.de/en/tactile_clock_privacy"&gt;Privacy
policy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;License: &lt;a href="https://www.gnu.org/licenses/gpl-3.0.en.html"&gt;GPL
V3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="change-log"&gt;Change log&lt;/h2&gt;
&lt;h3 id="version-2.6.2-from-june-23rd-2026"&gt;Version 2.6.2 from June 23rd,
2026&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Add support for Android 17&lt;/li&gt;
&lt;li&gt;Rebuild .apk because reproducible build failed for version 2.6.1 on
f-droid&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="version-2.6.1-from-june-21st-2026"&gt;Version 2.6.1 from June 21st,
2026&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Add support for Android 17&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="version-2.6.0-from-january-18th-2026"&gt;Version 2.6.0 from January
18th, 2026&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Add Tasker integration&lt;/li&gt;
&lt;li&gt;Add Ukrainian translation&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="version-2.5.1-from-december-29th-2025"&gt;Version 2.5.1 from
December 29th, 2025&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Add stop-clock action to the service notification&lt;/li&gt;
&lt;li&gt;Add audio ducking during Greenwich time signal playback&lt;/li&gt;
&lt;li&gt;Update tutorial text&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="version-2.5.0-from-december-16th-2025"&gt;Version 2.5.0 from
December 16th, 2025&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Duration of short and long vibrations configurable in settings&lt;/li&gt;
&lt;li&gt;Test of time vibration added to tutorial and settings&lt;/li&gt;
&lt;li&gt;When the screen is turned off and on, either a warning vibration or
the current time can be vibrated&lt;/li&gt;
&lt;li&gt;Greenwich Time Signal plays at the top of every hour&lt;/li&gt;
&lt;li&gt;Tutorial text revised&lt;/li&gt;
&lt;li&gt;Spanish translation added&lt;/li&gt;
&lt;li&gt;Leading zeros in hour and minute field omitted&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
</feed>
