Compare commits

...

30 Commits

Author SHA1 Message Date
nunks 87920bb2c0 dockerfile buscando da branch modificada 2026-05-31 18:23:43 -03:00
nunks bf73f99907 Tentando conciliar as novidades com as minhas alteracoes 2026-05-31 17:46:32 -03:00
Ross Stewart e0b6be24c1 Bump version number
Documentation Generator / Build Sphinx documentation (push) Has been cancelled
2026-03-06 15:49:51 +00:00
Ross Stewart cd3575d514 Documentation update 2026-03-06 15:15:09 +00:00
rosskouk 77e8546baa Merge pull request #73 from rosskouk/feature-enhanced-debugging
Added enhanced debugging to ping() method
2026-03-06 15:14:33 +00:00
rosskouk 122d328de4 Merge pull request #66 from f-longobardi/main
Enable display of song metadata
Thanks to @f-longobardi
2026-03-06 15:13:01 +00:00
Ross Stewart f3846a2311 Added enhanced debugging to ping() method 2026-03-06 14:27:29 +00:00
nunks 496ff9bb52 Adicionando skills para tocar mais musicas do artista/album que esta sendo tocado no momento
Documentation Generator / Build Sphinx documentation (push) Has been cancelled
2025-11-15 18:24:35 -03:00
nunks b27b77c572 Adicionando skills para tocar mais musicas do artista/album que esta sendo tocado no momento
Documentation Generator / Build Sphinx documentation (push) Has been cancelled
2025-11-15 18:08:49 -03:00
nunks e0b8cb71e4 Adicionando skills para tocar mais musicas do artista/album que esta sendo tocado no momento
Documentation Generator / Build Sphinx documentation (push) Has been cancelled
2025-11-15 17:56:11 -03:00
nunks a702b1befd Adicionando skills para tocar mais musicas do artista/album que esta sendo tocado no momento
Documentation Generator / Build Sphinx documentation (push) Has been cancelled
2025-11-15 17:50:11 -03:00
nunks d748ee2602 Adicionando skills para tocar mais musicas do artista/album que esta sendo tocado no momento
Documentation Generator / Build Sphinx documentation (push) Has been cancelled
2025-11-15 17:38:56 -03:00
nunks 609109dd17 Adicionando skills para tocar mais musicas do artista/album que esta sendo tocado no momento
Documentation Generator / Build Sphinx documentation (push) Has been cancelled
2025-11-15 17:15:49 -03:00
nunks c44b22d486 melhorando as falas da alexa
Documentation Generator / Build Sphinx documentation (push) Has been cancelled
2025-11-08 21:39:53 -03:00
nunks 4a36f0561b dockerfile mais enxuto
Documentation Generator / Build Sphinx documentation (push) Has been cancelled
2025-11-08 20:57:13 -03:00
nunks 4e9d03e774 testando alterar dockerfile
Documentation Generator / Build Sphinx documentation (push) Has been cancelled
2025-11-08 20:44:27 -03:00
nunks 46e9203677 testando alterar dockerfile
Documentation Generator / Build Sphinx documentation (push) Has been cancelled
2025-11-08 20:39:17 -03:00
nunks aa4d19aafd testando alterar dockerfile
Documentation Generator / Build Sphinx documentation (push) Has been cancelled
2025-11-08 13:03:04 -03:00
nunks 2ee1abd813 customizadas em portugues: interaction model e respostas da skill
Documentation Generator / Build Sphinx documentation (push) Has been cancelled
2025-11-08 12:50:08 -03:00
Francesco 0cf1c52743 also for continued play 2025-10-25 16:31:15 +02:00
Francesco fcce6a8d11 add song metadata, respond to button requests in media player 2025-10-25 14:56:38 +02:00
Ross Stewart 0461264f16 Merge remote-tracking branch 'refs/remotes/origin/main' 2025-09-08 17:14:04 +01:00
Ross Stewart 4cf9480f41 Update VSCode debug config 2025-09-08 17:13:09 +01:00
Ross Stewart 8edd3296c2 Documentation update 2025-09-08 15:44:38 +00:00
Ross Stewart da6e0ff317 Bump docs version number 2025-09-08 16:43:52 +01:00
Ross Stewart a8b1087c80 Update Docker container build actions script 2025-09-08 16:33:49 +01:00
Ross Stewart 67e5c7e20e - Update Alpine version for Docker container to v3.22.1
- Updated GH Actions script to cross-compile amd64 and arm64 images
- Updated GH Actions modules to latest versions
- Bumped version number in app.py
2025-09-08 16:27:31 +01:00
Ross Stewart b33209f7df Documentation update 2025-09-08 14:52:50 +00:00
rosskouk da418c55f8 Merge pull request #64 from rosskouk/linting-update
Linting updates
2025-09-08 15:52:09 +01:00
Ross Stewart 1db8ff9b16 Linting updates 2025-09-08 15:47:49 +01:00
16 changed files with 341 additions and 166 deletions
+11 -4
View File
@@ -18,10 +18,16 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v5.0.0
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.6.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.11.1
- name: Log in to the Container registry - name: Log in to the Container registry
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 uses: docker/login-action@v3.5.0
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -29,14 +35,15 @@ jobs:
- name: Extract metadata (tags, labels) for Docker - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 uses: docker/metadata-action@v5.8.0
with: with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc uses: docker/build-push-action@v6.18.0
with: with:
context: . context: .
platforms: linux/amd64, linux/arm64
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
+12 -3
View File
@@ -6,21 +6,30 @@
"configurations": [ "configurations": [
{ {
"name": "Python: Current File", "name": "Python: Current File",
"type": "python", "type": "debugpy",
"request": "launch", "request": "launch",
"program": "${file}", "program": "${file}",
"console": "integratedTerminal", "console": "integratedTerminal",
"justMyCode": true "justMyCode": true
}, },
{ {
"name": "Python: Flask", "name": "Python: Flask - AskNavidrome",
"type": "python", "type": "debugpy",
"request": "launch", "request": "launch",
"module": "flask", "module": "flask",
"env": { "env": {
"FLASK_APP": "skill/app.py", "FLASK_APP": "skill/app.py",
"FLASK_ENV": "development", "FLASK_ENV": "development",
"FLASK_DEBUG": "0", "FLASK_DEBUG": "0",
"NAVI_SKILL_ID": "<skill_id>",
"NAVI_SONG_COUNT": "50",
"NAVI_URL": "https://<url>",
"NAVI_USER": "<username>",
"NAVI_PASS": "<password>",
"NAVI_PORT": "443",
"NAVI_API_PATH": "/rest",
"NAVI_API_VER": "1.16.1",
"NAVI_DEBUG": "3"
}, },
"args": [ "args": [
"run", "run",
+10 -20
View File
@@ -1,33 +1,23 @@
FROM alpine:3.15.0 as build FROM alpine:3.22.1 AS build
LABEL maintainer="Ross Stewart <rosskouk@gmail.com>"
LABEL org.opencontainers.image.source https://github.com/rosskouk/asknavidrome
RUN apk add python3 py3-pip git build-base python3-dev libffi-dev openssl-dev RUN apk add python3 py3-pip git build-base python3-dev libffi-dev openssl-dev
WORKDIR /opt WORKDIR /opt
RUN pwd
RUN python3 -m venv env RUN python3 -m venv env
RUN ls -la
RUN git clone https://github.com/rosskouk/asknavidrome.git RUN git clone -b nunks https://g.nunks.org/nunks/asknavidrome-cafofo.git
RUN ls -la asknavidrome-cafofo
WORKDIR /opt/asknavidrome WORKDIR /opt/asknavidrome-cafofo
RUN pwd
RUN ls -la
RUN source ../env/bin/activate && pip --no-cache-dir install wheel && pip --no-cache-dir install -r skill/requirements-docker.txt RUN source ../env/bin/activate && pip --no-cache-dir install wheel && pip --no-cache-dir install -r skill/requirements-docker.txt
FROM alpine:3.22.1
FROM alpine:3.15.0
LABEL maintainer="Ross Stewart <rosskouk@gmail.com>"
RUN apk add python3 RUN apk add python3
COPY --from=build /opt/env /opt/env COPY --from=build /opt/env /opt/env
COPY --from=build /opt/asknavidrome/skill /opt/asknavidrome/ COPY --from=build /opt/asknavidrome-cafofo/skill /opt/asknavidrome/
WORKDIR /opt/asknavidrome WORKDIR /opt/asknavidrome
# Activate Python Virtual Environment # Activate Python Virtual Environment
ENV PATH="/opt/env/bin:$PATH" ENV PATH="/opt/env/bin:$PATH"
EXPOSE 5000 EXPOSE 5000
ENTRYPOINT ["python3", "app.py"] ENTRYPOINT ["python3", "app.py"]
+57 -49
View File
@@ -1,7 +1,7 @@
{ {
"interactionModel": { "interactionModel": {
"languageModel": { "languageModel": {
"invocationName": "navisonic", "invocationName": "cafofo music",
"intents": [ "intents": [
{ {
"name": "AMAZON.CancelIntent", "name": "AMAZON.CancelIntent",
@@ -40,8 +40,8 @@
} }
], ],
"samples": [ "samples": [
"play songs by {artist}", "tocar músicas da {artist}",
"play music by {artist}" "tocar músicas do {artist}"
] ]
}, },
{ {
@@ -65,8 +65,20 @@
} }
], ],
"samples": [ "samples": [
"Play the album {album}", "Tocar o album {album}",
"Play the album {album} by {artist}" "Tocar o album {album} do {artist}",
"Tocar o album {album} da {artist}",
"Tocar o disco {album}",
"Tocar o disco {album} do {artist}",
"Tocar o disco {album} da {artist}"
]
},
{
"name": "NaviSonicPlayMusicByCurrentAlbum",
"slots": [],
"samples": [
"Tocar mais músicas desse album",
"Tocar mais músicas desse disco"
] ]
}, },
{ {
@@ -78,49 +90,42 @@
} }
], ],
"samples": [ "samples": [
"Play the {playlist} playlist", "Tocar a playlist {playlist}"
"Start the {playlist} playlist"
] ]
}, },
{ {
"name": "NaviSonicSongDetails", "name": "NaviSonicSongDetails",
"slots": [], "slots": [],
"samples": [ "samples": [
"What is playing", "O que está tocando",
"Who is singing", "Quem está tocando",
"Who's singing", "Quem está cantando",
"What album is this song on", "Qual o album dessa música",
"What album is this on", "Que banda é essa",
"Which album", "Que musica é essa",
"Which album is this song from", "Que banda é essa"
"Which artist is this",
"What band is this",
"Who sings this song",
"Who sings this",
"What song is this",
"What's playing"
] ]
}, },
{ {
"name": "NaviSonicStarSong", "name": "NaviSonicStarSong",
"slots": [], "slots": [],
"samples": [ "samples": [
"Add this song to my favourites", "Adicionar música aos favoritos",
"Favourite this song", "Adicionar aos favoritos",
"Like this song", "Favoritar essa musica",
"Add this song to my liked songs", "Lembrar dessa música",
"Star this song" "Eu gosto dessa música"
] ]
}, },
{ {
"name": "NaviSonicUnstarSong", "name": "NaviSonicUnstarSong",
"slots": [], "slots": [],
"samples": [ "samples": [
"Remove this song from favourites", "Tirar a música dos favoritos",
"I don't like this song", "Tirar dos favoritos",
"Remove the star from this song", "Desfavoritar a música",
"Delete this song from my favourites", "Esquecer essa música",
"Unstar this song" "Eu não gosto dessa música"
] ]
}, },
{ {
@@ -136,21 +141,27 @@
} }
], ],
"samples": [ "samples": [
"Play the song {song} by the band {artist}", "Tocar a música {song} da banda {artist}",
"Play {song} by the band {artist}", "Tocar {song} da banda {artist}",
"Play {song} by {artist}", "Tocar {song} da {artist}"
"Play the song {song} by the artist {artist}" ]
},
{
"name": "NaviSonicPlayMusicByCurrentArtist",
"slots": [],
"samples": [
"Tocar mais músicas desse artista",
"Tocar mais músicas desse grupo",
"Tocar mais músicas dessa banda"
] ]
}, },
{ {
"name": "NaviSonicPlayFavouriteSongs", "name": "NaviSonicPlayFavouriteSongs",
"slots": [], "slots": [],
"samples": [ "samples": [
"Play my starred tracks", "Tocar favoritos",
"Play starred tracks", "Tocar meus favoritos",
"Play starred songs", "Tocar minhas músicas favoritas"
"Play my starred songs",
"Play my favourite songs"
] ]
}, },
{ {
@@ -162,29 +173,26 @@
} }
], ],
"samples": [ "samples": [
"Play {genre} songs", "Tocar {genre}"
"Play {genre} music"
] ]
}, },
{ {
"name": "NaviSonicPlayMusicRandom", "name": "NaviSonicPlayMusicRandom",
"slots": [], "slots": [],
"samples": [ "samples": [
"Play a selection of music", "Tocar seleção aleatória",
"Play a mix of tracks", "Tocar qualquer coisa",
"Play a mix of songs", "Tocar mix",
"Play random music", "Tocar músicas aleatórias"
"Play random songs"
] ]
}, },
{ {
"name": "NaviSonicRandomiseQueue", "name": "NaviSonicRandomiseQueue",
"slots": [], "slots": [],
"samples": [ "samples": [
"randomise the queue",
"randomise",
"shuffle", "shuffle",
"shuffle the queue" "shuffle na lista",
"mistura tudo"
] ]
} }
], ],
+1 -1
View File
@@ -1,5 +1,5 @@
const DOCUMENTATION_OPTIONS = { const DOCUMENTATION_OPTIONS = {
VERSION: '0.8', VERSION: '0.9',
LANGUAGE: 'en', LANGUAGE: 'en',
COLLAPSE_INDEX: false, COLLAPSE_INDEX: false,
BUILDER: 'html', BUILDER: 'html',
+4 -4
View File
@@ -7,7 +7,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Index &#8212; AskNavidrome 0.8 documentation</title> <title>Index &#8212; AskNavidrome 0.9 documentation</title>
@@ -37,7 +37,7 @@
<link rel="preload" as="script" href="_static/scripts/pydata-sphinx-theme.js?digest=dfe6caa3a7d634c4db9b" /> <link rel="preload" as="script" href="_static/scripts/pydata-sphinx-theme.js?digest=dfe6caa3a7d634c4db9b" />
<script src="_static/vendor/fontawesome/6.5.2/js/all.min.js?digest=dfe6caa3a7d634c4db9b"></script> <script src="_static/vendor/fontawesome/6.5.2/js/all.min.js?digest=dfe6caa3a7d634c4db9b"></script>
<script src="_static/documentation_options.js?v=85e8db4b"></script> <script src="_static/documentation_options.js?v=3e145956"></script>
<script src="_static/doctools.js?v=9bcbadda"></script> <script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script> <script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/sphinx-book-theme.js?v=887ef09a"></script> <script src="_static/scripts/sphinx-book-theme.js?v=887ef09a"></script>
@@ -133,7 +133,7 @@
<p class="title logo__title">AskNavidrome 0.8 documentation</p> <p class="title logo__title">AskNavidrome 0.9 documentation</p>
</a></div> </a></div>
<div class="sidebar-primary-item"> <div class="sidebar-primary-item">
@@ -612,7 +612,7 @@ document.write(`
</li> </li>
</ul></td> </ul></td>
<td style="width: 33%; vertical-align: top;"><ul> <td style="width: 33%; vertical-align: top;"><ul>
<li><a href="index.html#app.queueWorkerThread">queueWorkerThread() (in module app)</a> <li><a href="index.html#app.queue_worker_thread">queue_worker_thread() (in module app)</a>
</li> </li>
</ul></td> </ul></td>
</tr></table> </tr></table>
+25 -11
View File
@@ -8,7 +8,7 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<title>AskNavidrome Alexa Skill Documentation &#8212; AskNavidrome 0.8 documentation</title> <title>AskNavidrome Alexa Skill Documentation &#8212; AskNavidrome 0.9 documentation</title>
@@ -38,7 +38,7 @@
<link rel="preload" as="script" href="_static/scripts/pydata-sphinx-theme.js?digest=dfe6caa3a7d634c4db9b" /> <link rel="preload" as="script" href="_static/scripts/pydata-sphinx-theme.js?digest=dfe6caa3a7d634c4db9b" />
<script src="_static/vendor/fontawesome/6.5.2/js/all.min.js?digest=dfe6caa3a7d634c4db9b"></script> <script src="_static/vendor/fontawesome/6.5.2/js/all.min.js?digest=dfe6caa3a7d634c4db9b"></script>
<script src="_static/documentation_options.js?v=85e8db4b"></script> <script src="_static/documentation_options.js?v=3e145956"></script>
<script src="_static/doctools.js?v=9bcbadda"></script> <script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script> <script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/sphinx-book-theme.js?v=887ef09a"></script> <script src="_static/scripts/sphinx-book-theme.js?v=887ef09a"></script>
@@ -134,7 +134,7 @@
<p class="title logo__title">AskNavidrome 0.8 documentation</p> <p class="title logo__title">AskNavidrome 0.9 documentation</p>
</a></div> </a></div>
<div class="sidebar-primary-item"> <div class="sidebar-primary-item">
@@ -480,7 +480,7 @@ document.write(`
<li class="toc-h5 nav-item toc-entry"><a class="reference internal nav-link" href="#app.SystemExceptionHandler.handle"><code class="docutils literal notranslate"><span class="pre">SystemExceptionHandler.handle()</span></code></a></li> <li class="toc-h5 nav-item toc-entry"><a class="reference internal nav-link" href="#app.SystemExceptionHandler.handle"><code class="docutils literal notranslate"><span class="pre">SystemExceptionHandler.handle()</span></code></a></li>
</ul> </ul>
</li> </li>
<li class="toc-h4 nav-item toc-entry"><a class="reference internal nav-link" href="#app.queueWorkerThread"><code class="docutils literal notranslate"><span class="pre">queueWorkerThread()</span></code></a></li> <li class="toc-h4 nav-item toc-entry"><a class="reference internal nav-link" href="#app.queue_worker_thread"><code class="docutils literal notranslate"><span class="pre">queue_worker_thread()</span></code></a></li>
<li class="toc-h4 nav-item toc-entry"><a class="reference internal nav-link" href="#app.sanitise_speech_output"><code class="docutils literal notranslate"><span class="pre">sanitise_speech_output()</span></code></a></li> <li class="toc-h4 nav-item toc-entry"><a class="reference internal nav-link" href="#app.sanitise_speech_output"><code class="docutils literal notranslate"><span class="pre">sanitise_speech_output()</span></code></a></li>
</ul> </ul>
</li> </li>
@@ -1196,8 +1196,8 @@ and setting it to <em>DNS Only</em>.</p>
<p><strong>Functions:</strong></p> <p><strong>Functions:</strong></p>
<div class="pst-scrollable-table-container"><table class="autosummary longtable table"> <div class="pst-scrollable-table-container"><table class="autosummary longtable table">
<tbody> <tbody>
<tr class="row-odd"><td><p><a class="reference internal" href="#app.queueWorkerThread" title="app.queueWorkerThread"><code class="xref py py-obj docutils literal notranslate"><span class="pre">queueWorkerThread</span></code></a>(connection, play_queue, ...)</p></td> <tr class="row-odd"><td><p><a class="reference internal" href="#app.queue_worker_thread" title="app.queue_worker_thread"><code class="xref py py-obj docutils literal notranslate"><span class="pre">queue_worker_thread</span></code></a>(connection, play_queue, ...)</p></td>
<td><p></p></td> <td><p>Media queue worker</p></td>
</tr> </tr>
<tr class="row-even"><td><p><a class="reference internal" href="#app.sanitise_speech_output" title="app.sanitise_speech_output"><code class="xref py py-obj docutils literal notranslate"><span class="pre">sanitise_speech_output</span></code></a>(speech_string)</p></td> <tr class="row-even"><td><p><a class="reference internal" href="#app.sanitise_speech_output" title="app.sanitise_speech_output"><code class="xref py py-obj docutils literal notranslate"><span class="pre">sanitise_speech_output</span></code></a>(speech_string)</p></td>
<td><p>Sanitise speech output inline with the SSML standard</p></td> <td><p>Sanitise speech output inline with the SSML standard</p></td>
@@ -2787,9 +2787,21 @@ during dispatch.</p>
</dd></dl> </dd></dl>
<dl class="py function"> <dl class="py function">
<dt class="sig sig-object py" id="app.queueWorkerThread"> <dt class="sig sig-object py" id="app.queue_worker_thread">
<span class="sig-prename descclassname"><span class="pre">app.</span></span><span class="sig-name descname"><span class="pre">queueWorkerThread</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">connection</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">play_queue</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">song_id_list</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#app.queueWorkerThread" title="Link to this definition">#</a></dt> <span class="sig-prename descclassname"><span class="pre">app.</span></span><span class="sig-name descname"><span class="pre">queue_worker_thread</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">connection</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">object</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">play_queue</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">object</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">song_id_list</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">list</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">None</span></span></span><a class="headerlink" href="#app.queue_worker_thread" title="Link to this definition">#</a></dt>
<dd></dd></dl> <dd><p>Media queue worker</p>
<p>This function allows media queues to be populated in the background enabling multithreading
and increasing skill response times.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>connection</strong> (<em>object</em>) A SubSonic API connection object</p></li>
<li><p><strong>play_queue</strong> (<em>object</em>) A MediaQueue object</p></li>
<li><p><strong>song_id_list</strong> (<em>list</em>) A list containing Navidrome song IDs</p></li>
</ul>
</dd>
</dl>
</dd></dl>
<dl class="py function"> <dl class="py function">
<dt class="sig sig-object py" id="app.sanitise_speech_output"> <dt class="sig sig-object py" id="app.sanitise_speech_output">
@@ -3479,7 +3491,9 @@ embedded in the URI</p>
<span class="sig-name descname"><span class="pre">ping</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">bool</span></span></span><a class="headerlink" href="#asknavidrome.subsonic_api.SubsonicConnection.ping" title="Link to this definition">#</a></dt> <span class="sig-name descname"><span class="pre">ping</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">bool</span></span></span><a class="headerlink" href="#asknavidrome.subsonic_api.SubsonicConnection.ping" title="Link to this definition">#</a></dt>
<dd><p>Ping a Subsonic API server</p> <dd><p>Ping a Subsonic API server</p>
<p>Verify the connection to a Subsonic compatible API server <p>Verify the connection to a Subsonic compatible API server
is working</p> is working. Enhanced logging has been added to examine the
HTTP response returned from the server to assist with troubleshooting
connection issues.</p>
<dl class="field-list simple"> <dl class="field-list simple">
<dt class="field-odd">Returns<span class="colon">:</span></dt> <dt class="field-odd">Returns<span class="colon">:</span></dt>
<dd class="field-odd"><p>True if the connection works, False if it does not</p> <dd class="field-odd"><p>True if the connection works, False if it does not</p>
@@ -3842,7 +3856,7 @@ is working</p>
<li class="toc-h5 nav-item toc-entry"><a class="reference internal nav-link" href="#app.SystemExceptionHandler.handle"><code class="docutils literal notranslate"><span class="pre">SystemExceptionHandler.handle()</span></code></a></li> <li class="toc-h5 nav-item toc-entry"><a class="reference internal nav-link" href="#app.SystemExceptionHandler.handle"><code class="docutils literal notranslate"><span class="pre">SystemExceptionHandler.handle()</span></code></a></li>
</ul> </ul>
</li> </li>
<li class="toc-h4 nav-item toc-entry"><a class="reference internal nav-link" href="#app.queueWorkerThread"><code class="docutils literal notranslate"><span class="pre">queueWorkerThread()</span></code></a></li> <li class="toc-h4 nav-item toc-entry"><a class="reference internal nav-link" href="#app.queue_worker_thread"><code class="docutils literal notranslate"><span class="pre">queue_worker_thread()</span></code></a></li>
<li class="toc-h4 nav-item toc-entry"><a class="reference internal nav-link" href="#app.sanitise_speech_output"><code class="docutils literal notranslate"><span class="pre">sanitise_speech_output()</span></code></a></li> <li class="toc-h4 nav-item toc-entry"><a class="reference internal nav-link" href="#app.sanitise_speech_output"><code class="docutils literal notranslate"><span class="pre">sanitise_speech_output()</span></code></a></li>
</ul> </ul>
</li> </li>
BIN
View File
Binary file not shown.
+3 -3
View File
@@ -7,7 +7,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Python Module Index &#8212; AskNavidrome 0.8 documentation</title> <title>Python Module Index &#8212; AskNavidrome 0.9 documentation</title>
@@ -37,7 +37,7 @@
<link rel="preload" as="script" href="_static/scripts/pydata-sphinx-theme.js?digest=dfe6caa3a7d634c4db9b" /> <link rel="preload" as="script" href="_static/scripts/pydata-sphinx-theme.js?digest=dfe6caa3a7d634c4db9b" />
<script src="_static/vendor/fontawesome/6.5.2/js/all.min.js?digest=dfe6caa3a7d634c4db9b"></script> <script src="_static/vendor/fontawesome/6.5.2/js/all.min.js?digest=dfe6caa3a7d634c4db9b"></script>
<script src="_static/documentation_options.js?v=85e8db4b"></script> <script src="_static/documentation_options.js?v=3e145956"></script>
<script src="_static/doctools.js?v=9bcbadda"></script> <script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script> <script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/sphinx-book-theme.js?v=887ef09a"></script> <script src="_static/scripts/sphinx-book-theme.js?v=887ef09a"></script>
@@ -136,7 +136,7 @@
<p class="title logo__title">AskNavidrome 0.8 documentation</p> <p class="title logo__title">AskNavidrome 0.9 documentation</p>
</a></div> </a></div>
<div class="sidebar-primary-item"> <div class="sidebar-primary-item">
+3 -3
View File
@@ -6,7 +6,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Search - AskNavidrome 0.8 documentation</title> <meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Search - AskNavidrome 0.9 documentation</title>
@@ -36,7 +36,7 @@
<link rel="preload" as="script" href="_static/scripts/pydata-sphinx-theme.js?digest=dfe6caa3a7d634c4db9b" /> <link rel="preload" as="script" href="_static/scripts/pydata-sphinx-theme.js?digest=dfe6caa3a7d634c4db9b" />
<script src="_static/vendor/fontawesome/6.5.2/js/all.min.js?digest=dfe6caa3a7d634c4db9b"></script> <script src="_static/vendor/fontawesome/6.5.2/js/all.min.js?digest=dfe6caa3a7d634c4db9b"></script>
<script src="_static/documentation_options.js?v=85e8db4b"></script> <script src="_static/documentation_options.js?v=3e145956"></script>
<script src="_static/doctools.js?v=9bcbadda"></script> <script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script> <script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/sphinx-book-theme.js?v=887ef09a"></script> <script src="_static/scripts/sphinx-book-theme.js?v=887ef09a"></script>
@@ -135,7 +135,7 @@
<p class="title logo__title">AskNavidrome 0.8 documentation</p> <p class="title logo__title">AskNavidrome 0.9 documentation</p>
</a></div> </a></div>
<div class="sidebar-primary-item"> <div class="sidebar-primary-item">
+1 -1
View File
File diff suppressed because one or more lines are too long
+167 -44
View File
@@ -42,7 +42,7 @@ logger.addHandler(handler)
# Get service configuration # Get service configuration
# #
logger.info('AskNavidrome 0.6!') logger.info('AskNavidrome 0.10!')
logger.debug('Getting configuration from the environment...') logger.debug('Getting configuration from the environment...')
try: try:
@@ -222,7 +222,7 @@ class LaunchRequestHandler(AbstractRequestHandler):
logger.debug('In LaunchRequestHandler') logger.debug('In LaunchRequestHandler')
connection.ping() connection.ping()
speech = sanitise_speech_output('Ready!') speech = sanitise_speech_output('Simbora!')
handler_input.response_builder.speak(speech).ask(speech) handler_input.response_builder.speak(speech).ask(speech)
return handler_input.response_builder.response return handler_input.response_builder.response
@@ -300,7 +300,7 @@ class NaviSonicPlayMusicByArtist(AbstractRequestHandler):
# Check if a background process is already running, if it is then terminate the process # Check if a background process is already running, if it is then terminate the process
# in favour of the new process. # in favour of the new process.
if backgroundProcess != None: if backgroundProcess is not None:
backgroundProcess.terminate() backgroundProcess.terminate()
backgroundProcess.join() backgroundProcess.join()
@@ -311,7 +311,8 @@ class NaviSonicPlayMusicByArtist(AbstractRequestHandler):
artist_lookup = connection.search_artist(artist.value) artist_lookup = connection.search_artist(artist.value)
if artist_lookup is None: if artist_lookup is None:
text = sanitise_speech_output(f"I couldn't find the artist {artist.value} in the collection.") #text = sanitise_speech_output(f"I couldn't find the artist {artist.value} in the collection.")
text = sanitise_speech_output(f"Não achei o artista {artist.value} na nossa coleção.")
handler_input.response_builder.speak(text).ask(text) handler_input.response_builder.speak(text).ask(text)
return handler_input.response_builder.response return handler_input.response_builder.response
@@ -325,10 +326,11 @@ class NaviSonicPlayMusicByArtist(AbstractRequestHandler):
play_queue.clear() play_queue.clear()
controller.enqueue_songs(connection, play_queue, [song_id_list[0], song_id_list[1]]) # When generating the playlist return the first two tracks. controller.enqueue_songs(connection, play_queue, [song_id_list[0], song_id_list[1]]) # When generating the playlist return the first two tracks.
backgroundProcess = Process(target=queueWorkerThread, args=(connection, play_queue, song_id_list[2:])) # Create a thread to enqueue the remaining tracks backgroundProcess = Process(target=queue_worker_thread, args=(connection, play_queue, song_id_list[2:])) # Create a thread to enqueue the remaining tracks
backgroundProcess.start() # Start the additional thread backgroundProcess.start() # Start the additional thread
speech = sanitise_speech_output(f'Playing music by: {artist.value}') #speech = sanitise_speech_output(f'Playing music by: {artist.value}')
speech = sanitise_speech_output(f'Tocando músicas de: {artist.value}')
logger.info(speech) logger.info(speech)
card = {'title': 'AskNavidrome', card = {'title': 'AskNavidrome',
@@ -339,6 +341,97 @@ class NaviSonicPlayMusicByArtist(AbstractRequestHandler):
track_details = play_queue.get_next_track() track_details = play_queue.get_next_track()
return controller.start_playback('play', speech, card, track_details, handler_input) return controller.start_playback('play', speech, card, track_details, handler_input)
def get_current_song_details():
current_track = play_queue.get_current_track()
logger.debug(current_track)
return {
"title": current_track.title,
"artist": current_track.artist,
"album": current_track.album,
"_sanitized": {
"title": sanitise_speech_output(current_track.title),
"artist": sanitise_speech_output(current_track.artist),
"album": sanitise_speech_output(current_track.album)
}
}
class NaviSonicPlayMusicByCurrentArtist(AbstractRequestHandler):
"""Handle NaviSonicPlayMusicByCurrentArtist Intent
Plays more music of the current playing Artist
"""
def can_handle(self, handler_input: HandlerInput) -> bool:
return is_intent_name('NaviSonicPlayMusicByCurrentArtist')(handler_input)
def handle(self, handler_input: HandlerInput) -> Response:
global backgroundProcess
logger.debug('In NaviSonicPlayMusicByCurrentArtist Handler (custom)')
if backgroundProcess is not None:
backgroundProcess.terminate()
backgroundProcess.join()
t = get_current_song_details() #current "T"rack
artist_lookup = connection.search_artist(t["artist"])
artist_album_lookup = connection.albums_by_artist(artist_lookup[0].get('id'))
song_id_list = connection.build_song_list_from_albums(artist_album_lookup, min_song_count)
play_queue.clear()
controller.enqueue_songs(connection, play_queue, [song_id_list[0], song_id_list[1]]) # When generating the playlist return the first two tracks.
backgroundProcess = Process(target=queue_worker_thread, args=(connection, play_queue, song_id_list[2:])) # Create a thread to enqueue the remaining tracks
backgroundProcess.start() # Start the additional thread
speech = sanitise_speech_output(f'Tocando mais músicas de {t["artist"]}')
logger.info(speech)
card = {'title': 'AskNavidrome',
'text': speech
}
play_queue.shuffle()
track_details = play_queue.get_next_track()
return controller.start_playback('play', speech, card, track_details, handler_input)
class NaviSonicPlayMusicByCurrentAlbum(AbstractRequestHandler):
"""Handle NaviSonicPlayMusicByCurrentAlbum Intent
Plays more music of the current playing Album
"""
def can_handle(self, handler_input: HandlerInput) -> bool:
return is_intent_name('NaviSonicPlayMusicByCurrentAlbum')(handler_input)
def handle(self, handler_input: HandlerInput) -> Response:
global backgroundProcess
logger.debug('In NaviSonicPlayMusicByCurrentAlbum Handler (custom)')
if backgroundProcess is not None:
backgroundProcess.terminate()
backgroundProcess.join()
t = get_current_song_details() #current "T"rack
artist_lookup = connection.search_artist(t["artist"])
artist_album_lookup = connection.albums_by_artist(artist_lookup[0].get('id'))
result = [album_result for album_result in artist_album_lookup if album_result.get('name').lower() == t["album"].lower()]
song_id_list = connection.build_song_list_from_albums(result, -1)
play_queue.clear()
controller.enqueue_songs(connection, play_queue, [song_id_list[0], song_id_list[1]]) # When generating the playlist return the first two tracks.
backgroundProcess = Process(target=queue_worker_thread, args=(connection, play_queue, song_id_list[2:])) # Create a thread to enqueue the remaining tracks
backgroundProcess.start() # Start the additional thread
speech = sanitise_speech_output(f'Tocando mais músicas do album {t["album"]}, de {t["artist"]}')
logger.info(speech)
card = {'title': 'AskNavidrome',
'text': speech
}
play_queue.shuffle()
track_details = play_queue.get_next_track()
return controller.start_playback('play', speech, card, track_details, handler_input)
class NaviSonicPlayAlbumByArtist(AbstractRequestHandler): class NaviSonicPlayAlbumByArtist(AbstractRequestHandler):
"""Handle NaviSonicPlayAlbumByArtist """Handle NaviSonicPlayAlbumByArtist
@@ -355,7 +448,7 @@ class NaviSonicPlayAlbumByArtist(AbstractRequestHandler):
# Check if a background process is already running, if it is then terminate the process # Check if a background process is already running, if it is then terminate the process
# in favour of the new process. # in favour of the new process.
if backgroundProcess != None: if backgroundProcess is not None:
backgroundProcess.terminate() backgroundProcess.terminate()
backgroundProcess.join() backgroundProcess.join()
@@ -371,7 +464,8 @@ class NaviSonicPlayAlbumByArtist(AbstractRequestHandler):
artist_lookup = connection.search_artist(artist.value) artist_lookup = connection.search_artist(artist.value)
if artist_lookup is None: if artist_lookup is None:
text = sanitise_speech_output(f"I couldn't find the artist {artist.value} in the collection.") #text = sanitise_speech_output(f"I couldn't find the artist {artist.value} in the collection.")
text = sanitise_speech_output(f"Não achei o artista {artist.value} na nossa coleção.")
handler_input.response_builder.speak(text).ask(text) handler_input.response_builder.speak(text).ask(text)
return handler_input.response_builder.response return handler_input.response_builder.response
@@ -384,7 +478,8 @@ class NaviSonicPlayAlbumByArtist(AbstractRequestHandler):
result = [album_result for album_result in artist_album_lookup if album_result.get('name').lower() == album.value.lower()] result = [album_result for album_result in artist_album_lookup if album_result.get('name').lower() == album.value.lower()]
if not result: if not result:
text = sanitise_speech_output(f"I couldn't find an album called {album.value} by {artist.value} in the collection.") #text = sanitise_speech_output(f"I couldn't find an album called {album.value} by {artist.value} in the collection.")
text = sanitise_speech_output(f"Não achei na nossa coleção um disco chamado {album.value} de {artist.value}.")
handler_input.response_builder.speak(text).ask(text) handler_input.response_builder.speak(text).ask(text)
return handler_input.response_builder.response return handler_input.response_builder.response
@@ -395,10 +490,11 @@ class NaviSonicPlayAlbumByArtist(AbstractRequestHandler):
# Work around the Amazon / Alexa 8 second timeout. # Work around the Amazon / Alexa 8 second timeout.
controller.enqueue_songs(connection, play_queue, [song_id_list[0], song_id_list[1]]) # When generating the playlist return the first two tracks. controller.enqueue_songs(connection, play_queue, [song_id_list[0], song_id_list[1]]) # When generating the playlist return the first two tracks.
backgroundProcess = Process(target=queueWorkerThread, args=(connection, play_queue, song_id_list[2:])) # Create a thread to enqueue the remaining tracks backgroundProcess = Process(target=queue_worker_thread, args=(connection, play_queue, song_id_list[2:])) # Create a thread to enqueue the remaining tracks
backgroundProcess.start() # Start the additional thread backgroundProcess.start() # Start the additional thread
speech = sanitise_speech_output(f'Playing {album.value} by: {artist.value}') #speech = sanitise_speech_output(f'Playing {album.value} by: {artist.value}')
speech = sanitise_speech_output(f'Tocando o álbum {album.value} de: {artist.value}')
logger.info(speech) logger.info(speech)
card = {'title': 'AskNavidrome', card = {'title': 'AskNavidrome',
'text': speech 'text': speech
@@ -414,7 +510,8 @@ class NaviSonicPlayAlbumByArtist(AbstractRequestHandler):
result = connection.search_album(album.value) result = connection.search_album(album.value)
if result is None: if result is None:
text = sanitise_speech_output(f"I couldn't find the album {album.value} in the collection.") #text = sanitise_speech_output(f"I couldn't find the album {album.value} in the collection.")
text = sanitise_speech_output(f"Não achei o disco {album.value} na nossa coleção.")
handler_input.response_builder.speak(text).ask(text) handler_input.response_builder.speak(text).ask(text)
return handler_input.response_builder.response return handler_input.response_builder.response
@@ -425,11 +522,10 @@ class NaviSonicPlayAlbumByArtist(AbstractRequestHandler):
# Work around the Amazon / Alexa 8 second timeout. # Work around the Amazon / Alexa 8 second timeout.
controller.enqueue_songs(connection, play_queue, [song_id_list[0], song_id_list[1]]) # When generating the playlist return the first two tracks. controller.enqueue_songs(connection, play_queue, [song_id_list[0], song_id_list[1]]) # When generating the playlist return the first two tracks.
backgroundProcess = Process(target=queueWorkerThread, args=(connection, play_queue, song_id_list[2:])) # Create a thread to enqueue the remaining tracks backgroundProcess = Process(target=queue_worker_thread, args=(connection, play_queue, song_id_list[2:])) # Create a thread to enqueue the remaining tracks
backgroundProcess.start() # Start the additional thread backgroundProcess.start() # Start the additional thread
speech = sanitise_speech_output(f'Tocando o disco {album.value}')
speech = sanitise_speech_output(f'Playing {album.value}')
logger.info(speech) logger.info(speech)
card = {'title': 'AskNavidrome', card = {'title': 'AskNavidrome',
'text': speech 'text': speech
@@ -462,7 +558,8 @@ class NaviSonicPlaySongByArtist(AbstractRequestHandler):
artist_lookup = connection.search_artist(artist.value) artist_lookup = connection.search_artist(artist.value)
if artist_lookup is None: if artist_lookup is None:
text = sanitise_speech_output(f"I couldn't find the artist {artist.value} in the collection.") #text = sanitise_speech_output(f"I couldn't find the artist {artist.value} in the collection.")
text = sanitise_speech_output(f"Não achei o artista {artist.value} na nossa coleção.")
handler_input.response_builder.speak(text).ask(text) handler_input.response_builder.speak(text).ask(text)
return handler_input.response_builder.response return handler_input.response_builder.response
@@ -477,7 +574,8 @@ class NaviSonicPlaySongByArtist(AbstractRequestHandler):
song_dets = [item.get('id') for item in song_list if item.get('artistId') == artist_id] song_dets = [item.get('id') for item in song_list if item.get('artistId') == artist_id]
if not song_dets: if not song_dets:
text = sanitise_speech_output(f"I couldn't find a song called {song.value} by {artist.value} in the collection.") #text = sanitise_speech_output(f"I couldn't find a song called {song.value} by {artist.value} in the collection.")
text = sanitise_speech_output(f"Não achei uma música chamada {song.value} de {artist.value} na nossa coleção.")
handler_input.response_builder.speak(text).ask(text) handler_input.response_builder.speak(text).ask(text)
return handler_input.response_builder.response return handler_input.response_builder.response
@@ -485,7 +583,7 @@ class NaviSonicPlaySongByArtist(AbstractRequestHandler):
play_queue.clear() play_queue.clear()
controller.enqueue_songs(connection, play_queue, song_dets) controller.enqueue_songs(connection, play_queue, song_dets)
speech = sanitise_speech_output(f'Playing {song.value} by {artist.value}') speech = sanitise_speech_output(f'Tocando {song.value} de {artist.value}')
logger.info(speech) logger.info(speech)
card = {'title': 'AskNavidrome', card = {'title': 'AskNavidrome',
'text': speech 'text': speech
@@ -510,7 +608,7 @@ class NaviSonicPlayPlaylist(AbstractRequestHandler):
# Check if a background process is already running, if it is then terminate the process # Check if a background process is already running, if it is then terminate the process
# in favour of the new process. # in favour of the new process.
if backgroundProcess != None: if backgroundProcess is not None:
backgroundProcess.terminate() backgroundProcess.terminate()
backgroundProcess.join() backgroundProcess.join()
@@ -521,7 +619,7 @@ class NaviSonicPlayPlaylist(AbstractRequestHandler):
playlist_id = connection.search_playlist(playlist.value) playlist_id = connection.search_playlist(playlist.value)
if playlist_id is None: if playlist_id is None:
text = sanitise_speech_output("I couldn't find the playlist " + str(playlist.value) + ' in the collection.') text = sanitise_speech_output("Não achei a playlist " + str(playlist.value) + ' na nossa coleção.')
handler_input.response_builder.speak(text).ask(text) handler_input.response_builder.speak(text).ask(text)
return handler_input.response_builder.response return handler_input.response_builder.response
@@ -532,10 +630,10 @@ class NaviSonicPlayPlaylist(AbstractRequestHandler):
# Work around the Amazon / Alexa 8 second timeout. # Work around the Amazon / Alexa 8 second timeout.
controller.enqueue_songs(connection, play_queue, [song_id_list[0], song_id_list[1]]) # When generating the playlist return the first two tracks. controller.enqueue_songs(connection, play_queue, [song_id_list[0], song_id_list[1]]) # When generating the playlist return the first two tracks.
backgroundProcess = Process(target=queueWorkerThread, args=(connection, play_queue, song_id_list[2:])) # Create a thread to enqueue the remaining tracks backgroundProcess = Process(target=queue_worker_thread, args=(connection, play_queue, song_id_list[2:])) # Create a thread to enqueue the remaining tracks
backgroundProcess.start() # Start the additional thread backgroundProcess.start() # Start the additional thread
speech = sanitise_speech_output('Playing playlist ' + str(playlist.value)) speech = sanitise_speech_output('Tocando a playlist ' + str(playlist.value))
logger.info(speech) logger.info(speech)
card = {'title': 'AskNavidrome', card = {'title': 'AskNavidrome',
'text': speech 'text': speech
@@ -544,11 +642,6 @@ class NaviSonicPlayPlaylist(AbstractRequestHandler):
return controller.start_playback('play', speech, card, track_details, handler_input) return controller.start_playback('play', speech, card, track_details, handler_input)
def queueWorkerThread(connection, play_queue, song_id_list):
logger.debug('In playlist processing thread!')
controller.enqueue_songs(connection, play_queue, song_id_list)
play_queue.sync()
logger.debug('Finished playlist processing!')
class NaviSonicPlayMusicByGenre(AbstractRequestHandler): class NaviSonicPlayMusicByGenre(AbstractRequestHandler):
""" Play songs from the given genre """ Play songs from the given genre
@@ -565,7 +658,7 @@ class NaviSonicPlayMusicByGenre(AbstractRequestHandler):
# Check if a background process is already running, if it is then terminate the process # Check if a background process is already running, if it is then terminate the process
# in favour of the new process. # in favour of the new process.
if backgroundProcess != None: if backgroundProcess is not None:
backgroundProcess.terminate() backgroundProcess.terminate()
backgroundProcess.join() backgroundProcess.join()
@@ -575,7 +668,7 @@ class NaviSonicPlayMusicByGenre(AbstractRequestHandler):
song_id_list = connection.build_song_list_from_genre(genre.value, min_song_count) song_id_list = connection.build_song_list_from_genre(genre.value, min_song_count)
if song_id_list is None: if song_id_list is None:
text = sanitise_speech_output(f"I couldn't find any {genre.value} songs in the collection.") text = sanitise_speech_output(f"Não achei nada do estilo {genre.value} na nossa coleção.")
handler_input.response_builder.speak(text).ask(text) handler_input.response_builder.speak(text).ask(text)
return handler_input.response_builder.response return handler_input.response_builder.response
@@ -586,10 +679,10 @@ class NaviSonicPlayMusicByGenre(AbstractRequestHandler):
# Work around the Amazon / Alexa 8 second timeout. # Work around the Amazon / Alexa 8 second timeout.
controller.enqueue_songs(connection, play_queue, [song_id_list[0], song_id_list[1]]) # When generating the playlist return the first two tracks. controller.enqueue_songs(connection, play_queue, [song_id_list[0], song_id_list[1]]) # When generating the playlist return the first two tracks.
backgroundProcess = Process(target=queueWorkerThread, args=(connection, play_queue, song_id_list[2:])) # Create a thread to enqueue the remaining tracks backgroundProcess = Process(target=queue_worker_thread, args=(connection, play_queue, song_id_list[2:])) # Create a thread to enqueue the remaining tracks
backgroundProcess.start() # Start the additional thread backgroundProcess.start() # Start the additional thread
speech = sanitise_speech_output(f'Playing {genre.value} music') speech = sanitise_speech_output(f'Tocando músicas do estilo {genre.value}')
logger.info(speech) logger.info(speech)
card = {'title': 'AskNavidrome', card = {'title': 'AskNavidrome',
'text': speech 'text': speech
@@ -614,14 +707,15 @@ class NaviSonicPlayMusicRandom(AbstractRequestHandler):
# Check if a background process is already running, if it is then terminate the process # Check if a background process is already running, if it is then terminate the process
# in favour of the new process. # in favour of the new process.
if backgroundProcess != None: if backgroundProcess is not None:
backgroundProcess.terminate() backgroundProcess.terminate()
backgroundProcess.join() backgroundProcess.join()
song_id_list = connection.build_random_song_list(min_song_count) song_id_list = connection.build_random_song_list(min_song_count)
if song_id_list is None: if song_id_list is None:
text = sanitise_speech_output("I couldn't find any songs in the collection.") #text = sanitise_speech_output("I couldn't find any songs in the collection.")
text = sanitise_speech_output("Não achei nenhuma música na nossa coleção. Estranho.")
handler_input.response_builder.speak(text).ask(text) handler_input.response_builder.speak(text).ask(text)
return handler_input.response_builder.response return handler_input.response_builder.response
@@ -632,10 +726,10 @@ class NaviSonicPlayMusicRandom(AbstractRequestHandler):
# Work around the Amazon / Alexa 8 second timeout. # Work around the Amazon / Alexa 8 second timeout.
controller.enqueue_songs(connection, play_queue, [song_id_list[0], song_id_list[1]]) # When generating the playlist return the first two tracks. controller.enqueue_songs(connection, play_queue, [song_id_list[0], song_id_list[1]]) # When generating the playlist return the first two tracks.
backgroundProcess = Process(target=queueWorkerThread, args=(connection, play_queue, song_id_list[2:])) # Create a thread to enqueue the remaining tracks backgroundProcess = Process(target=queue_worker_thread, args=(connection, play_queue, song_id_list[2:])) # Create a thread to enqueue the remaining tracks
backgroundProcess.start() # Start the additional thread backgroundProcess.start() # Start the additional thread
speech = sanitise_speech_output('Playing random music') speech = sanitise_speech_output('Tocando qualquer coisa aleatória')
logger.info(speech) logger.info(speech)
card = {'title': 'AskNavidrome', card = {'title': 'AskNavidrome',
'text': speech 'text': speech
@@ -660,14 +754,14 @@ class NaviSonicPlayFavouriteSongs(AbstractRequestHandler):
# Check if a background process is already running, if it is then terminate the process # Check if a background process is already running, if it is then terminate the process
# in favour of the new process. # in favour of the new process.
if backgroundProcess != None: if backgroundProcess is not None:
backgroundProcess.terminate() backgroundProcess.terminate()
backgroundProcess.join() backgroundProcess.join()
song_id_list = connection.build_song_list_from_favourites() song_id_list = connection.build_song_list_from_favourites()
if song_id_list is None: if song_id_list is None:
text = sanitise_speech_output("You don't have any favourite songs in the collection.") text = sanitise_speech_output("Não tenho nada nos favoritos.")
handler_input.response_builder.speak(text).ask(text) handler_input.response_builder.speak(text).ask(text)
return handler_input.response_builder.response return handler_input.response_builder.response
@@ -678,10 +772,10 @@ class NaviSonicPlayFavouriteSongs(AbstractRequestHandler):
# Work around the Amazon / Alexa 8 second timeout. # Work around the Amazon / Alexa 8 second timeout.
controller.enqueue_songs(connection, play_queue, [song_id_list[0], song_id_list[1]]) # When generating the playlist return the first two tracks. controller.enqueue_songs(connection, play_queue, [song_id_list[0], song_id_list[1]]) # When generating the playlist return the first two tracks.
backgroundProcess = Process(target=queueWorkerThread, args=(connection, play_queue, song_id_list[2:])) # Create a thread to enqueue the remaining tracks backgroundProcess = Process(target=queue_worker_thread, args=(connection, play_queue, song_id_list[2:])) # Create a thread to enqueue the remaining tracks
backgroundProcess.start() # Start the additional thread backgroundProcess.start() # Start the additional thread
speech = sanitise_speech_output('Playing your favourite tracks.') speech = sanitise_speech_output('Tocando suas músicas favoritas.')
logger.info(speech) logger.info(speech)
card = {'title': 'AskNavidrome', card = {'title': 'AskNavidrome',
'text': speech 'text': speech
@@ -727,7 +821,8 @@ class NaviSonicSongDetails(AbstractRequestHandler):
artist = sanitise_speech_output(current_track.artist) artist = sanitise_speech_output(current_track.artist)
album = sanitise_speech_output(current_track.album) album = sanitise_speech_output(current_track.album)
text = f'This is {title} by {artist}, from the album {album}' # text = f'This is {title} by {artist}, from the album {album}'
text = f'Essa é {title} de {artist}, do album {album}'
handler_input.response_builder.speak(text) handler_input.response_builder.speak(text)
return handler_input.response_builder.response return handler_input.response_builder.response
@@ -909,7 +1004,8 @@ class NextPlaybackHandler(AbstractRequestHandler):
"""Handle NextIntent""" """Handle NextIntent"""
def can_handle(self, handler_input: HandlerInput) -> bool: def can_handle(self, handler_input: HandlerInput) -> bool:
return is_intent_name('AMAZON.NextIntent')(handler_input) return (is_intent_name('AMAZON.NextIntent')(handler_input) or
is_request_type('PlaybackController.NextCommandIssued')(handler_input))
def handle(self, handler_input: HandlerInput) -> Response: def handle(self, handler_input: HandlerInput) -> Response:
logger.debug('In NextPlaybackHandler') logger.debug('In NextPlaybackHandler')
@@ -926,7 +1022,8 @@ class PreviousPlaybackHandler(AbstractRequestHandler):
"""Handle PreviousIntent""" """Handle PreviousIntent"""
def can_handle(self, handler_input: HandlerInput) -> bool: def can_handle(self, handler_input: HandlerInput) -> bool:
return is_intent_name('AMAZON.PreviousIntent')(handler_input) return (is_intent_name('AMAZON.PreviousIntent')(handler_input) or
is_request_type('PlaybackController.PreviousCommandIssued')(handler_input))
def handle(self, handler_input: HandlerInput) -> Response: def handle(self, handler_input: HandlerInput) -> Response:
logger.debug('In PreviousPlaybackHandler') logger.debug('In PreviousPlaybackHandler')
@@ -993,7 +1090,8 @@ class SystemExceptionHandler(AbstractExceptionHandler):
if get_request_type(handler_input) == 'IntentRequest': if get_request_type(handler_input) == 'IntentRequest':
logger.error(f'Intent Name Was: {get_intent_name(handler_input)}') logger.error(f'Intent Name Was: {get_intent_name(handler_input)}')
speech = sanitise_speech_output("Sorry, I didn't get that. Can you please say it again!!") #speech = sanitise_speech_output("Sorry, I didn't get that. Can you please say it again!!")
speech = sanitise_speech_output("Foi mal, não entendi.")
handler_input.response_builder.speak(speech).ask(speech) handler_input.response_builder.speak(speech).ask(speech)
return handler_input.response_builder.response return handler_input.response_builder.response
@@ -1019,7 +1117,8 @@ class GeneralExceptionHandler(AbstractExceptionHandler):
if get_request_type(handler_input) == 'IntentRequest': if get_request_type(handler_input) == 'IntentRequest':
logger.error(f'Intent Name Was: {get_intent_name(handler_input)}') logger.error(f'Intent Name Was: {get_intent_name(handler_input)}')
speech = sanitise_speech_output("Sorry, I didn't get that. Can you please say it again!!") #speech = sanitise_speech_output("Sorry, I didn't get that. Can you please say it again!!")
speech = sanitise_speech_output("Foi mal, não entendi. Pode repetir!!")
handler_input.response_builder.speak(speech).ask(speech) handler_input.response_builder.speak(speech).ask(speech)
return handler_input.response_builder.response return handler_input.response_builder.response
@@ -1053,6 +1152,7 @@ class LoggingResponseInterceptor(AbstractResponseInterceptor):
# Functions # Functions
# #
def sanitise_speech_output(speech_string: str) -> str: def sanitise_speech_output(speech_string: str) -> str:
"""Sanitise speech output inline with the SSML standard """Sanitise speech output inline with the SSML standard
@@ -1084,6 +1184,27 @@ def sanitise_speech_output(speech_string: str) -> str:
return speech_string return speech_string
def queue_worker_thread(connection: object, play_queue: object, song_id_list: list) -> None:
"""Media queue worker
This function allows media queues to be populated in the background enabling multithreading
and increasing skill response times.
:param connection: A SubSonic API connection object
:type connection: object
:param play_queue: A MediaQueue object
:type play_queue: object
:param song_id_list: A list containing Navidrome song IDs
:type song_id_list: list
"""
logger.debug('In playlist processing thread!')
controller.enqueue_songs(connection, play_queue, song_id_list)
play_queue.sync()
logger.debug('Finished playlist processing!')
# Register Intent Handlers # Register Intent Handlers
sb.add_request_handler(LaunchRequestHandler()) sb.add_request_handler(LaunchRequestHandler())
sb.add_request_handler(CheckAudioInterfaceHandler()) sb.add_request_handler(CheckAudioInterfaceHandler())
@@ -1096,6 +1217,8 @@ sb.add_request_handler(NaviSonicPlayPlaylist())
sb.add_request_handler(NaviSonicPlayFavouriteSongs()) sb.add_request_handler(NaviSonicPlayFavouriteSongs())
sb.add_request_handler(NaviSonicPlayMusicByGenre()) sb.add_request_handler(NaviSonicPlayMusicByGenre())
sb.add_request_handler(NaviSonicPlayMusicRandom()) sb.add_request_handler(NaviSonicPlayMusicRandom())
sb.add_request_handler(NaviSonicPlayMusicByCurrentArtist())
sb.add_request_handler(NaviSonicPlayMusicByCurrentAlbum())
sb.add_request_handler(NaviSonicRandomiseQueue()) sb.add_request_handler(NaviSonicRandomiseQueue())
sb.add_request_handler(NaviSonicSongDetails()) sb.add_request_handler(NaviSonicSongDetails())
sb.add_request_handler(NaviSonicStarSong()) sb.add_request_handler(NaviSonicStarSong())
Regular → Executable
+14 -2
View File
@@ -42,6 +42,18 @@ def start_playback(mode: str, text: str, card_data: dict, track_details: Track,
:return: Amazon Alexa Response class :return: Amazon Alexa Response class
:rtype: Response :rtype: Response
""" """
metadata = AudioItemMetadata(
title=track_details.title,
subtitle=track_details.artist,
art=display.Image(
content_description=track_details.title,
sources=[
display.ImageInstance(
url='https://github.com/navidrome/navidrome/raw/master/resources/logo-192x192.png'
)
]
)
)
if mode == 'play': if mode == 'play':
# Starting playback # Starting playback
@@ -67,7 +79,7 @@ def start_playback(mode: str, text: str, card_data: dict, track_details: Track,
url=track_details.uri, url=track_details.uri,
offset_in_milliseconds=track_details.offset, offset_in_milliseconds=track_details.offset,
expected_previous_token=None), expected_previous_token=None),
metadata=add_screen_background(card_data) if card_data else None metadata=metadata
) )
) )
).set_should_end_session(True) ).set_should_end_session(True)
@@ -95,7 +107,7 @@ def start_playback(mode: str, text: str, card_data: dict, track_details: Track,
# if the Previous intent is used # if the Previous intent is used
offset_in_milliseconds=0, offset_in_milliseconds=0,
expected_previous_token=track_details.previous_id), expected_previous_token=track_details.previous_id),
metadata=None metadata=metadata
) )
) )
).set_should_end_session(True) ).set_should_end_session(True)
+21 -9
View File
@@ -40,29 +40,41 @@ class SubsonicConnection:
self.api_version, self.api_version,
False) False)
self.logger.debug('Connected to Navidrome') self.logger.debug('Connecting to Navidrome.....')
def ping(self) -> bool: def ping(self) -> bool:
"""Ping a Subsonic API server """Ping a Subsonic API server
Verify the connection to a Subsonic compatible API server Verify the connection to a Subsonic compatible API server
is working is working. Enhanced logging has been added to examine the
HTTP response returned from the server to assist with troubleshooting
connection issues.
:return: True if the connection works, False if it does not :return: True if the connection works, False if it does not
:rtype: bool :rtype: bool
""" """
self.logger.debug('In function ping()') self.logger.debug('In function ping()')
status = self.conn.ping() http_request = self.conn._getRequest('ping.view')
try:
http_request_result = self.conn._doInfoReq(http_request)
except Exception as e:
self.logger.error('Failed to connect to Navidrome: %s', e, exc_info=True)
return False
if status: self.logger.debug('ping() response from server: %s', http_request_result)
# Success status = http_request_result.get('status')
if status == 'ok':
self.logger.info('Successfully connected to Navidrome') self.logger.info('Successfully connected to Navidrome')
return True
elif status == 'failed':
err = http_request_result.get('error', {})
self.logger.error('Failed to connect to Navidrome: code=%s msg=%s',
err.get('code'), err.get('message'))
return False
else: else:
# Fail self.logger.error('Unexpected error when connecting to Navidrome: %r', status)
self.logger.error('Failed to connect to Navidrome') return False
return self.conn.ping()
def scrobble(self, track_id: str, time: int) -> None: def scrobble(self, track_id: str, time: int) -> None:
"""Scrobble the given track """Scrobble the given track
+1 -1
View File
@@ -22,7 +22,7 @@ copyright = '2025, Ross Stewart'
author = 'Ross Stewart' author = 'Ross Stewart'
# The full version, including alpha/beta/rc tags # The full version, including alpha/beta/rc tags
release = '0.8' release = '0.9'
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------