Compare commits

...

10 Commits

Author SHA1 Message Date
2ee1abd813 customizadas em portugues: interaction model e respostas da skill
Some checks failed
Documentation Generator / Build Sphinx documentation (push) Has been cancelled
2025-11-08 12:50:08 -03: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
15 changed files with 180 additions and 133 deletions

View File

@@ -18,10 +18,16 @@ jobs:
steps:
- 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
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
uses: docker/login-action@v3.5.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -29,14 +35,15 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
uses: docker/metadata-action@v5.8.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
uses: docker/build-push-action@v6.18.0
with:
context: .
platforms: linux/amd64, linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

15
.vscode/launch.json vendored
View File

@@ -6,21 +6,30 @@
"configurations": [
{
"name": "Python: Current File",
"type": "python",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true
},
{
"name": "Python: Flask",
"type": "python",
"name": "Python: Flask - AskNavidrome",
"type": "debugpy",
"request": "launch",
"module": "flask",
"env": {
"FLASK_APP": "skill/app.py",
"FLASK_ENV": "development",
"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": [
"run",

View File

@@ -1,6 +1,6 @@
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
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
@@ -15,8 +15,9 @@ WORKDIR /opt/asknavidrome
RUN source ../env/bin/activate && pip --no-cache-dir install wheel && pip --no-cache-dir install -r skill/requirements-docker.txt
FROM alpine:3.15.0
FROM alpine:3.22.1
LABEL maintainer="Ross Stewart <rosskouk@gmail.com>"
LABEL org.opencontainers.image.source=https://github.com/rosskouk/asknavidrome
RUN apk add python3

View File

@@ -1,7 +1,7 @@
{
"interactionModel": {
"languageModel": {
"invocationName": "navisonic",
"invocationName": "cafofo music",
"intents": [
{
"name": "AMAZON.CancelIntent",
@@ -40,8 +40,8 @@
}
],
"samples": [
"play songs by {artist}",
"play music by {artist}"
"tocar músicas da {artist}",
"tocar músicas do {artist}"
]
},
{
@@ -65,8 +65,12 @@
}
],
"samples": [
"Play the album {album}",
"Play the album {album} by {artist}"
"Tocar o album {album}",
"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}"
]
},
{
@@ -78,49 +82,42 @@
}
],
"samples": [
"Play the {playlist} playlist",
"Start the {playlist} playlist"
"Tocar a playlist {playlist}"
]
},
{
"name": "NaviSonicSongDetails",
"slots": [],
"samples": [
"What is playing",
"Who is singing",
"Who's singing",
"What album is this song on",
"What album is this on",
"Which album",
"Which album is this song from",
"Which artist is this",
"What band is this",
"Who sings this song",
"Who sings this",
"What song is this",
"What's playing"
"O que está tocando",
"Quem está tocando",
"Quem está cantando",
"Qual o album dessa música",
"Que banda é essa",
"Que musica é essa",
"Que banda é essa"
]
},
{
"name": "NaviSonicStarSong",
"slots": [],
"samples": [
"Add this song to my favourites",
"Favourite this song",
"Like this song",
"Add this song to my liked songs",
"Star this song"
"Adicionar música aos favoritos",
"Adicionar aos favoritos",
"Favoritar essa musica",
"Lembrar dessa música",
"Eu gosto dessa música"
]
},
{
"name": "NaviSonicUnstarSong",
"slots": [],
"samples": [
"Remove this song from favourites",
"I don't like this song",
"Remove the star from this song",
"Delete this song from my favourites",
"Unstar this song"
"Tirar a música dos favoritos",
"Tirar dos favoritos",
"Desfavoritar a música",
"Esquecer essa música",
"Eu não gosto dessa música"
]
},
{
@@ -136,21 +133,18 @@
}
],
"samples": [
"Play the song {song} by the band {artist}",
"Play {song} by the band {artist}",
"Play {song} by {artist}",
"Play the song {song} by the artist {artist}"
"Tocar a música {song} da banda {artist}",
"Tocar {song} da banda {artist}",
"Tocar {song} da {artist}"
]
},
{
"name": "NaviSonicPlayFavouriteSongs",
"slots": [],
"samples": [
"Play my starred tracks",
"Play starred tracks",
"Play starred songs",
"Play my starred songs",
"Play my favourite songs"
"Tocar favoritos",
"Tocar meus favoritos",
"Tocar minhas músicas favoritas"
]
},
{
@@ -162,29 +156,26 @@
}
],
"samples": [
"Play {genre} songs",
"Play {genre} music"
"Tocar {genre}"
]
},
{
"name": "NaviSonicPlayMusicRandom",
"slots": [],
"samples": [
"Play a selection of music",
"Play a mix of tracks",
"Play a mix of songs",
"Play random music",
"Play random songs"
"Tocar seleção aleatória",
"Tocar qualquer coisa",
"Tocar mix",
"Tocar músicas aleatórias"
]
},
{
"name": "NaviSonicRandomiseQueue",
"slots": [],
"samples": [
"randomise the queue",
"randomise",
"shuffle",
"shuffle the queue"
"shuffle na lista",
"mistura tudo"
]
}
],
@@ -212,4 +203,4 @@
]
}
}
}
}

View File

@@ -1,5 +1,5 @@
const DOCUMENTATION_OPTIONS = {
VERSION: '0.8',
VERSION: '0.9',
LANGUAGE: 'en',
COLLAPSE_INDEX: false,
BUILDER: 'html',

View File

@@ -7,7 +7,7 @@
<head>
<meta charset="utf-8" />
<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" />
<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/sphinx_highlight.js?v=dc90522c"></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>
<div class="sidebar-primary-item">
@@ -612,7 +612,7 @@ document.write(`
</li>
</ul></td>
<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>
</ul></td>
</tr></table>

View File

@@ -8,7 +8,7 @@
<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" />
<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" />
<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/sphinx_highlight.js?v=dc90522c"></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>
<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>
</ul>
</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>
</ul>
</li>
@@ -1196,8 +1196,8 @@ and setting it to <em>DNS Only</em>.</p>
<p><strong>Functions:</strong></p>
<div class="pst-scrollable-table-container"><table class="autosummary longtable table">
<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>
<td><p></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>Media queue worker</p></td>
</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>
<td><p>Sanitise speech output inline with the SSML standard</p></td>
@@ -2787,9 +2787,21 @@ during dispatch.</p>
</dd></dl>
<dl class="py function">
<dt class="sig sig-object py" id="app.queueWorkerThread">
<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>
<dd></dd></dl>
<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">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><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">
<dt class="sig sig-object py" id="app.sanitise_speech_output">
@@ -3090,7 +3102,7 @@ request early to queue the next track while maintaining the playlist</p>
<span class="sig-name descname"><span class="pre">get_current_track</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"><a class="reference internal" href="#asknavidrome.track.Track" title="asknavidrome.track.Track"><span class="pre">Track</span></a></span></span><a class="headerlink" href="#asknavidrome.media_queue.MediaQueue.get_current_track" title="Link to this definition">#</a></dt>
<dd><p>Method to return current_track attribute</p>
<p>Added to allow access to the current_track object while using BaseManager
for multi threading, as BaseManager does not allow access to class
for multi threading, as BaseManager does not allow access to class
attributes / properties</p>
<dl class="field-list simple">
<dt class="field-odd">Returns<span class="colon">:</span></dt>
@@ -3181,7 +3193,7 @@ add it to the front of the play queue</p>
<span class="sig-name descname"><span class="pre">set_current_track_offset</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">offset</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">int</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="#asknavidrome.media_queue.MediaQueue.set_current_track_offset" title="Link to this definition">#</a></dt>
<dd><p>Method to set the offset of the current track in milliseconds</p>
<p>Set the offset for the current track in milliseconds. This is used
when resuming a paused track to ensure the track isnt played from
when resuming a paused track to ensure the track isnt played from
the beginning again.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
@@ -3842,7 +3854,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>
</ul>
</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>
</ul>
</li>

Binary file not shown.

View File

@@ -7,7 +7,7 @@
<head>
<meta charset="utf-8" />
<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" />
<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/sphinx_highlight.js?v=dc90522c"></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>
<div class="sidebar-primary-item">

View File

@@ -6,7 +6,7 @@
<head>
<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" />
<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/sphinx_highlight.js?v=dc90522c"></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>
<div class="sidebar-primary-item">

File diff suppressed because one or more lines are too long

View File

@@ -42,7 +42,7 @@ logger.addHandler(handler)
# Get service configuration
#
logger.info('AskNavidrome 0.6!')
logger.info('AskNavidrome 0.9!')
logger.debug('Getting configuration from the environment...')
try:
@@ -222,7 +222,7 @@ class LaunchRequestHandler(AbstractRequestHandler):
logger.debug('In LaunchRequestHandler')
connection.ping()
speech = sanitise_speech_output('Ready!')
speech = sanitise_speech_output('Simbora!')
handler_input.response_builder.speak(speech).ask(speech)
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
# in favour of the new process.
if backgroundProcess != None:
if backgroundProcess is not None:
backgroundProcess.terminate()
backgroundProcess.join()
@@ -311,7 +311,8 @@ class NaviSonicPlayMusicByArtist(AbstractRequestHandler):
artist_lookup = connection.search_artist(artist.value)
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)
return handler_input.response_builder.response
@@ -325,10 +326,11 @@ class NaviSonicPlayMusicByArtist(AbstractRequestHandler):
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=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
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)
card = {'title': 'AskNavidrome',
@@ -355,7 +357,7 @@ class NaviSonicPlayAlbumByArtist(AbstractRequestHandler):
# Check if a background process is already running, if it is then terminate the process
# in favour of the new process.
if backgroundProcess != None:
if backgroundProcess is not None:
backgroundProcess.terminate()
backgroundProcess.join()
@@ -371,7 +373,8 @@ class NaviSonicPlayAlbumByArtist(AbstractRequestHandler):
artist_lookup = connection.search_artist(artist.value)
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)
return handler_input.response_builder.response
@@ -384,7 +387,8 @@ class NaviSonicPlayAlbumByArtist(AbstractRequestHandler):
result = [album_result for album_result in artist_album_lookup if album_result.get('name').lower() == album.value.lower()]
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)
return handler_input.response_builder.response
@@ -395,10 +399,11 @@ class NaviSonicPlayAlbumByArtist(AbstractRequestHandler):
# 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.
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
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)
card = {'title': 'AskNavidrome',
'text': speech
@@ -414,7 +419,8 @@ class NaviSonicPlayAlbumByArtist(AbstractRequestHandler):
result = connection.search_album(album.value)
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)
return handler_input.response_builder.response
@@ -425,11 +431,10 @@ class NaviSonicPlayAlbumByArtist(AbstractRequestHandler):
# 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.
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
speech = sanitise_speech_output(f'Playing {album.value}')
speech = sanitise_speech_output(f'Tocando o disco {album.value}')
logger.info(speech)
card = {'title': 'AskNavidrome',
'text': speech
@@ -462,7 +467,8 @@ class NaviSonicPlaySongByArtist(AbstractRequestHandler):
artist_lookup = connection.search_artist(artist.value)
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)
return handler_input.response_builder.response
@@ -477,7 +483,8 @@ class NaviSonicPlaySongByArtist(AbstractRequestHandler):
song_dets = [item.get('id') for item in song_list if item.get('artistId') == artist_id]
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)
return handler_input.response_builder.response
@@ -485,7 +492,7 @@ class NaviSonicPlaySongByArtist(AbstractRequestHandler):
play_queue.clear()
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)
card = {'title': 'AskNavidrome',
'text': speech
@@ -510,7 +517,7 @@ class NaviSonicPlayPlaylist(AbstractRequestHandler):
# Check if a background process is already running, if it is then terminate the process
# in favour of the new process.
if backgroundProcess != None:
if backgroundProcess is not None:
backgroundProcess.terminate()
backgroundProcess.join()
@@ -521,7 +528,7 @@ class NaviSonicPlayPlaylist(AbstractRequestHandler):
playlist_id = connection.search_playlist(playlist.value)
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)
return handler_input.response_builder.response
@@ -532,10 +539,10 @@ class NaviSonicPlayPlaylist(AbstractRequestHandler):
# 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.
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
speech = sanitise_speech_output('Playing playlist ' + str(playlist.value))
speech = sanitise_speech_output('Tocando a playlist ' + str(playlist.value))
logger.info(speech)
card = {'title': 'AskNavidrome',
'text': speech
@@ -544,11 +551,6 @@ class NaviSonicPlayPlaylist(AbstractRequestHandler):
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):
""" Play songs from the given genre
@@ -565,7 +567,7 @@ class NaviSonicPlayMusicByGenre(AbstractRequestHandler):
# Check if a background process is already running, if it is then terminate the process
# in favour of the new process.
if backgroundProcess != None:
if backgroundProcess is not None:
backgroundProcess.terminate()
backgroundProcess.join()
@@ -575,7 +577,7 @@ class NaviSonicPlayMusicByGenre(AbstractRequestHandler):
song_id_list = connection.build_song_list_from_genre(genre.value, min_song_count)
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)
return handler_input.response_builder.response
@@ -586,10 +588,10 @@ class NaviSonicPlayMusicByGenre(AbstractRequestHandler):
# 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.
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
speech = sanitise_speech_output(f'Playing {genre.value} music')
speech = sanitise_speech_output(f'Tocando músicas do estilo {genre.value}')
logger.info(speech)
card = {'title': 'AskNavidrome',
'text': speech
@@ -614,14 +616,15 @@ class NaviSonicPlayMusicRandom(AbstractRequestHandler):
# Check if a background process is already running, if it is then terminate the process
# in favour of the new process.
if backgroundProcess != None:
if backgroundProcess is not None:
backgroundProcess.terminate()
backgroundProcess.join()
song_id_list = connection.build_random_song_list(min_song_count)
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)
return handler_input.response_builder.response
@@ -632,10 +635,10 @@ class NaviSonicPlayMusicRandom(AbstractRequestHandler):
# 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.
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
speech = sanitise_speech_output('Playing random music')
speech = sanitise_speech_output('Tocando qualquer coisa aleatória')
logger.info(speech)
card = {'title': 'AskNavidrome',
'text': speech
@@ -660,14 +663,14 @@ class NaviSonicPlayFavouriteSongs(AbstractRequestHandler):
# Check if a background process is already running, if it is then terminate the process
# in favour of the new process.
if backgroundProcess != None:
if backgroundProcess is not None:
backgroundProcess.terminate()
backgroundProcess.join()
song_id_list = connection.build_song_list_from_favourites()
if song_id_list is None:
text = sanitise_speech_output("You don't have any favourite songs in the collection.")
text = sanitise_speech_output("Você não tem nada nos favoritos.")
handler_input.response_builder.speak(text).ask(text)
return handler_input.response_builder.response
@@ -678,10 +681,10 @@ class NaviSonicPlayFavouriteSongs(AbstractRequestHandler):
# 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.
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
speech = sanitise_speech_output('Playing your favourite tracks.')
speech = sanitise_speech_output('Tocando suas músicas favoritas.')
logger.info(speech)
card = {'title': 'AskNavidrome',
'text': speech
@@ -993,7 +996,8 @@ class SystemExceptionHandler(AbstractExceptionHandler):
if get_request_type(handler_input) == 'IntentRequest':
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)
return handler_input.response_builder.response
@@ -1019,7 +1023,8 @@ class GeneralExceptionHandler(AbstractExceptionHandler):
if get_request_type(handler_input) == 'IntentRequest':
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)
return handler_input.response_builder.response
@@ -1053,6 +1058,7 @@ class LoggingResponseInterceptor(AbstractResponseInterceptor):
# Functions
#
def sanitise_speech_output(speech_string: str) -> str:
"""Sanitise speech output inline with the SSML standard
@@ -1084,6 +1090,27 @@ def sanitise_speech_output(speech_string: str) -> str:
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
sb.add_request_handler(LaunchRequestHandler())
sb.add_request_handler(CheckAudioInterfaceHandler())

View File

@@ -44,19 +44,19 @@ class MediaQueue:
"""Method to return current_track attribute
Added to allow access to the current_track object while using BaseManager
for multi threading, as BaseManager does not allow access to class
for multi threading, as BaseManager does not allow access to class
attributes / properties
:return: A Track object representing the current playing audio track
:rtype: Track
"""
return self.current_track
def set_current_track_offset(self, offset: int) -> None:
"""Method to set the offset of the current track in milliseconds
Set the offset for the current track in milliseconds. This is used
when resuming a paused track to ensure the track isn't played from
when resuming a paused track to ensure the track isn't played from
the beginning again.
:param offset: The track offset in milliseconds
@@ -75,7 +75,7 @@ class MediaQueue:
"""
return self.queue
def get_buffer(self) -> deque:
"""Get the buffer
@@ -86,7 +86,7 @@ class MediaQueue:
"""
return self.buffer
def get_history(self) -> deque:
"""Get history
@@ -97,7 +97,7 @@ class MediaQueue:
"""
return self.history
def add_track(self, track: Track) -> None:
"""Add tracks to the queue

View File

@@ -63,7 +63,7 @@ class SubsonicConnection:
self.logger.error('Failed to connect to Navidrome')
return self.conn.ping()
def scrobble(self, track_id: str, time: int) -> None:
"""Scrobble the given track

View File

@@ -22,7 +22,7 @@ copyright = '2025, Ross Stewart'
author = 'Ross Stewart'
# The full version, including alpha/beta/rc tags
release = '0.8'
release = '0.9'
# -- General configuration ---------------------------------------------------