diff --git a/notizen.md b/notizen.md index fe52be4..601cd0c 100644 --- a/notizen.md +++ b/notizen.md @@ -1,19 +1,10 @@ # Notizen -## Ziele - -- soll eine kleine Web-Anwendung werden -- einzige Domäne im Modell ist die der Dokumente -- Eigenschaften von Dokumenten: - - Autor : Zeichenkette - - Titel : Zeichenkette - - Formulierung : Zeichenkette - - Begründung (optional) : Zeichenkette -- dafür soll es ein schlichtes [CRUD](https://de.wikipedia.org/wiki/CRUD) geben -- für jedes bestehende Dokument soll es Funktionen zum Aufbereiten und Herunderladen geben in den Formaten pdf und ogg -- für die Erstellung der Audio-Variante soll nach Möglichkeit [Piper](https://github.com/rhasspy/piper) verwendet werden - - ## Zu erledigen - asynchrones Erstellen der Audio-Dateien (solange nicht fertig, eine Markierung in der Liste anzeigen) +- 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 a40a398..e94faf7 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 +- curl (Debian-Paket-Name: `curl`) ### Anweisungen diff --git a/source/helpers.php b/source/helpers.php deleted file mode 100644 index 87ab5c2..0000000 --- a/source/helpers.php +++ /dev/null @@ -1,268 +0,0 @@ -query($query_template); - /* -$query = "SELECT * FROM books"; -$result = $db->query($query); - -while ($row = $result->fetchArray(SQLITE3_ASSOC)) { - */ -} - - -/** - */ -function database_put(string $query_template, array $arguments) : int -{ - $connection = new \SQLite3('data.sqlite'); - $statement = $connection->prepare($query); - foreach ($arguments as $key => $value) - { - $statement->bindValue( - \sprintf(':%s', $key), - $value['value'], - database_map_type($value['type']) - ); - } - if ($statement->execute()) { - // SELECT last_insert_rowid() - echo "Buch erfolgreich hinzugefügt!"; - } - else - { - echo "Fehler beim Hinzufügen des Buches: " . $db->lastErrorMsg(); - } -} - - -/** - */ -function string_coin(string $template, array $arguments) : string -{ - $result = $template; - foreach ($arguments as $key => $value) - { - $result = \str_replace(\sprintf('{{%s}}', $key), $value, $result); - } - return $result; -} - - -/** - */ -class class_crud_jsonfile -{ - private string $path; - - private \Closure $id_encode; - - private \Closure $id_decode; - - public function __construct( - string $path, - \Closure $id_encode, - \Closure $id_decode - ) - { - $this->path = $path; - $this->id_encode = $id_encode; - $this->id_decode = $id_decode; - } - - private function get() : array - { - $content = (\file_exists($this->path) ? \file_get_contents($this->path) : null); - return ( - ($content === null) - ? - ['last_id' => 0, 'entries' => []] - : - \json_decode($content, true) - ); - } - - private function put(array $data) : void - { - $content = \json_encode($data, \JSON_PRETTY_PRINT); - \file_put_contents($this->path, $content); - } - - public function list_() : array - { - $data = $this->get(); - return \array_map( - fn ($id_encoded) => [ - 'id' => ($this->id_decode)($id_encoded), - 'value' => $data['entries'][$id_encoded], - ], - \array_keys($data['entries']) - ); - } - - public function read(int $id) - { - $data = $this->get(); - $id_encoded = ($this->id_encode)($id); - if (! \array_key_exists($id_encoded, $data['entries'])) - { - throw (new \Exception('not found')); - } - else - { - return $data['entries'][$id_encoded]; - } - } - - public function create($value) : int - { - $data = $this->get(); - $id = ($data['last_id'] + 1); - $id_encoded = ($this->id_encode)($id); - $data['last_id'] = $id; - $data['entries'][$id_encoded] = $value; - $this->put($data); - return $id; - } - - public function update(int $id, $value) : void - { - $data = $this->get(); - $id_encoded = ($this->id_encode)($id); - if (! \array_key_exists($id_encoded, $data['entries'])) - { - throw (new \Exception('not found')); - } - else - { - $data['entries'][$id_encoded] = $value; - $this->put($data); - } - } - - public function delete(int $id) : void - { - $data = $this->get(); - $id_encoded = ($this->id_encode)($id); - if (! \array_key_exists($id_encoded, $data['entries'])) - { - throw (new \Exception('not found')); - } - else - { - unset($data['entries'][$id_encoded]); - $this->put($data); - } - } -} - - -/** - */ -function render(string $template_name, array $arguments) : string -{ - return string_coin( - cache_get( - string_coin( - 'template.{{name}}', - [ - 'name' => $template_name, - ] - ), - fn() => \file_get_contents( - string_coin( - '{{directory}}/templates/{{name}}.html.tpl', - [ - 'directory' => __DIR__, - 'name' => $template_name, - ] - ) - ) - ), - $arguments - ); -} - - -/** - */ -function navigate(string $target) : void -{ - \header('Location: ' . $target); -} - - -/** - */ -function generate_audio(string $name, string $input) : string -{ - $path_wav = string_coin( - '/tmp/{{name}}.wav', - [ - 'name' => $name, - ] - ); - $path_ogg = string_coin( - '{{name}}.ogg', - [ - 'name' => $name, - ] - ); - $command = string_coin( - 'echo "{{input}}" | piper/piper --model piper/voice.onnx --output_file {{path_wav}} ; ffmpeg -y -i {{path_wav}} {{path_ogg}}', - [ - 'input' => $input, - 'path_wav' => $path_wav, - 'path_ogg' => $path_ogg, - ] - ); - exec($command); - return $path_ogg; -} - - ?> diff --git a/source/helpers/cache.php b/source/helpers/cache.php new file mode 100644 index 0000000..a501f52 --- /dev/null +++ b/source/helpers/cache.php @@ -0,0 +1,30 @@ + diff --git a/source/helpers/misc.php b/source/helpers/misc.php new file mode 100644 index 0000000..0ced4e5 --- /dev/null +++ b/source/helpers/misc.php @@ -0,0 +1,85 @@ + $template_name, + ] + ), + fn() => \file_get_contents( + \rosavox\helpers\string_\coin( + '{{directory}}/templates/{{name}}.html.tpl', + [ + 'directory' => /*__DIR__*/'.', + 'name' => $template_name, + ] + ) + ) + ), + $arguments + ); +} + + +/** + */ +function navigate(string $target) : void +{ + \header('Location: ' . $target); +} + + +/** + */ +function generate_audio(string $input, string $output_path) : void +{ + $key = \hash('sha256', $input); + $path_wav = \rosavox\helpers\string_\coin( + '/tmp/{{key}}.wav', + [ + 'key' => $key, + ] + ); + $path_ogg = $output_path; + $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}}', + [ + 'input' => $input, + 'path_wav' => $path_wav, + 'path_ogg' => $path_ogg, + ] + ); + \exec($command); +} + + +/** + */ +function translate(string $key, ?array $options = null) : string +{ + $strings = \rosavox\helpers\cache\get( + 'strings', + fn () => \json_decode(\file_get_contents('strings.json'), true) + ); + $options = \array_merge( + [ + 'language' => 'de', + ], + ($options ?? []) + ); + return ($strings[$options['language']][$key] ?? \sprintf('{%s}', $key)); +} + + ?> diff --git a/source/helpers/storage.php b/source/helpers/storage.php new file mode 100644 index 0000000..94149c9 --- /dev/null +++ b/source/helpers/storage.php @@ -0,0 +1,128 @@ +path = $path; + $this->id_encode = $id_encode; + $this->id_decode = $id_decode; + } + + private function get() : array + { + $content = (\file_exists($this->path) ? \file_get_contents($this->path) : null); + return ( + ($content === null) + ? + ['last_id' => 0, 'entries' => []] + : + \json_decode($content, true) + ); + } + + private function put(array $data) : void + { + $content = \json_encode($data, \JSON_PRETTY_PRINT); + \file_put_contents($this->path, $content); + } + + public function list_() : array + { + $data = $this->get(); + return \array_map( + fn ($id_encoded) => [ + 'id' => ($this->id_decode)($id_encoded), + 'value' => $data['entries'][$id_encoded], + ], + \array_keys($data['entries']) + ); + } + + public function read(int $id) + { + $data = $this->get(); + $id_encoded = ($this->id_encode)($id); + if (! \array_key_exists($id_encoded, $data['entries'])) + { + throw (new \Exception('not found')); + } + else + { + return $data['entries'][$id_encoded]; + } + } + + public function create($value) : int + { + $data = $this->get(); + $id = ($data['last_id'] + 1); + $id_encoded = ($this->id_encode)($id); + $data['last_id'] = $id; + $data['entries'][$id_encoded] = $value; + $this->put($data); + return $id; + } + + public function update(int $id, $value) : void + { + $data = $this->get(); + $id_encoded = ($this->id_encode)($id); + if (! \array_key_exists($id_encoded, $data['entries'])) + { + throw (new \Exception('not found')); + } + else + { + $data['entries'][$id_encoded] = $value; + $this->put($data); + } + } + + public function delete(int $id) : void + { + $data = $this->get(); + $id_encoded = ($this->id_encode)($id); + if (! \array_key_exists($id_encoded, $data['entries'])) + { + throw (new \Exception('not found')); + } + else + { + unset($data['entries'][$id_encoded]); + $this->put($data); + } + } +} + + ?> diff --git a/source/helpers/string.php b/source/helpers/string.php new file mode 100644 index 0000000..80daa97 --- /dev/null +++ b/source/helpers/string.php @@ -0,0 +1,18 @@ + $value) + { + $result = \str_replace(\sprintf('{{%s}}', $key), $value, $result); + } + return $result; +} + + ?> diff --git a/source/index.html.php b/source/index.html.php index 751d3f0..ac739e6 100644 --- a/source/index.html.php +++ b/source/index.html.php @@ -1,133 +1,181 @@ $mode]); - $target = \implode( - '&', - \array_map( - fn ($key) => \rosavox\helpers\string_coin( - '{{key}}={{value}}', - [ - 'key' => $key, - 'value' => $a[$key], - ] - ), - \array_keys($a) + return ( + '?' + . + \implode( + '&', + \array_map( + fn ($key) => \rosavox\helpers\string_\coin( + '{{key}}={{value}}', + [ + 'key' => $key, + 'value' => $a[$key], + ] + ), + \array_keys($a) + ) ) ); - \rosavox\helpers\navigate('?' . $target); } + +/** + */ +function nav(string $mode, array $args) : void +{ + \rosavox\helpers\misc\navigate(make_link($mode, $args)); +} + + +/** + */ +function render_list() : string +{ + return \rosavox\helpers\misc\render( + 'docs-list', + [ + 'label_make' => \rosavox\helpers\misc\translate('action.make'), + 'link_make' => make_link('make', []), + 'entries' => \implode( + "\n", + \array_map( + fn ($entry) => \rosavox\helpers\misc\render( + 'docs-list-entry', + [ + 'label_read' => \rosavox\helpers\misc\translate('action.read'), + 'label_hear' => \rosavox\helpers\misc\translate('action.hear'), + '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']), + ] + ), + 'value_text' => $entry['value']['title'], + ] + ), + \rosavox\services\doc\list_() + ) + ), + ] + ); +} + + +/** + */ +function render_edit(?int $id) : string +{ + $doc = ( + ($id === null) + ? + \rosavox\services\doc\empty_() + : + \rosavox\services\doc\read($id) + ); + return \rosavox\helpers\misc\render( + 'docs-edit', + [ + 'label_action_back' => \rosavox\helpers\misc\translate('action.back'), + 'label_action_save' => \rosavox\helpers\misc\translate('action.save'), + 'label_action_delete' => \rosavox\helpers\misc\translate('action.delete'), + 'label_doc_title' => \rosavox\helpers\misc\translate('domain.doc.title'), + 'label_doc_authors' => \rosavox\helpers\misc\translate('domain.doc.authors'), + 'label_doc_content' => \rosavox\helpers\misc\translate('domain.doc.content'), + 'label_doc_reasoning' => \rosavox\helpers\misc\translate('domain.doc.reasoning'), + 'value_action_back' => make_link('list', []), + 'value_action_save' => make_link('save', (($id === null) ? [] : ['id' => \strval($id)])), + 'value_action_delete' => make_link('delete', (($id === null) ? [] : ['id' => \strval($id)])), + 'value_doc_title' => $doc['title'], + 'value_doc_authors' => \implode(', ', $doc['authors']), + 'value_doc_content' => $doc['content'], + 'value_doc_reasoning' => ($doc['reasoning'] ?? ''), + ] + ); +} + + ?>
+