diff --git a/notizen.md b/notizen.md index 601cd0c..22f74d9 100644 --- a/notizen.md +++ b/notizen.md @@ -2,9 +2,7 @@ ## Zu erledigen -- asynchrones Erstellen der Audio-Dateien (solange nicht fertig, eine Markierung in der Liste anzeigen) +- sanitizing überprüfen - Persistenz mit SQLite - Datum für Dokumente ergänzen -- eingebetter Media-Spieler? - für jedes bestehende Dokument soll es Funktionen zum Aufbereiten und Herunderladen geben in den Formaten pdf/odt (pandoc nutzen) und ogg -- bei Löschung eines Dokuments auch die zugehörigen Dateien löschen diff --git a/readme.md b/readme.md index e94faf7..cba5e94 100644 --- a/readme.md +++ b/readme.md @@ -9,7 +9,7 @@ proof-of-concept für Partei-Arbeits-Dokumenten-Verwaltung, welche hörbare Vers ### Voraussetzungen -- curl (Debian-Paket-Name: `curl`) +- [curl](https://curl.se/docs/manpage.html) (Debian-Paket-Name: `curl`) ### Anweisungen @@ -33,9 +33,9 @@ proof-of-concept für Partei-Arbeits-Dokumenten-Verwaltung, welche hörbare Vers ### Voraussetzungen -- PHP auf Kommandozeile (Debian-Paket-Name: `php-cli`) -- ffmpeg (Debian-Paket-Name: `ffmpeg`) -- beliebigen Browser +- [PHP](https://www.php.net/)-Interpreter für Kommandozeile (Debian-Paket-Name: `php-cli`) +- [FFmpeg](https://ffmpeg.org/) (Debian-Paket-Name: `ffmpeg`) +- Browser ### Anweisungen diff --git a/source/helpers/misc.php b/source/helpers/misc.php index 0ced4e5..4f96140 100644 --- a/source/helpers/misc.php +++ b/source/helpers/misc.php @@ -6,6 +6,14 @@ require_once('string.php'); require_once('cache.php'); +/** + */ +function path_wrapped(string $path) : ?string +{ + return (\file_exists($path) ? $path : null); +} + + /** */ function render(string $template_name, array $arguments) : string @@ -43,24 +51,36 @@ function navigate(string $target) : void /** */ -function generate_audio(string $input, string $output_path) : void +function generate_audio( + string $input_value, + string $output_path, + ?array $options = null +) : void { - $key = \hash('sha256', $input); - $path_wav = \rosavox\helpers\string_\coin( - '/tmp/{{key}}.wav', + $options = \array_merge( + [ + 'blocking' => false, + ], + ($options ?? []) + ); + $key = \hash('sha256', $input_value); + $input_path = \rosavox\helpers\string_\coin( + '/tmp/{{key}}.txt', [ 'key' => $key, ] ); - $path_ogg = $output_path; + \file_put_contents($input_path, $input_value); $command = \rosavox\helpers\string_\coin( - 'echo "{{input}}" | piper/piper --model piper/voice.onnx --output_file {{path_wav}} && ffmpeg -y -i {{path_wav}} {{path_ogg}} ; rm -f {{path_wav}}', + 'scripts/generate-audio {{input_path}} {{output_path}}{{suffix}}', [ - 'input' => $input, - 'path_wav' => $path_wav, - 'path_ogg' => $path_ogg, + 'input_path' => $input_path, + 'output_path' => $output_path, + 'suffix' => ($options['blocking'] ? '' : ' > /dev/null 2>&1 &'), ] ); + // \unlink($input_path); + // \error_log(\sprintf("-- %s\n", $command)); \exec($command); } diff --git a/source/helpers/string.php b/source/helpers/string.php index 80daa97..5406d5e 100644 --- a/source/helpers/string.php +++ b/source/helpers/string.php @@ -15,4 +15,19 @@ function coin(string $template, array $arguments) : string return $result; } + +/** + */ +function replace_all( + string $string, + array $replacements +) : string +{ + $result = $string; + foreach ($replacement as $replacement_key => $replacement_value) { + $result = \str_replace($replacement_key, $replacement_value, $result); + } + return $result; +} + ?> diff --git a/source/index.html.php b/source/index.html.php index 18c14f1..30b4fbb 100644 --- a/source/index.html.php +++ b/source/index.html.php @@ -61,17 +61,105 @@ function render_list() : string 'docs-list-entry', [ 'label_read' => \rosavox\helpers\misc\translate('action.read'), - 'label_hear' => \rosavox\helpers\misc\translate('action.hear'), + 'value_text' => $entry['value']['title'], 'value_link_open' => make_link('edit', ['id' => \sprintf('%u', $entry['id'])]), 'value_link_read' => \rosavox\services\doc\readable_path($entry['id']), - 'value_link_hear' => \rosavox\services\doc\audio_path($entry['id']), - 'value_name' => \rosavox\helpers\string_\coin( - '{{name}}.oga', - [ - 'name' => \rosavox\services\doc\name($entry['id']), - ] + 'area_download_readable' => ( + (fn ($name, $path) => ( + (! \file_exists($path)) + ? + \rosavox\helpers\misc\render( + 'state-waiting', + [ + 'info' => \rosavox\helpers\misc\translate('state.generating'), + ] + ) + : + \rosavox\helpers\misc\render( + 'download', + [ + 'text' => \rosavox\helpers\misc\translate('item.readable.short'), + 'tooltip' => \rosavox\helpers\string_\coin( + '{{item}} {{action}}', + [ + 'action' => \rosavox\helpers\misc\translate('action.download'), + 'item' => \rosavox\helpers\misc\translate('item.readable.long'), + ] + ), + 'link' => $path, + 'name' => \rosavox\helpers\string_\coin( + '{{name}}.md', + [ + 'name' => $name, + ] + ), + 'type' => 'text/markdown', + ] + ) + )) ( + \rosavox\services\doc\name($entry['id']), + \rosavox\services\doc\readable_path($entry['id']) + ) + ), + 'area_download_audible' => ( + (fn ($name, $path) => ( + (! \file_exists($path)) + ? + \rosavox\helpers\misc\render( + 'state-waiting', + [ + 'info' => \rosavox\helpers\misc\translate('state.generating'), + ] + ) + : + \rosavox\helpers\misc\render( + 'download', + [ + 'text' => \rosavox\helpers\misc\translate('item.audible.short'), + 'tooltip' => \rosavox\helpers\string_\coin( + '{{item}} {{action}}', + [ + 'action' => \rosavox\helpers\misc\translate('action.download'), + 'item' => \rosavox\helpers\misc\translate('item.audible.long'), + ] + ), + 'link' => $path, + 'name' => \rosavox\helpers\string_\coin( + '{{name}}.oga', + [ + 'name' => $name, + ] + ), + 'type' => 'audio/ogg', + ] + ) + )) ( + \rosavox\services\doc\name($entry['id']), + \rosavox\services\doc\audible_path($entry['id']) + ) + ), + 'area_hear' => ( + (fn ($name, $path) => ( + (! \file_exists($path)) + ? + \rosavox\helpers\misc\render( + 'state-waiting', + [ + 'info' => \rosavox\helpers\misc\translate('state.generating'), + ] + ) + : + \rosavox\helpers\misc\render( + 'player', + [ + 'source_path' => $path, + ] + ) + )) ( + \rosavox\services\doc\name($entry['id']), + \rosavox\services\doc\audible_path($entry['id']) + ) ), - 'value_text' => $entry['value']['title'], ] ), \rosavox\services\doc\list_() diff --git a/source/scripts/generate-audio b/source/scripts/generate-audio new file mode 100755 index 0000000..06ea964 --- /dev/null +++ b/source/scripts/generate-audio @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +## functions + +function syntaxerror +{ + echo "SYNTAX: generate-audio " +} + + +## args + +if [ $# -ge 1 ] ; then input_path=$1 && shift ; else syntaxerror ; fi +if [ $# -ge 1 ] ; then output_path=$1 && shift ; else syntaxerror ; fi + + +## vars + +key=$(cat "${input_path}" | sha256sum | cut -d ' ' -f 1) +wav_path=/tmp/${key}.wav + + +## exec + +cat ${input_path} | piper/piper --model piper/voice.onnx --output_file ${wav_path} > /dev/null +ffmpeg -loglevel error -y -i ${wav_path} -vn ${output_path} +rm -f ${wav_path} +echo ${output_path} diff --git a/source/services/doc.php b/source/services/doc.php index 3c8116d..04a4a06 100644 --- a/source/services/doc.php +++ b/source/services/doc.php @@ -25,17 +25,17 @@ function name(int $id) : string /** */ -function audio_path(int $id) : string +function readable_path(int $id) : string { - return \sprintf('docs/%s.oga', name($id)); + return \sprintf('docs/%s.md', name($id)); } /** */ -function readable_path(int $id) : string +function audible_path(int $id) : string { - return \sprintf('docs/%s.md', name($id)); + return \sprintf('docs/%s.oga', name($id)); } @@ -101,7 +101,28 @@ function generate_readable(int $id, $doc) : void /** */ -function generate_audio(int $id, $doc) : void +function remove_readable( + int $id +) : void +{ + $path = readable_path($id); + if (! \file_exists($path)) + { + // do nothing + } + else + { + \unlink($path); + } +} + + +/** + */ +function generate_audible( + int $id, + $doc +) : void { $pause = " .\n"; $text = \rosavox\helpers\string_\coin( @@ -132,11 +153,32 @@ function generate_audio(int $id, $doc) : void ); \rosavox\helpers\misc\generate_audio( $text, - audio_path($id) + audible_path($id), + [ + 'blocking' => false, + ] ); } +/** + */ +function remove_audible( + int $id +) : void +{ + $path = audible_path($id); + if (! \file_exists($path)) + { + // do nothing + } + else + { + \unlink($path); + } +} + + /** */ function empty_() : array @@ -173,7 +215,7 @@ function create(array $doc) : int { $id = state::$storage->create($doc); generate_readable($id, $doc); - generate_audio($id, $doc); + generate_audible($id, $doc); return $id; } @@ -183,8 +225,10 @@ function create(array $doc) : int function update(int $id, array $doc) : void { state::$storage->update($id, $doc); + remove_readable($id); + remove_audible($id); generate_readable($id, $doc); - generate_audio($id, $doc); + generate_audible($id, $doc); } @@ -193,6 +237,8 @@ function update(int $id, array $doc) : void function delete(int $id) : void { state::$storage->delete($id); + remove_readable($id); + remove_audible($id); } @@ -217,7 +263,7 @@ function add_examples() : void 'authors' => [ 'Fenriswolf', ], - 'content' => 'Der Allvater hat mich betrogen. Ich werde ihn und seine elende Asenbrut verschlingen.', + 'content' => 'Der Allvater hat mich betrogen. Ich werde ihn und seine elende Asenbrut verschlingen. Diese Welt wird enden.', 'reasoning' => null, ] ); diff --git a/source/strings.json b/source/strings.json index 31fb4fe..ad7f5a1 100644 --- a/source/strings.json +++ b/source/strings.json @@ -6,6 +6,12 @@ "action.delete": "löschen", "action.hear": "hören", "action.read": "lesen", + "action.download": "herunterladen", + "item.audible.short": "A", + "item.audible.long": "Audio", + "item.readable.short": "T", + "item.readable.long": "Text", + "state.generating": "wird erstellt", "domain.doc.title": "Titel", "domain.doc.authors": "Autoren", "domain.doc.content": "Formulierung", diff --git a/source/style.css b/source/style.css index 8799cb5..b6f95a0 100644 --- a/source/style.css +++ b/source/style.css @@ -82,7 +82,22 @@ button font-size: 1.0em; } +.docs-list-entry +{ + margin-bottom: 16px; +} + .docs-list-entry > * { vertical-align: middle; } + +.state-waiting +{ + cursor: progress; +} + +.download::before +{ + content: "\21A7 "; +} diff --git a/source/templates/docs-list-entry.html.tpl b/source/templates/docs-list-entry.html.tpl index af17860..3c8db3a 100644 --- a/source/templates/docs-list-entry.html.tpl +++ b/source/templates/docs-list-entry.html.tpl @@ -1,9 +1,9 @@
  • {{value_text}} | - [{{label_read}}] + {{area_download_readable}} | - [{{label_hear}}] + {{area_download_audible}} | - + {{area_hear}}
  • diff --git a/source/templates/download.html.tpl b/source/templates/download.html.tpl new file mode 100644 index 0000000..6fddde2 --- /dev/null +++ b/source/templates/download.html.tpl @@ -0,0 +1 @@ +{{text}} diff --git a/source/templates/player.html.tpl b/source/templates/player.html.tpl new file mode 100644 index 0000000..70bd195 --- /dev/null +++ b/source/templates/player.html.tpl @@ -0,0 +1 @@ + diff --git a/source/templates/state-waiting.html.tpl b/source/templates/state-waiting.html.tpl new file mode 100644 index 0000000..3721a2d --- /dev/null +++ b/source/templates/state-waiting.html.tpl @@ -0,0 +1 @@ +[~]