https://www.eric-scheibler.de/Erics Blog2022-04-30T20:52:05ZEric Scheiblerhttps://www.eric-scheibler.detag:www.eric-scheibler.de,2022-04-30:/en/blog/2022/04/enable-the-android-screen-reader-talkback-from-the-linux-command-line/Enable the Android screen reader Talkback from the Linux command line2022-04-30T20:52:05Z2022-04-30T20:52:05Z<p>You can enable Androids screen reader from the Linux command line
with the following command:</p>
<pre><code>adb shell settings put secure enabled_accessibility_services com.google.android.marvin.talkback/com.google.android.marvin.talkback.TalkBackService</code></pre>
<p>Unfortunately you had to enable <a
href="https://developer.android.com/studio/debug/dev-options">USB
Debugging</a> on your Android device before. Furthermore you must
install the <a
href="https://developer.android.com/studio/command-line/adb">Android
Debug Bridge</a> on your PC. Otherwise the command above will not
work.</p>
tag:www.eric-scheibler.de,2021-07-03:/en/blog/2021/07/shell-script-for-adf-scanner-fujitsu-sp-1120/Shell script for ADF scanner Fujitsu SP-11202021-07-03T21:10:31Z2021-07-03T21:10:31Z<p>Recently I bought a <a
href="https://www.fujitsu.com/global/products/computing/peripheral/scanners/business/sp1120/">Fujitsu
SP-1120</a> 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.</p>
<p>This article describes the installation under Debian Linux and
provides a simple scan-to-pdf shell script.</p>
<!-- more -->
<h2 id="install-the-scanner">Install the scanner</h2>
<p>First download the <a
href="https://imagescanner.fujitsu.com/global/dl/index-sps.html">Linux
driver package</a> - at least it works under Ubuntu and Debian.</p>
<p>Then install:</p>
<pre><code># dpkg -i pfusp-ubuntu18.04_2.1.1_amd64.deb</code></pre>
<p>And create the udev rule <code>99-fujitsu-sp-1120.rules</code> under
<code>/etc/udev/rules.d</code> with the following content:</p>
<pre><code># Fujitsu ScanSnap SP-1120
ATTRS{idVendor}=="04c5", ATTRS{idProduct}=="1473", MODE="0664", GROUP="scanner", ENV{libsane_matched}="yes"</code></pre>
<p>Adapt the product id, if you bought a SP-1125 or SP-1130. Afterwards
reload the udev rules:</p>
<pre><code># udevadm control --reload
# udevadm trigger</code></pre>
<p>Lastly install the sane package (Scanner Access Now Easy) and turn on
the scanner and check, if the scanner was installed correctly:</p>
<pre><code># apt install sane
# scanadf --list-devices</code></pre>
<p>Expected output:</p>
<pre><code>device `pfusp:SP1120:001:093' is a FUJITSU SP1120 scanner</code></pre>
<h2 id="scan-script">Scan script</h2>
<p>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 <a
href="/en/blog/2015/04/script-to-extract-text-from-images-and-scanned-pdf-files/">here</a>.</p>
<p>To start scanning, put some document into your scanner and type:</p>
<pre><code>adf</code></pre>
<p>Use the <code>-p</code> parameter, to preview the ocr result of the
first page and <code>-h</code> for help.</p>
<p>Download: <a href="/downloads/adf">adf</a></p>
<pre><code>#!/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="scan.pdf"
TEXT_EDITOR="/usr/bin/vim"
PDF_VIEWER="$HOME/bin/ocr"
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 "" scan.XXXXXXXXXX)
cleanup() {
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
function yes_or_no {
while true; do
read -p "$* [y/n]: " yn
case $yn in
[Yy]*) return 0 ;;
[Nn]*) echo "Aborted" ; return 1 ;;
esac
done
}
# Parse command-line options
while [[ $# > 0 ]]; do
case "$1" in
-h|--help) HELP=1 ;;
-v|--verbose) VERBOSE=1 ;;
-o|--output) shift; OUTPUT="$1" ;;
-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 "$(basename $0) [OPTIONS]... [OUTPUT]"
echo ""
echo "OPTIONS"
echo " -x, --device"
echo " Override scanner device name, defaulting to \"pfusp\""
echo " -m, --mode"
echo " Mode e.g. Lineart (default), Halftone, Gray, Color, etc."
echo " -r, --resolution"
echo " Resolution e.g 300 (default)"
echo " -l, --language <lang>"
echo " which language to use for OCR"
echo " -p, --preview-first-page"
echo " OCR first page and preview in $TEXT_EDITOR"
echo ""
echo "OUTPUT"
echo " -o, --output <outputfile>"
echo " Output to named file default=scan.pdf"
echo " -w, --overwrite-output-file"
echo " Overwrite the output pdf file, if it already exists"
echo " -v, --verbose"
exit 0
fi
if [[ $VERBOSE == 0 ]]; then
quiet_paran="--quiet"
suppress_error_messages="2> /dev/null"
fi
if [[ "$OUTPUT" == "" ]]; then
echo >&2 "Output file must be specified. Aborting."
exit 1
fi
if [[ -f "$OUTPUT" ]]; then
if [[ $OVERWRITE_OUTPUT_FILE == 0 ]]; then
echo >&2 "Output file $OUTPUT already exists. Aborting."
exit 1
else
rm "$OUTPUT"
fi
fi
echo >&2 "Scanning..."
scanadf --device-name "$DEVICE" --source Adf-duplex --resolution $RESOLUTION --mode $MODE -o $TMP_DIR/scan-%04d
if [[ $? != 0 ]]; then
exit 1
fi
echo ""
shopt -s extglob nullglob
image_files=($TMP_DIR/scan-[0-9]*)
num_scans=${#image_files[@]}
if [[ $num_scans > 0 ]]; then
if [[ $OCR_PREVIEW_FIRST_PAGE == 1 ]]; then
echo "Creating preview..."
preview_image_file="${image_files[0]}"
preview_text_file="$TMP_DIR/preview_first_page.txt"
# 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 "Proceed?"; then
exit 0
fi
# remove preview text file
rm $preview_text_file
echo ""
fi
echo "Processing $num_scans pages"
for image_file in ${image_files[@]}; do
echo "Process $(basename $image_file)"
# 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 == *"Rotate: 180"* ]]; then
echo "Image orientation is upside down, rotate"
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 "%[fx:100*mean]" info:) || percentage_white=0
is_empty_page=$(echo "$percentage_white >= 99.8" | bc -l)
if [[ $is_empty_page == 1 && $orientation_result == "" ]]; then
echo "Empty page removed"
else
eval tesseract ${image_file}.tiff $image_file -l $OCR_LANGUAGE pdf $suppress_error_messages
rm ${image_file}.tiff
fi
echo ""
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 "Renaming..."
mv $TMP_DIR/scan-0*.pdf "$OUTPUT"
elif [[ $num_pdf_files > 1 ]]; then
echo "Concatenating pdfs..."
pdfunite "${pdf_files[@]}" "$OUTPUT"
fi
fi
if [[ -f "$OUTPUT" ]]; then
echo "Done."
if [[ $PDF_VIEWER != "" ]]; then
if yes_or_no "Open ${OUTPUT}?"; then
$PDF_VIEWER $OUTPUT
fi
fi
else
echo "No scans found."
fi
</code></pre>
tag:www.eric-scheibler.de,2021-06-12:/en/blog/2021/06/easy-and-accessible-network-request-analysis/Easy and accessible network request analysis2021-06-12T12:56:47Z2021-06-12T12:56:47Z<p>I had to analyse some network requests while I was working on an
Android app project. That turned out to be surprisingly easy, using <a
href="https://mitmproxy.org">mitmproxy</a>. This article covers the
proxy installation and usage by <code>wget</code> and within the source
code of an Android app.</p>
<!-- more -->
<p>Basically it works like a <a
href="https://en.wikipedia.org/wiki/Man-in-the-middle_attack">mitm
attack</a>. 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 <a
href="https://brltty.app">brltty</a>.</p>
<p>Install: <code>sudo apt install mitmproxy</code></p>
<p>Run:
<code>mitmproxy --listen-host 192.168.100.100 --listen-port 22222 --save-stream-file +requests.log</code></p>
<h2 id="wget">Wget</h2>
<p>Now try to send a http request through the proxy:</p>
<pre><code>wget -e https_proxy=192.168.100.100:22222 http://example.org</code></pre>
<p>It should appear in the main view of mitmproxy.</p>
<p>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
<code>~/.mitmproxy/mitmproxy-ca-cert.pem</code>. Copy the file onto your
client machine and send a second request:</p>
<pre><code>wget -e https_proxy=192.168.100.100:22222 --ca-certificate /path/to/mitmproxy-ca-cert.pem https://example.org</code></pre>
<p>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.</p>
<p>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 “>>”.</p>
<p>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.</p>
<h2 id="android-app-development">Android app development</h2>
<p>First add the same mitmproxy certificate to your apps “res”
folder:</p>
<pre><code>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</code></pre>
<p>Then include the following code into your app to:</p>
<ol type="1">
<li>set the proxy</li>
<li>use the SSL certificate from above for the request</li>
</ol>
<div class="sourceCode" id="cb4"><pre
class="sourceCode java"><code class="sourceCode java"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="im">org</span><span class="op">.</span><span class="im">example</span><span class="op">.</span><span class="im">project</span><span class="op">.</span><span class="im">BuildConfig</span><span class="op">;</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="im">java</span><span class="op">.</span><span class="im">net</span><span class="op">.</span><span class="im">InetSocketAddress</span><span class="op">;</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="im">java</span><span class="op">.</span><span class="im">net</span><span class="op">.</span><span class="im">Proxy</span><span class="op">;</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="im">java</span><span class="op">.</span><span class="im">net</span><span class="op">.</span><span class="im">URL</span><span class="op">;</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="im">java</span><span class="op">.</span><span class="im">security</span><span class="op">.</span><span class="im">KeyStore</span><span class="op">;</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="im">java</span><span class="op">.</span><span class="im">security</span><span class="op">.</span><span class="im">cert</span><span class="op">.</span><span class="im">CertificateFactory</span><span class="op">;</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="im">javax</span><span class="op">.</span><span class="im">net</span><span class="op">.</span><span class="im">ssl</span><span class="op">.</span><span class="im">HttpsURLConnection</span><span class="op">;</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="im">javax</span><span class="op">.</span><span class="im">net</span><span class="op">.</span><span class="im">ssl</span><span class="op">.</span><span class="im">SSLContext</span><span class="op">;</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a><span class="kw">import</span> <span class="im">javax</span><span class="op">.</span><span class="im">net</span><span class="op">.</span><span class="im">ssl</span><span class="op">.</span><span class="im">TrustManagerFactory</span><span class="op">;</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a><span class="bu">URL</span> url <span class="op">=</span> <span class="kw">new</span> <span class="bu">URL</span><span class="op">(</span><span class="st">"https://example.org"</span><span class="op">);</span></span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a><span class="bu">String</span> proxyHost <span class="op">=</span> <span class="st">"192.168.100.100"</span><span class="op">;</span></span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> proxyPort <span class="op">=</span> <span class="dv">22222</span><span class="op">;</span></span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true" tabindex="-1"></a><span class="bu">HttpsURLConnection</span> connection <span class="op">=</span> <span class="kw">null</span><span class="op">;</span></span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="op">(</span>BuildConfig<span class="op">.</span><span class="fu">DEBUG</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-18"><a href="#cb4-18" aria-hidden="true" tabindex="-1"></a> <span class="bu">Proxy</span> proxy <span class="op">=</span> <span class="kw">new</span> <span class="bu">Proxy</span><span class="op">(</span></span>
<span id="cb4-19"><a href="#cb4-19" aria-hidden="true" tabindex="-1"></a> <span class="bu">Proxy</span><span class="op">.</span><span class="fu">Type</span><span class="op">.</span><span class="fu">HTTP</span><span class="op">,</span> <span class="kw">new</span> <span class="bu">InetSocketAddress</span><span class="op">(</span>proxyHost<span class="op">,</span> proxyPort<span class="op">));</span></span>
<span id="cb4-20"><a href="#cb4-20" aria-hidden="true" tabindex="-1"></a> connection <span class="op">=</span> <span class="op">(</span><span class="bu">HttpsURLConnection</span><span class="op">)</span> url<span class="op">.</span><span class="fu">openConnection</span><span class="op">(</span>proxy<span class="op">);</span></span>
<span id="cb4-21"><a href="#cb4-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-22"><a href="#cb4-22" aria-hidden="true" tabindex="-1"></a> <span class="cf">try</span> <span class="op">{</span></span>
<span id="cb4-23"><a href="#cb4-23" aria-hidden="true" tabindex="-1"></a> <span class="bu">CertificateFactory</span> cf <span class="op">=</span> <span class="bu">CertificateFactory</span><span class="op">.</span><span class="fu">getInstance</span><span class="op">(</span><span class="st">"X.509"</span><span class="op">);</span></span>
<span id="cb4-24"><a href="#cb4-24" aria-hidden="true" tabindex="-1"></a> <span class="bu">InputStream</span> caInput<span class="op">;</span></span>
<span id="cb4-25"><a href="#cb4-25" aria-hidden="true" tabindex="-1"></a> <span class="co">// Create a KeyStore containing our trusted CAs</span></span>
<span id="cb4-26"><a href="#cb4-26" aria-hidden="true" tabindex="-1"></a> <span class="bu">KeyStore</span> keyStore <span class="op">=</span> <span class="bu">KeyStore</span><span class="op">.</span><span class="fu">getInstance</span><span class="op">(</span><span class="bu">KeyStore</span><span class="op">.</span><span class="fu">getDefaultType</span><span class="op">());</span></span>
<span id="cb4-27"><a href="#cb4-27" aria-hidden="true" tabindex="-1"></a> keyStore<span class="op">.</span><span class="fu">load</span><span class="op">(</span><span class="kw">null</span><span class="op">,</span> <span class="kw">null</span><span class="op">);</span></span>
<span id="cb4-28"><a href="#cb4-28" aria-hidden="true" tabindex="-1"></a> <span class="co">// load mitm_proxy certificate</span></span>
<span id="cb4-29"><a href="#cb4-29" aria-hidden="true" tabindex="-1"></a> caInput <span class="op">=</span> ApplicationInstance<span class="op">.</span><span class="fu">getContext</span><span class="op">().</span><span class="fu">getResources</span><span class="op">().</span><span class="fu">openRawResource</span><span class="op">(</span>R<span class="op">.</span><span class="fu">raw</span><span class="op">.</span><span class="fu">mitm_proxy</span><span class="op">);</span></span>
<span id="cb4-30"><a href="#cb4-30" aria-hidden="true" tabindex="-1"></a> <span class="cf">try</span> <span class="op">{</span></span>
<span id="cb4-31"><a href="#cb4-31" aria-hidden="true" tabindex="-1"></a> keyStore<span class="op">.</span><span class="fu">setCertificateEntry</span><span class="op">(</span><span class="st">"ca1"</span><span class="op">,</span> cf<span class="op">.</span><span class="fu">generateCertificate</span><span class="op">(</span>caInput<span class="op">));</span></span>
<span id="cb4-32"><a href="#cb4-32" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span> <span class="cf">finally</span> <span class="op">{</span></span>
<span id="cb4-33"><a href="#cb4-33" aria-hidden="true" tabindex="-1"></a> caInput<span class="op">.</span><span class="fu">close</span><span class="op">();</span></span>
<span id="cb4-34"><a href="#cb4-34" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb4-35"><a href="#cb4-35" aria-hidden="true" tabindex="-1"></a> <span class="co">// Create a TrustManager that trusts the CAs in our KeyStore</span></span>
<span id="cb4-36"><a href="#cb4-36" aria-hidden="true" tabindex="-1"></a> <span class="bu">String</span> tmfAlgorithm <span class="op">=</span> <span class="bu">TrustManagerFactory</span><span class="op">.</span><span class="fu">getDefaultAlgorithm</span><span class="op">();</span></span>
<span id="cb4-37"><a href="#cb4-37" aria-hidden="true" tabindex="-1"></a> <span class="bu">TrustManagerFactory</span> tmf <span class="op">=</span> <span class="bu">TrustManagerFactory</span><span class="op">.</span><span class="fu">getInstance</span><span class="op">(</span>tmfAlgorithm<span class="op">);</span></span>
<span id="cb4-38"><a href="#cb4-38" aria-hidden="true" tabindex="-1"></a> tmf<span class="op">.</span><span class="fu">init</span><span class="op">(</span>keyStore<span class="op">);</span></span>
<span id="cb4-39"><a href="#cb4-39" aria-hidden="true" tabindex="-1"></a> <span class="co">// Create an SSLContext that uses our TrustManager</span></span>
<span id="cb4-40"><a href="#cb4-40" aria-hidden="true" tabindex="-1"></a> <span class="bu">SSLContext</span> sslContext <span class="op">=</span> <span class="bu">SSLContext</span><span class="op">.</span><span class="fu">getInstance</span><span class="op">(</span><span class="st">"TLS"</span><span class="op">);</span></span>
<span id="cb4-41"><a href="#cb4-41" aria-hidden="true" tabindex="-1"></a> sslContext<span class="op">.</span><span class="fu">init</span><span class="op">(</span><span class="kw">null</span><span class="op">,</span> tmf<span class="op">.</span><span class="fu">getTrustManagers</span><span class="op">(),</span> <span class="kw">null</span><span class="op">);</span></span>
<span id="cb4-42"><a href="#cb4-42" aria-hidden="true" tabindex="-1"></a> connection<span class="op">.</span><span class="fu">setSSLSocketFactory</span><span class="op">(</span>sslContext<span class="op">.</span><span class="fu">getSocketFactory</span><span class="op">());</span></span>
<span id="cb4-43"><a href="#cb4-43" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span> <span class="cf">catch</span> <span class="op">(</span><span class="bu">Exception</span> e<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-44"><a href="#cb4-44" aria-hidden="true" tabindex="-1"></a> <span class="bu">System</span><span class="op">.</span><span class="fu">out</span><span class="op">.</span><span class="fu">println</span><span class="op">(</span></span>
<span id="cb4-45"><a href="#cb4-45" aria-hidden="true" tabindex="-1"></a> <span class="bu">String</span><span class="op">.</span><span class="fu">format</span><span class="op">(</span><span class="st">"Cert exception: </span><span class="sc">%1$s</span><span class="st">"</span><span class="op">,</span> e<span class="op">.</span><span class="fu">getMessage</span><span class="op">()));</span></span>
<span id="cb4-46"><a href="#cb4-46" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb4-47"><a href="#cb4-47" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-48"><a href="#cb4-48" aria-hidden="true" tabindex="-1"></a><span class="op">}</span> <span class="cf">else</span> <span class="op">{</span></span>
<span id="cb4-49"><a href="#cb4-49" aria-hidden="true" tabindex="-1"></a> connection <span class="op">=</span> <span class="op">(</span><span class="bu">HttpsURLConnection</span><span class="op">)</span> url<span class="op">.</span><span class="fu">openConnection</span><span class="op">();</span></span>
<span id="cb4-50"><a href="#cb4-50" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>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 -> security) and set a
system-wide proxy. Unfortunately not every application respects this -
for example browsers may define their own proxy settings.</p>
<p>Therefore if you want to debug your own app, go for the direct
integration - much easier to implement.</p>
tag:www.eric-scheibler.de,2018-12-29:/en/blog/2018/12/mpd-announce-current-song/MPD - Announce current song2018-12-29T02:14:13Z2018-12-29T02:14:13Z<p>My server runs an <a href="https://www.musicpd.org">MPD</a> 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.</p>
<!-- more -->
<p>Download: <a
href="/downloads/mpd_speak_current_song.py">mpd_speak_current_song.py</a></p>
<pre><code>#!/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's written for Mac OS but could be easily adapted to Linux as well (change tts_command variable)
#
# author: Eric Scheibler <email@eric-scheibler.de>
# 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 = "192.168.2.11"
mpd_port = "6600"
# audio output device
# Mac: get all devices with: say -a '?'
audio_output_device = ""
# voice table
# Mac: get all installed voices with: say -v '?'
voices = {
"de" : {"name":"Yannick", "rate":280},
"en" : {"name":"Samantha", "rate":200},
"fr" : {"name":"Thomas", "rate":200},
# default: different German voice
"default" : {"name":"Anna", "rate":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 = ["mpc", "--quiet"]
if mpd_host:
mpc_command += ["--host", mpd_host]
if mpd_port:
mpc_command += ["--port", mpd_port]
# speak with: say ...
tts_command = ["say"]
if audio_output_device:
tts_command += ["-a", 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 + ["pause"])
# announce new song
if not self.killed:
self.execute(tts_command + ["-v", self.voice.get("name"), "-r", str(self.voice.get("rate")), self.song])
# resume playback
if not self.killed:
self.execute(mpc_command + ["play"])
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 + ["current", "--wait"], encoding='UTF-8').strip()
except subprocess.CalledProcessError as e:
# could not get current song
error_message = "Error: Could not get the current song"
subprocess.Popen(tts_command + ["-v", "Alex", error_message])
print('{}: {}'.format(error_message, e))
sys.exit(1)
else:
# detect language and get tts voice
voice = voices.get("default")
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('{}.{}: {}'.format(language, voice.get("name"), current_song))
if __name__ == "__main__":
main()
</code></pre>
tag:www.eric-scheibler.de,2018-02-10:/en/blog/2018/02/clipboardtospeech-android-app-speaks-the-clipboard-content/ClipboardToSpeech - Android app speaks the clipboard content2018-02-10T09:43:24Z2018-02-10T09:43:24Z<p>This Android application Monitors the clipboard and speaks newly
added text immediately.</p>
<!-- more -->
<p>Additional features are:</p>
<ul>
<li>Uses the default tts engine for speech output</li>
<li>Contains a clipboard history with the last entries</li>
<li>Autostarts at boot time</li>
<li>Creates a low-priority notification when the background service is
running</li>
<li>Requires at least Android version 4.1 and is compatible to 8.0 and
8.1</li>
</ul>
<p>Clone the git repository:</p>
<pre><code>git clone https://github.com/scheibler/ClipboardToSpeech/</code></pre>
<p>Then install the pre-compiled version with:</p>
<pre><code>adb install app/src/main/misc/releases/ClipboardToSpeech_vx.x.x.apk</code></pre>
<p>Or build manually:</p>
<pre><code>./gradlew build</code></pre>
tag:www.eric-scheibler.de,2017-08-13:/en/blog/2017/08/game-of-thrones-tactile-maps-for-the-blind-and-visually-impaired/Game of thrones - Tactile maps for the blind and visually impaired2017-08-13T11:01:16Z2017-08-13T11:01:16Z<p>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.</p>
<!-- more -->
<p>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.</p>
<p>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
<a
href="https://tu-dresden.de/ing/informatik/ai/mci/forschung/forschungsgebiete/tangram">TANGRAM</a>
at TU Dresden, focused on interactively creating tactile maps and images
for the blind.</p>
<p>We took the map template from <a
href="http://gameofthrones.net">gameofthrones.net</a> 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 <a
href="http://www.hyperbraille.de/?lang=en">2-dimensional braille
display</a>.</p>
<p>Together we created these two maps:</p>
<ol type="1">
<li>A landscape format world map containing Westeros and Essos.
(unfortunately there was no space left to include Sothoryos)</li>
<li>A portrait format map of Westeros, including the most important
POI.</li>
</ol>
<p>The maps are available in SVG format, each containing a second page
detailing the abbreviations used.</p>
<p>We offer two sets of maps:</p>
<p>If you have access to a <a href="https://viewplus.com">ViewPlus
braille printer</a> please download <a
href="/downloads/got_maps_viewPlus-tiger.zip">got_maps_viewPlus-tiger.zip</a>.
It produces the best results and should be easy to use.</p>
<p>Alternatively you can try <a
href="/downloads/got_maps_print.zip">got_maps_print.zip</a>. 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.</p>
tag:www.eric-scheibler.de,2016-07-03:/en/blog/2016/07/disable-laptop-screen-on-linux/Disable laptop screen on Linux2016-07-03T20:29:42Z2016-07-03T20:29:42Z<p>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.</p>
<!-- more -->
<p>Save the following script under /usr/local/sbin/monitor:</p>
<pre><code>#!/bin/bash
if [ -f /tmp/screen_stays_active ]; then
echo 0 > /sys/class/backlight/intel_backlight/brightness
rm /tmp/screen_stays_active
echo "Disable screen"
else
echo 850 > /sys/class/backlight/intel_backlight/brightness
touch /tmp/screen_stays_active
echo "Restore screen brightness"
fi</code></pre>
<p>and make it executable:</p>
<pre><code>chmod +x /usr/local/sbin/monitor</code></pre>
<p>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.</p>
<pre><code># crontab -e
* * * * * if [ ! -f /tmp/screen_stays_active ]; then echo 0 > /sys/class/backlight/intel_backlight/brightness; fi
* * * * * sleep 20 && if [ ! -f /tmp/screen_stays_active ]; then echo 0 > /sys/class/backlight/intel_backlight/brightness; fi
* * * * * sleep 40 && if [ ! -f /tmp/screen_stays_active ]; then echo 0 > /sys/class/backlight/intel_backlight/brightness; fi</code></pre>
<p>Please note, that the screen is disabled directly after you’ve
created the cronjobs above and it will stay disabled even after
reboot.</p>
<p>You may toggle the brightness state with the command
<code>sudo monitor</code>. 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 <code>visudo</code> command as root and append the following
line:</p>
<pre><code>MY_USER_NAME ALL = NOPASSWD: /usr/local/sbin/monitor</code></pre>
<p>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:</p>
<pre><code># crontab -e
* * * * * echo 0 > /sys/class/leds/tpacpi::kbd_backlight/brightness</code></pre>
<p>Find additional information at <a
href="http://www.thinkwiki.org/wiki/ThinkLight"
class="uri">http://www.thinkwiki.org/wiki/ThinkLight</a></p>
tag:www.eric-scheibler.de,2016-05-06:/en/blog/2016/05/android-app-tactileclock-vibrates-the-current-time/Android app Tactile Clock vibrates the current time2016-05-06T21:45:20Z2016-05-06T21:45:20Z<p>I’ve created a small Android app, which vibrates the current time. To
start the vibration the display must be locked. Then press the power
button twice in a row with a delay between 500 and 1500
milliseconds.</p>
<p>Update 2024: Version 2.2.0 supports Android 14</p>
<!-- more -->
<p>The program is started automatically after booting is finished. It
supports all devices with Android version >= 4.0 and is licensed
under the GPL V3.</p>
<p>Basically there exist two different vibration pattern: A short
vibration stands for the digit 1 and a long one for the digit 5. So 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.</p>
<p>Examples:</p>
<pre><code>01:16 = .. s ... s .. l . s
02:51 = .. s . s ... l .. s
10:11 = s .. l . l ... s .. s</code></pre>
<p>Explanation:</p>
<p>The time is processed digit by digit. s = short, l = long. A leading
zero at the hour field is omitted. To simplify the recognition of the
vibration pattern, there exist three kind of gabs with different
durations, marked by the number of dots in the examples above. A single
dot stands for the pause between two vibrations, two dots symbolize the
separation of two digits within the hour and minute field and three dots
split hours and minutes.</p>
<p>Download and source code:</p>
<ul>
<li><a
href="https://play.google.com/store/apps/details?id=de.eric_scheibler.tactileclock&hl=en">Google
Play Store</a></li>
<li><a
href="https://apt.izzysoft.de/packages/de.eric_scheibler.tactileclock">IzzySoft
Android Repository</a></li>
<li>APK download: <a
href="/downloads/TactileClock_v2.2.0.apk">TactileClock_v2.2.0.apk</a>
(updated on 2024-03-25)</li>
<li><a href="https://github.com/scheibler/TactileClock">Source code on
GitHub</a></li>
<li><a
href="https://www.eric-scheibler.de/en/tactile_clock_privacy">Privacy
policy</a></li>
</ul>